diff --git a/README.md b/README.md
index ef6626b..5d097d1 100644
--- a/README.md
+++ b/README.md
@@ -1,67 +1,136 @@
-# ЭльФиСА — Электронная Фармация
-
-WinForms-клиент «Электронная Фармация» с современным UI на базе библиотеки **Elfisa.UI** (тема **PharmaTrust**).
-
-## Структура
-
-```
-src/
- Elfisa.UI/ — UI-библиотека (ThemeManager, Modern* контролы)
- ElectronicPharmacy/ — основное приложение (SQLite, API, бизнес-логика)
-```
-
-## Сборка
-
-Требуется .NET SDK 9+ (или MSBuild 16+) и .NET Framework 4.7.2.
-
-```powershell
-dotnet build Elfisa.sln -c Release
-```
-
-Запуск:
-
-```powershell
-.\src\ElectronicPharmacy\bin\Release\Электронная` Фармация.exe
-```
-
-База `efClient.db` копируется в output (`Data\efClient.db` или рядом с exe). Путь задаётся через `AppConfig.SqliteDbPath`.
-
-## Настройки
-
-В диалоге **Авторизация** (и в `Settings`):
-
-- **URL API** — базовый адрес сервера (по умолчанию из `AppConfig`)
-- **Location ID** — UUID точки для отправки заказов
-- **Region ID** — опциональный фильтр при загрузке прайса
-
-Переменная окружения `ELF_API_BASE_URL` переопределяет URL API.
-
-## Архив для заказчика
-
-```powershell
-.\dist\build-release.ps1
-```
-
-Создаёт `dist\Электронная-Фармация.zip` с Release-сборкой.
-
-## Основной цикл (V2)
-
-1. Старт → проверка БД → выбор грузополучателя → прайс-лист
-2. **Загрузить** — прайс с API в SQLite, обновление открытых вкладок прайса
-3. Фильтр поставщика, поиск, набор в корзину с проверкой остатка
-4. **Сохранить заказ** — мин. сумма по поставщику, уникальный номер, грузополучатель в `Orders`
-5. **Отправить** — POST `/api/buyer/orders`, статус `ОТПРАВЛЕН` только у отправленного заказа
-6. **Заказы** — фильтры по статусу, поставщику, грузополучателю; отправка одного заказа
-7. **Отказы / Накладные** — синхронизация с `/api/buyer/invoices`
-
-## Приёмочный чек-лист
-
-1. Старт → БД → выбор точки → прайс
-2. Загрузить → прайс обновился, корзина очищена с предупреждением
-3. Фильтр поставщика + поиск + количество + проверка остатка
-4. Сохранить заказ (мин. сумма, корректный номер, грузополучатель)
-5. Отправить → только выбранные заказы → статус / ошибка API
-6. Журнал: фильтры, комментарий, удаление `НОВЫЙ`
-7. Отказы: загрузка с API + детали позиций
-8. Авторизация + `location_id` в настройках
-9. Единый UI: `UiDialogs` / toast, без debug-панелей загрузки
+# ЭльФиСА — Электронная Фармация
+
+
+
+WinForms-клиент «Электронная Фармация» с современным UI на базе библиотеки **Elfisa.UI** (тема **PharmaTrust**).
+
+
+
+## Структура
+
+
+
+```
+
+src/
+
+ Elfisa.UI/ — UI-библиотека (ThemeManager, Modern* контролы)
+
+ ElectronicPharmacy/ — основное приложение (SQLite, API, бизнес-логика)
+
+```
+
+
+
+## Сборка
+
+
+
+Требуется .NET SDK 9+ (или MSBuild 16+) и .NET Framework 4.7.2.
+
+
+
+```powershell
+
+dotnet build Elfisa.sln -c Release
+
+```
+
+
+
+Запуск:
+
+
+
+```powershell
+
+.\src\ElectronicPharmacy\bin\Release\Электронная` Фармация.exe
+
+```
+
+
+
+База `efClient.db` копируется в output (`Data\efClient.db` или рядом с exe). Путь задаётся через `AppConfig.SqliteDbPath`.
+
+
+
+## Настройки
+
+
+
+В диалоге **Авторизация** (и в `Settings`):
+
+
+
+- **URL API** — базовый адрес сервера (по умолчанию из `AppConfig`)
+
+- **Location ID** — UUID точки для отправки заказов
+
+- **Region ID** — опциональный фильтр при загрузке прайса
+
+
+
+Переменная окружения `ELF_API_BASE_URL` переопределяет URL API.
+
+Подробное описание всех HTTP-запросов — в [docs/API.md](docs/API.md).
+
+
+
+## Архив для заказчика
+
+
+
+```powershell
+
+.\dist\build-release.ps1
+
+```
+
+
+
+Создаёт `dist\Электронная-Фармация.zip` с Release-сборкой.
+
+
+
+## Основной цикл (V2)
+
+
+
+1. Старт → проверка БД → выбор грузополучателя → прайс-лист
+
+2. **Загрузить** — прайс с API в SQLite, обновление открытых вкладок прайса
+
+3. Фильтр поставщика, поиск, набор в корзину с проверкой остатка
+
+4. **Сохранить заказ** — мин. сумма по поставщику, уникальный номер, грузополучатель в `Orders`
+
+5. **Отправить** — POST `/api/buyer/orders`, статус `ОТПРАВЛЕН` только у отправленного заказа
+
+6. **Заказы** — фильтры по статусу, поставщику, грузополучателю; отправка одного заказа
+
+7. **Отказы / Накладные** — синхронизация с `/api/buyer/invoices`
+
+
+
+## Приёмочный чек-лист
+
+
+
+1. Старт → БД → выбор точки → прайс
+
+2. Загрузить → прайс обновился, корзина очищена с предупреждением
+
+3. Фильтр поставщика + поиск + количество + проверка остатка
+
+4. Сохранить заказ (мин. сумма, корректный номер, грузополучатель)
+
+5. Отправить → только выбранные заказы → статус / ошибка API
+
+6. Журнал: фильтры, комментарий, удаление `НОВЫЙ`
+
+7. Отказы: загрузка с API + детали позиций
+
+8. Авторизация + `location_id` в настройках
+
+9. Единый UI: `UiDialogs` / toast, без debug-панелей загрузки
+
diff --git a/docs/API.md b/docs/API.md
new file mode 100644
index 0000000..ba27529
--- /dev/null
+++ b/docs/API.md
@@ -0,0 +1,290 @@
+# 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}"
+```
+
diff --git a/src/ElectronicPharmacy/Classes/ApiClient.cs b/src/ElectronicPharmacy/Classes/ApiClient.cs
index a12e5fe..f121e08 100644
--- a/src/ElectronicPharmacy/Classes/ApiClient.cs
+++ b/src/ElectronicPharmacy/Classes/ApiClient.cs
@@ -83,55 +83,6 @@ namespace Электронная_Фармация.Classes
///
public bool IsAuthenticated => !string.IsNullOrEmpty(_token);
-
- ///
- /// Получает сводный прайс поставщиков
- ///
- /// ID поставщика (опционально)
- /// ID региона (опционально)
- /// Сводный прайс
- ///
-
- public async Task GetPriceItem()
- {
- if (!IsAuthenticated)
- {
- throw new InvalidOperationException("Необходимо выполнить авторизацию перед запросом данных");
- }
-
- //var queryParams = new List();
- var url = "/api/supplier-prices/556EE09D-AD06-439E-91AF-D6CEBCD61AD3";
-
- _httpClient.DefaultRequestHeaders.Authorization =
- new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", _token);
-
- try
- {
- var response = await _httpClient.GetAsync(url);
-
- if (response.IsSuccessStatusCode)
- {
- var summary = await response.Content.ReadFromJsonAsync();
- return summary;
- }
- else if (response.StatusCode == System.Net.HttpStatusCode.Unauthorized)
- {
- _token = null; // Токен истек или невалиден
- var error = await response.Content.ReadFromJsonAsync();
- throw new UnauthorizedAccessException($"Сессия истекла: {error?.Error ?? "Необходимо войти заново"}");
- }
- else
- {
- var error = await response.Content.ReadFromJsonAsync();
- throw new HttpRequestException($"Ошибка HTTP {response.StatusCode}: {error?.Error ?? "Неизвестная ошибка"}");
- }
- }
- catch (TaskCanceledException)
- {
- throw new HttpRequestException("Превышено время ожидания ответа сервера (таймаут 30 секунд)");
- }
- }
-
public async Task GetPriceSummaryAsync(string supplierId = null, string regionId = null)
{
if (!IsAuthenticated)
@@ -139,7 +90,6 @@ namespace Электронная_Фармация.Classes
throw new InvalidOperationException("Необходимо выполнить авторизацию перед запросом данных");
}
- // Формируем URL с параметрами
var queryParams = new List();
if (!string.IsNullOrEmpty(supplierId))
{
@@ -149,20 +99,6 @@ namespace Электронная_Фармация.Classes
{
queryParams.Add($"region_id={Uri.EscapeDataString(regionId)}");
}
- //if (!string.IsNullOrEmpty(supplierPriceId))
- //{
- // queryParams.Add($"supplier_price_id={Uri.EscapeDataString(supplierPriceId)}");
- //}
-
- //var queryParams = new List();
- //if (!string.IsNullOrEmpty(supplierId))
- //{
- // queryParams.Add($"supplier_id={Uri.EscapeDataString(supplierId)}");
- //}
- //if (!string.IsNullOrEmpty(regionId))
- //{
- // queryParams.Add($"region_id={Uri.EscapeDataString(regionId)}");
- //}
var url = "/api/supplier-prices/summary";
if (queryParams.Count > 0)
diff --git a/src/ElectronicPharmacy/HelpForms/HF_DownloadDataFromServer.cs b/src/ElectronicPharmacy/HelpForms/HF_DownloadDataFromServer.cs
index 169f215..062bf0b 100644
--- a/src/ElectronicPharmacy/HelpForms/HF_DownloadDataFromServer.cs
+++ b/src/ElectronicPharmacy/HelpForms/HF_DownloadDataFromServer.cs
@@ -60,12 +60,6 @@ namespace Электронная_Фармация.HelpForms
regionId: string.IsNullOrWhiteSpace(Settings.Default.RegionId) ? null : Settings.Default.RegionId);
rtxtDebug.Text += ($"✓ Получено {allSuppliersSummary.Summary.Count} позиций\n");
-
- //загружаю один товар:
- //rtxtDebug.Text += ("\nЗагружаю сводный прайс для всех поставщиков...");
- // var PriceItem = await client.GetPriceItem();
- //rtxtDebug.Text += ($"✓ Получено {allSuppliersSummary.DrugName.Count} позиций\n");
-
DataTable tablePrice = new DataTable();
tablePrice.Columns.Add("supplier_price_id");
@@ -134,45 +128,6 @@ namespace Электронная_Фармация.HelpForms
//tablePrice.Columns.Add("RegistryStatus");
#endregion
- // Выводим первые 5 позиций
- //Console.WriteLine("Первые 500 позиций:");
-
- #region загрузка одного элемента
- //if (PriceItem.DrugName != null | PriceItem.DrugName != "" | PriceItem.DrugName != string.Empty)
- //{
-
- // Console.WriteLine(PriceItem.ToString());
-
- // DataRow row = tablePrice.NewRow();
-
- // row["supplier_price_id"] = PriceItem.SupplierPriceID.ToString();
- // row["guid_es"] = PriceItem.GuidEs.ToString();
- // row["es_code"] = PriceItem.EsCode.ToString();
- // row["supplier_id"] = PriceItem.SupplierId.ToString();
- // row["DrugName"] = PriceItem.DrugName.ToString();
- // row["SupplierName"] = "Катрен";//PriceItem.SupplierName.ToString();
- // row["Price"] = PriceItem.Price.ToString();
- // row["Quantity"] = PriceItem.Quantity.ToString();
-
- // if (PriceItem.ExpiryPeriod == null)
- // { row["ExpiryPeriod"] = DBNull.Value; }
- // else { row["ExpiryPeriod"] = PriceItem.ExpiryPeriod.ToString(); }
- // ;
-
- // if (PriceItem.Description == null)
- // { row["Description"] = DBNull.Value; }
- // else { row["Description"] = PriceItem.Description.ToString(); }
- // ;
-
- // row["Zakaz"] = string.Empty;
- // row["SummaZakaza"] = string.Empty;
-
- // tablePrice.Rows.Add(row);
- //}
- #endregion
-
-
- #region загрузка всего прайса РАБОЧАЯ
if (allSuppliersSummary.Summary != null)
{
@@ -256,7 +211,6 @@ namespace Электронная_Фармация.HelpForms
#endregion
}
}
- #endregion
string commandToDelete = "delete from [PriceList]";
string commandTODeleteTempOrder = "delete from [TempOrderItems]";