293 lines
8.8 KiB
Markdown
293 lines
8.8 KiB
Markdown
# API — Электронная Фармация
|
||
|
||
Документация по HTTP-запросам, которые использует десктоп-клиент.
|
||
|
||
Реализация: `src/ElectronicPharmacy/Classes/ApiClient.cs`, `DataSender.cs`, `InvoiceSyncService.cs`.
|
||
|
||
---
|
||
|
||
## Базовый URL
|
||
|
||
```
|
||
{ApiBaseUrl}
|
||
```
|
||
|
||
|
||
| Источник | Приоритет |
|
||
| -------------------------------------------------------------------- | --------------------------- |
|
||
| Настройки пользователя (`Settings.ApiBaseUrl`, окно **Авторизация**) | 1 |
|
||
| Переменная окружения `ELF_API_BASE_URL` | 2 |
|
||
| Значение по умолчанию | `http://195.34.241.84:9988` |
|
||
|
||
|
||
**Таймаут:** 30 секунд (`ApiClient`). Для отправки заказов (`DataSender`) таймаут не задан явно.
|
||
|
||
**Формат:** JSON, кодировка UTF-8.
|
||
|
||
---
|
||
|
||
## Авторизация
|
||
|
||
Все запросы, кроме логина, требуют заголовок:
|
||
|
||
```
|
||
Authorization: Bearer {JWT}
|
||
```
|
||
|
||
Токен сохраняется в `Settings.stringToken` после успешного входа.
|
||
|
||
---
|
||
|
||
## Эндпоинты
|
||
|
||
### 1. Вход
|
||
|
||
|
||
| | |
|
||
| --------------- | ------------- |
|
||
| **Метод** | `POST` |
|
||
| **Путь** | `/auth/login` |
|
||
| **Авторизация** | не требуется |
|
||
|
||
|
||
**Где в приложении:** окно **Авторизация** (`HF_Registration`), загрузка прайса (`HF_DownloadDataFromServer` — если токен отсутствует).
|
||
|
||
**Запрос:**
|
||
|
||
```json
|
||
{
|
||
"username": "логин",
|
||
"password": "пароль"
|
||
}
|
||
```
|
||
|
||
**Ответ `200`:**
|
||
|
||
```json
|
||
{
|
||
"token": "jwt..."
|
||
}
|
||
```
|
||
|
||
**Ошибка `401`:**
|
||
|
||
```json
|
||
{
|
||
"error": "описание ошибки"
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### 2. Сводный прайс
|
||
|
||
|
||
| | |
|
||
| --------- | ---------------------------------------- |
|
||
| **Метод** | `GET` |
|
||
| **Путь** | `/api/supplier-prices/summary` |
|
||
| **Query** | `supplier_id` (опц.), `region_id` (опц.) |
|
||
|
||
|
||
**Где в приложении:** меню **Обмен → Загрузить**, кнопка обновления прайса в главном окне (`HF_DownloadDataFromServer`).
|
||
|
||
**Примеры:**
|
||
|
||
```http
|
||
GET /api/supplier-prices/summary
|
||
GET /api/supplier-prices/summary?region_id={RegionId}
|
||
GET /api/supplier-prices/summary?supplier_id={uuid}
|
||
```
|
||
|
||
**Ответ `200`:**
|
||
|
||
```json
|
||
{
|
||
"markup_percent": 15.5,
|
||
"summary": [
|
||
{
|
||
"supplier_price_id": "uuid",
|
||
"guid_es": "uuid",
|
||
"supplier_id": "uuid",
|
||
"supplier_name": "Катрен",
|
||
"drug_name": "Название препарата",
|
||
"inn": "МНН",
|
||
"cure_form": "таб.",
|
||
"barcode": "...",
|
||
"price": 123.45,
|
||
"markup_percent": 15.5,
|
||
"quantity": 10,
|
||
"region_id": "...",
|
||
"region_name": "...",
|
||
"last_price_date": "2026-07-14T00:00:00Z",
|
||
"match_method": "BARCODE",
|
||
"match_confidence": 0.95,
|
||
"trade_name": "...",
|
||
"dosage": "...",
|
||
"registry_price": 100.00,
|
||
"instruction_guid": "...",
|
||
"description": "...",
|
||
"storing_condition": "...",
|
||
"expiry_period": "12.2027",
|
||
"producer_name": "...",
|
||
"registry_date": "...",
|
||
"es_code": 123456,
|
||
"registry_status": "..."
|
||
}
|
||
]
|
||
}
|
||
```
|
||
|
||
**После ответа:** данные записываются в локальную SQLite-таблицу `PriceList` (полная перезапись).
|
||
|
||
**Класс:** `ApiClient.GetPriceSummaryAsync`
|
||
|
||
---
|
||
|
||
### 3. Отправка заказа
|
||
|
||
|
||
| | |
|
||
| ---------------- | ------------------- |
|
||
| **Метод** | `POST` |
|
||
| **Путь** | `/api/buyer/orders` |
|
||
| **Content-Type** | `application/json` |
|
||
|
||
|
||
**Где в приложении:**
|
||
|
||
- меню **Обмен → Отправить** — все заказы со статусом `НОВЫЙ` (`DataSender.Main`);
|
||
- страница **Заказы**, кнопка **ОТПРАВИТЬ** — один выбранный заказ (`DataSender.SendSingleOrderAsync`).
|
||
|
||
**Запрос** (один заказ = один POST):
|
||
|
||
```json
|
||
{
|
||
"location_id": "uuid точки из Settings.LocationId",
|
||
"comment": "комментарий или null",
|
||
"items": [
|
||
{
|
||
"supplier_price_id": "uuid позиции из прайса",
|
||
"qty": 5
|
||
}
|
||
]
|
||
}
|
||
```
|
||
|
||
**Условия отправки:**
|
||
|
||
- в локальной БД заказ имеет статус `НОВЫЙ`;
|
||
- у каждой позиции заполнен `supplier_price_id`;
|
||
- `qty` > 0;
|
||
- задан `location_id` в настройках.
|
||
|
||
**При успехе:** локально `Orders.OrderState` → `ОТПРАВЛЕН` (только для отправленного заказа).
|
||
|
||
**При ошибке:** toast с HTTP-кодом и телом ответа сервера.
|
||
|
||
**Класс:** `DataSender`
|
||
|
||
---
|
||
|
||
### 4. Накладные и отказы
|
||
|
||
|
||
| | |
|
||
| --------- | --------------------- |
|
||
| **Метод** | `GET` |
|
||
| **Путь** | `/api/buyer/invoices` |
|
||
|
||
|
||
**Где в приложении:** кнопка **Обновить с сервера** на вкладках **Накладные** и **Отказы** (`InvoiceSyncService`).
|
||
|
||
**Ответ `200`:** массив объектов
|
||
|
||
```json
|
||
[
|
||
{
|
||
"InvoiceNumber": "12345",
|
||
"InvoiceDate": "2026-07-14",
|
||
"SupplierName": "Поставщик",
|
||
"ConsigneeName": "Грузополучатель",
|
||
"InvoiceSum": "10000.00",
|
||
"RefuseSum": "500.00"
|
||
}
|
||
]
|
||
```
|
||
|
||
**После ответа:** `INSERT OR REPLACE` в локальную таблицу `Invoice`.
|
||
|
||
**Класс:** `ApiClient.GetInvoicesAsync`
|
||
|
||
---
|
||
|
||
## Сводная таблица
|
||
|
||
|
||
| Действие в UI | HTTP | Эндпоинт | Класс / метод |
|
||
| --------------------------- | ------ | ------------------------------ | -------------------------------- |
|
||
| Авторизация | `POST` | `/auth/login` | `ApiClient.LoginAsync` |
|
||
| Загрузить прайс | `GET` | `/api/supplier-prices/summary` | `ApiClient.GetPriceSummaryAsync` |
|
||
| Отправить заказ(ы) | `POST` | `/api/buyer/orders` | `DataSender.SendOrderAsync` |
|
||
| Обновить накладные / отказы | `GET` | `/api/buyer/invoices` | `ApiClient.GetInvoicesAsync` |
|
||
|
||
|
||
---
|
||
|
||
## Обработка ошибок
|
||
|
||
|
||
| Ситуация | Поведение клиента |
|
||
| ------------------ | ---------------------------------------------------------------------------- |
|
||
| `401 Unauthorized` | Сообщение об ошибке авторизации; для прайса токен сбрасывается в `ApiClient` |
|
||
| Таймаут (30 с) | `HttpRequestException` с текстом о превышении времени ожидания |
|
||
| Прочие HTTP-ошибки | Тело `{ "error": "..." }` если сервер вернул JSON |
|
||
| Нет `location_id` | Отправка заказов блокируется до заполнения в настройках |
|
||
| Нет токена | Загрузка накладных и отправка заказов блокируются |
|
||
|
||
|
||
---
|
||
|
||
## Что не реализовано
|
||
|
||
- `PUT`, `PATCH`, `DELETE` к API
|
||
- Получение статуса заказа с сервера
|
||
- Синхронизация справочников (поставщики, грузополучатели) через API
|
||
- Автоматический re-login при истечении токена
|
||
|
||
---
|
||
|
||
## Примеры curl
|
||
|
||
**Логин:**
|
||
|
||
```bash
|
||
curl -X POST "{ApiBaseUrl}/auth/login" \
|
||
-H "Content-Type: application/json" \
|
||
-d '{"username":"user","password":"pass"}'
|
||
```
|
||
|
||
**Прайс:**
|
||
|
||
```bash
|
||
curl "{ApiBaseUrl}/api/supplier-prices/summary" \
|
||
-H "Authorization: Bearer {token}"
|
||
```
|
||
|
||
**Отправка заказа:**
|
||
|
||
```bash
|
||
curl -X POST "{ApiBaseUrl}/api/buyer/orders" \
|
||
-H "Authorization: Bearer {token}" \
|
||
-H "Content-Type: application/json" \
|
||
-d '{"location_id":"...","comment":null,"items":[{"supplier_price_id":"...","qty":1}]}'
|
||
```
|
||
|
||
**Накладные:**
|
||
|
||
```bash
|
||
curl "{ApiBaseUrl}/api/buyer/invoices" \
|
||
-H "Authorization: Bearer {token}"
|
||
```
|
||
|