# 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 { "summary": [ { "supplier_price_id": "uuid", "guid_es": "uuid", "supplier_id": "uuid", "supplier_name": "Катрен", "drug_name": "Название препарата", "inn": "МНН", "cure_form": "таб.", "barcode": "...", "price": 123.45, "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}" ```