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>
|
||
|---|---|---|
| .claude | ||
| .forgejo/workflows | ||
| .github/workflows.disabled | ||
| assets/brand | ||
| deploy | ||
| docs | ||
| src | ||
| tests | ||
| .editorconfig | ||
| .gitattributes | ||
| .gitignore | ||
| .nvmrc | ||
| CLAUDE.md | ||
| Directory.Build.props | ||
| Directory.Packages.props | ||
| food-market.sln | ||
| global.json | ||
| README.md | ||
food-market
Аналог системы МойСклад для розничной торговли в Казахстане.
Состав системы
- Сервер (ASP.NET Core 8 + PostgreSQL) — мультитенантный API, web-админка на React
- Web-админка (React 18 + Vite + shadcn/ui) — управление магазином, справочниками, документами, отчётами
- Кассовая программа (WPF на .NET 8) — офлайн-работоспособная касса для Windows 10+, синхронизируется с сервером, работает с весами (Масса-К в первую очередь)
Структура репозитория
food-market/
├── src/
│ ├── food-market.domain/ # доменные сущности, enum'ы, события
│ ├── food-market.application/ # use cases (MediatR), DTO, интерфейсы
│ ├── food-market.infrastructure/ # EF Core, PostgreSQL, внешние сервисы
│ ├── food-market.api/ # ASP.NET Core + OpenIddict + SignalR
│ ├── food-market.web/ # React + Vite + shadcn/ui (SPA)
│ ├── food-market.shared/ # DTO-контракты сервер ↔ POS
│ ├── food-market.pos.core/ # логика POS (независима от UI)
│ └── food-market.pos/ # WPF + .NET 8 кассовая программа
├── tests/
├── deploy/
│ ├── docker-compose.yml # PostgreSQL для локальной разработки
│ └── Dockerfile.api
└── docs/
Именование
- Папки, проекты, csproj, docker-образы, URL — lowercase:
food-market,food-market.api - C# namespace —
foodmarket.Api,foodmarket.Domain(lowercase root; C# не допускает дефис в идентификаторах) - Отображаемое имя в UI — "Food Market"
Стек
Сервер
- .NET 8 LTS (до ноября 2026), ASP.NET Core Minimal APIs + Controllers
- EF Core 8 + Npgsql + PostgreSQL 16
- OpenIddict 5 (OAuth2/OIDC — password + refresh tokens)
- MediatR + FluentValidation (CQRS-lite)
- SignalR (real-time синхронизация)
- Hangfire (фоновые задачи)
- Serilog (структурированное логирование)
Web
- React 18 + Vite + TypeScript
- shadcn/ui + Tailwind CSS
- TanStack Query + TanStack Table
- AG Grid Community (для тяжёлых grid'ов)
- Recharts / Tremor (графики)
- react-hook-form + Zod
POS
- .NET 8 WPF, Windows 10+
- CommunityToolkit.Mvvm (source-generated MVVM)
- SQLite (локальная БД)
- Refit + Polly (API-клиент с retry)
- System.IO.Ports (драйверы весов: Масса-К и др.)
Мультитенантность
Каждая сущность имеет OrganizationId. Пользователь scoped к организации. EF Core query filter автоматически изолирует данные между тенантами.
Локальная разработка
# Поднять PostgreSQL
cd deploy && docker compose up -d
# Мигрировать БД
cd src/food-market.api && dotnet ef database update
# Запустить API
dotnet run --project src/food-market.api
# Запустить Web
cd src/food-market.web && pnpm install && pnpm dev
Статус
🚧 Phase 0: фундамент (scaffolding, auth, multi-tenancy)