elfisa-pharmacy/docs/API.md
Magomed 5c439fe780 Apply client markup_percent from price API and fix truncated download logs.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-20 11:36:20 +03:00

293 lines
8.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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}"
```