food-market/docs/sprint11-progress.md
nns 0d3ef81f72 feat(s11): ОФД-scaffolding — IFiscalProvider + 4 провайдера + UI/тесты
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>
2026-06-07 02:27:17 +05:00

7.1 KiB
Raw Permalink Blame History

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.

Чек-лист

  • 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)).
  • 2. MockFiscalProviderInfrastructure/Fiscal/MockFiscalProvider.cs, имитация 300мс задержки, детерминированный фейк MOCK-<8hex> от Sale.Id. 5 unit-тестов (контракт + идемпотентность + latency) + integration-тест FiscalMockFlowTests (3 сценария: Mock даёт FiscalNumber, test-send отвечает OK, None не фискализует).
  • 3. WebkassaProvider skeletonInfrastructure/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 с подсказкой «заполните в настройках».
  • 4. Kassa24Provider skeleton — заготовка с тем же контрактом, RegisterAsync бросает FiscalNotConfiguredException («интеграция ещё не реализована, нужны спецификации API»). Подробности — в docs.
  • 5. OfdSoloProvider skeleton — аналогично.
  • 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 или сообщение об ошибке.
  • 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.PostTryFiscalizeAsync после 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).