Sprint 11 — каркас для интеграции с операторами фискальных данных РК.
Реальные ApiKey'и появятся у user'а позже; задача — построить такой
фрейм, чтобы подключение оператора сводилось к вписыванию кредов в UI
без правок кода/деплоя.
Что сделано:
- IFiscalProvider (Application/Common/Fiscal) + FiscalResult,
FiscalProviderKind (None/Mock/Webkassa/Kassa24/OfdSolo),
IFiscalProviderFactory, FiscalNotConfiguredException.
- 4 реализации в Infrastructure/Fiscal:
• MockFiscalProvider — фейк MOCK-<8hex> через 300мс, идемпотентный
по Sale.Id (используется dev/stage и интеграционными тестами);
• WebkassaProvider — полный HTTP-pipeline Authorize→Check, парсинг
JSON-ответа, NDS-в-ставке, retry-safe через ExternalCheckNumber;
• Kassa24Provider / OfdSoloProvider — скелет с тем же контрактом,
RegisterAsync бросает FiscalNotConfiguredException (нужны
спецификации API от user'а, NDA-only).
- Миграция Phase11a: 5 колонок в retail_sales (FiscalNumber, QrCode,
Url, ProviderTxId, ProviderKind) + 5 в organizations (FiscalProvider
NOT NULL default 0, ApiKey/Secret encrypted, CashboxUniqueNumber,
ApiBaseUrl). Default 0 = обратная совместимость, существующие чеки
и продажи без фискализации работают как раньше.
- RetailSalesController.Post — TryFiscalizeAsync после commit'а
stock-транзакции. Best-effort: сетевые/HTTP-ошибки логируются, чек
остаётся проведённым. Идемпотентность по IsNullOrEmpty(FiscalNumber).
- OrgFiscalSettingsController: GET/PUT настройки + GET /providers
(опции для select'а) + POST /test-send (фейк-чек к выбранному
провайдеру, не сохраняет в БД).
- UI: FiscalSection в OrganizationSettingsPage с password-input'ами
для ApiKey/Secret (шифруются DataProtection.purpose=foodmarket.fiscal,
в GET — только has-* флаги), спец-значение "__clear__" для снятия,
кнопка «Тестовая отправка».
- Тесты: 11 unit (Mock 5 + Webkassa payload 6) + 3 integration
(Mock сохраняет FiscalNumber, test-send даёт MOCK-номер, None
не фискализует).
- docs/ofd-integration.md — гид с архитектурой, шагами подключения
Webkassa (полный pap), TODO для Касса24/ОФД-Соло, безопасностью
кредов, retry-сценариями.
Все 68 unit + 8 integration в Fiscal/Loyalty/RetailOversell — зелёные.
Web vite build — зелёный.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
99 lines
7.1 KiB
Markdown
99 lines
7.1 KiB
Markdown
# Sprint 11 — ОФД-scaffolding (фискализация РК)
|
||
|
||
Цель: построить фрейм для интеграции с операторами фискальных данных
|
||
Казахстана (Webkassa / Касса24 / ОФД-Соло), чтобы как только пользователь
|
||
получит реальный ApiKey — провайдер «оживал» одной настройкой в UI,
|
||
без правок кода/деплоя. Реальные аккаунты у user'а пока нет; задача
|
||
этого спринта — каркас + Mock + один полностью описанный провайдер
|
||
(Webkassa) с тестами на HTTP-контракт.
|
||
|
||
Старт: 2026-06-07. Исполнитель: Claude Opus 4.7 (автономный режим).
|
||
|
||
## Принципы
|
||
|
||
- Multi-tenant обязателен: настройка ОФД хранится на уровне
|
||
Organization (как SMTP — но per-tenant, не глобально). API-ключи
|
||
шифруются через DataProtection (purpose=`foodmarket.fiscal`).
|
||
- Поведение «по умолчанию» (`Fiscal:Provider=None` или не задано) —
|
||
ровно как до спринта: RetailSale.Post не зовёт никакого провайдера,
|
||
FiscalNumber остаётся пустым. Это даёт обратную совместимость.
|
||
- Каждый пункт: build + локальные тесты + `~/deploy-stage.sh` + retest.
|
||
- НЕ трогать: `global.json`, прод-стек, POS WPF.
|
||
|
||
## Чек-лист
|
||
|
||
- [x] **1. IFiscalProvider абстракция** — `Application/Common/Fiscal/IFiscalProvider.cs`
|
||
с `FiscalResult`, `FiscalProviderKind` (None/Mock/Webkassa/Kassa24/OfdSolo),
|
||
`IFiscalProviderFactory`, `FiscalNotConfiguredException`,
|
||
`FiscalProviderException`. Миграция `Phase11a_FiscalScaffolding`
|
||
добавляет 5 колонок в `retail_sales` (FiscalNumber, FiscalQrCode,
|
||
FiscalUrl, FiscalProviderTxId, FiscalProviderKind) и 5 в `organizations`
|
||
(FiscalProvider, FiscalApiKeyEncrypted, FiscalApiSecretEncrypted,
|
||
FiscalCashboxUniqueNumber, FiscalApiBaseUrl). `FiscalProvider` NOT NULL
|
||
с default 0 (None) — обратная совместимость. `RetailSalesController.Post`
|
||
получил `TryFiscalizeAsync` (best-effort после commit'а stock-tx,
|
||
идемпотентность по `IsNullOrEmpty(FiscalNumber)`).
|
||
- [x] **2. MockFiscalProvider** — `Infrastructure/Fiscal/MockFiscalProvider.cs`,
|
||
имитация 300мс задержки, детерминированный фейк `MOCK-<8hex>` от
|
||
`Sale.Id`. 5 unit-тестов (контракт + идемпотентность + latency) +
|
||
integration-тест `FiscalMockFlowTests` (3 сценария: Mock даёт FiscalNumber,
|
||
test-send отвечает OK, None не фискализует).
|
||
- [x] **3. WebkassaProvider skeleton** — `Infrastructure/Fiscal/WebkassaProvider.cs`,
|
||
полный HTTP-flow `Authorize → Check`, парсинг JSON-ответа Webkassa.
|
||
Token берётся каждым вызовом (TTL-кеш — следующий спринт).
|
||
`BuildCheckPayload` public для тестируемости. 6 unit-тестов на маппинг
|
||
(positions, payments, ndsв-ставке, returns, mixed/cash fallback,
|
||
JSON camelCase). Без реального ApiKey/CashboxNumber бросает
|
||
`FiscalNotConfiguredException` с подсказкой «заполните в настройках».
|
||
- [x] **4. Kassa24Provider skeleton** — заготовка с тем же контрактом,
|
||
RegisterAsync бросает `FiscalNotConfiguredException` («интеграция ещё
|
||
не реализована, нужны спецификации API»). Подробности — в docs.
|
||
- [x] **5. OfdSoloProvider skeleton** — аналогично.
|
||
- [x] **6. UI: настройка ОФД-провайдера** — секция `FiscalSection` в
|
||
`OrganizationSettingsPage.tsx` + backend `OrgFiscalSettingsController`
|
||
(`GET/PUT /api/organization/fiscal`, `GET /providers` со списком
|
||
опций, `POST /test-send`). Поля ApiKey/ApiSecret — password-input,
|
||
шифруются на сервере; в GET возвращаются только has-* флаги.
|
||
Спец-значение `"__clear__"` — снять креды. Кнопка «Тестовая отправка»
|
||
вызывает провайдера на фейк-чеке (не сохраняет в БД), показывает
|
||
FiscalNumber или сообщение об ошибке.
|
||
- [x] **7. docs/ofd-integration.md** — гид «как подключить оператора»
|
||
(Webkassa — полный pap, Касса24/ОФД-Соло — TODO для будущих спринтов,
|
||
безопасность кредов, поведение на retry/network failure).
|
||
|
||
## Журнал
|
||
|
||
### 2026-06-07 старт
|
||
Sprint 10 закрыт (4/4 ✓). Поехали по ОФД-чек-листу.
|
||
|
||
### 2026-06-07 п.1–п.5 (абстракция + 4 провайдера)
|
||
`IFiscalProvider` + `FiscalProviderFactory` + 4 реализации (Mock полная,
|
||
3 оператора скелет с осмысленным `FiscalNotConfiguredException`).
|
||
Миграция Phase11a добавила 10 колонок в `retail_sales` + `organizations`.
|
||
`RetailSalesController.Post` — `TryFiscalizeAsync` после commit'а.
|
||
Тесты: 11 unit (Mock + Webkassa payload) + 3 integration. Все зелёные.
|
||
|
||
### 2026-06-07 п.6 (UI)
|
||
`OrgFiscalSettingsController` (5 endpoints) + `FiscalSection` в
|
||
существующей странице OrganizationSettings. UI прячет поля кредов
|
||
для провайдеров None/Mock, показывает их для трёх реальных операторов.
|
||
Тестовая отправка работает с любым провайдером — для скелет-операторов
|
||
вернёт «не реализовано», для Mock — настоящий MOCK-номер.
|
||
|
||
### 2026-06-07 п.7 (docs)
|
||
`docs/ofd-integration.md` — архитектура, поведение по умолчанию, шаги
|
||
подключения каждого оператора, безопасность, retry-сценарии.
|
||
|
||
### Итог
|
||
Все 7 пунктов ✓. Suite-тесты:
|
||
- 68/68 unit (включая 11 новых для Fiscal).
|
||
- 8/8 integration (Fiscal + Loyalty + RetailOversell в одной группе).
|
||
- Web `vite build` зелёный, TS — без ошибок.
|
||
|
||
API готов: пользователь заводит аккаунт у любого оператора, вписывает
|
||
ApiKey/Secret/CashboxNumber в «Настройки организации → ОФД», нажимает
|
||
«Тестовая отправка» — если оператор отвечает, следующий проведённый
|
||
чек получит фискальный номер автоматически. Для Webkassa полный
|
||
HTTP-pipeline реализован; для Касса24/ОФД-Соло нужны спецификации API
|
||
от user'а (NDA-only).
|