food-market/docs/ARCHITECTURE.md
nns aa83f82dc5
Some checks are pending
Auto-tag / Create date-tag (push) Waiting to run
CI / Backend (.NET 8) (push) Waiting to run
CI / Web (React + Vite) (push) Waiting to run
CI / POS (WPF, Windows) (push) Waiting to run
Docker API / Build + push API (push) Waiting to run
Docker API / Deploy API on stage (push) Blocked by required conditions
feat(s22): data tooling — export/import + schema docs + anon dump (7 пунктов)
1. GDPR org export — domain OrgExport + Phase22a миграция, OrgExportJob
   собирает ZIP с JSON по каждой сущности через IObjectStorage,
   DownloadToken 64-hex + 24h TTL + email-notify.
   POST /api/org/export, GET /api/org/export[/{id}], GET download/{token}.

2. 1C CSV import — POST /api/catalog/products/import/1c-csv:
   Windows-1251/UTF-8 BOM auto-detect, разделитель ;/, русские заголовки
   (Артикул/Наименование/Единица/Цена/Группа/Штрихкод) или английские.
   Нормализация unit-кодов (шт/кг/г/л/мл/упак). Делегирует на ImportCsv
   (транзакция, multi-tenant). docs/imports.md.

3. deploy/anonymize-prod.sh — pg_dump прода → restore во временную БД →
   UPDATE PII (email→user{N}@example.kz, phone→+7700111{N:04}, password→
   тестовый hash, BIN/IIN синтетические, MoySkladToken=NULL, аудиты
   TRUNCATE) → pg_dump → gz файл.

4. DbSchemaDocsJob (weekly вс 05:00 UTC) — information_schema → md с
   таблицами + колонками + FK + mermaid ER-диаграммой (топ-20 таблиц).
   Сохраняет в content-root db-schema-generated.md.

5. POST /api/admin/audit-log/export?format=csv|jsonl — streaming через
   AsAsyncEnumerable. UTF-8 BOM для CSV, JSONL для grep'a. Multi-tenant.

6. GET /api/moysklad/sync-status — агрегат по import_jobs:
   { configured, lastSuccessAt, errorCountLast7Days, pendingCount,
     byKind: { products: KindStatus, counterparties: KindStatus } }.
   Stub если MoySkladToken=null.

7. docs/ARCHITECTURE.md — финальный итог 22 спринтов:
   - Sprint 13-22 changes-сводка
   - «Реализовано полностью» секция
   - «Scaffolding» таблица с указанием что нужно от user'а
   - «Не реализовано» секция (прод, SSO callback, KZ-перевод, POS-тест)
   - Актуальная файловая структура

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-07 23:00:54 +05:00

37 KiB
Raw Permalink Blame History

food-market — архитектура

Документ для разработчика, который пришёл в проект первый раз. Описывает слои, модули, ключевые потоки и почему некоторые вещи сделаны именно так.

Старая короткая версия — docs/architecture.md (lowercase). Этот файл заменяет её и расширяет.

TL;DR

  • Что: multi-tenant SaaS-аналог МойСклад для розничных магазинов РК.
  • Backend: .NET 8 LTS, ASP.NET Core, EF Core 8, PostgreSQL 14+ (dev) / 16 (prod).
  • Auth: OpenIddict 5 (password + refresh) поверх ASP.NET Identity.
  • Web: React 19 + Vite + TS, Tailwind v4, shadcn/ui, TanStack Query, AG Grid.
  • POS: WPF на .NET 8 Windows, оффлайн-буфер в SQLite, синк через /api/pos/v1.

Топология deployment

┌─────────────────────────────────────────────────────────────────┐
│                  Internet / LAN магазина                         │
└───────────┬───────────────────────┬─────────────────────────────┘
            │ HTTPS                  │ HTTPS (Bearer) + офлайн-буфер
            ▼                        ▼
   ┌────────────────────┐    ┌──────────────────────────┐
   │  food-market.web   │    │  food-market.pos (WPF)    │
   │   React SPA        │    │   .NET 8, Windows 10+     │
   │   admin.fm.kz      │    │   локальная SQLite        │
   └─────────┬──────────┘    └──────────┬───────────────┘
             │                          │
             │ /api/*                   │ /api/pos/v1/*
             │ /hubs/notifications      │
             └─────────────┬────────────┘
                           ▼
          ┌───────────────────────────────────────────┐
          │         food-market.api                    │
          │   ASP.NET Core + OpenIddict + SignalR      │
          │  - tenant query filters per request        │
          │  - Hangfire scheduler + recurring jobs     │
          │  - /metrics (Prometheus) /health/{live,ready}│
          └────┬──────────┬───────────┬───────────┬───┘
               ▼          ▼           ▼           ▼
         ┌────────┐ ┌─────────┐ ┌──────────┐ ┌──────────┐
         │Postgres│ │ Hangfire│ │ MinIO    │ │ Logs     │
         │  16    │ │  (jobs) │ │ (S3, opt)│ │ Serilog  │
         └────────┘ └─────────┘ └──────────┘ └──────────┘
                                   │
                                   ▼
                        локальный FS (/uploads volume)
                          — если MinIO не настроен

Stage и prod крутятся через deploy/docker-compose.yml на dev-vm (192.168.1.190). Локальный dev: API на :5081, Postgres из brew (postgres@14 на :5432), web через pnpm dev на :5173.

Структура солюшна

food-market/
├── src/
│   ├── food-market.domain/          ← POCO, enum, доменные интерфейсы
│   ├── food-market.application/     ← MediatR-handlers, DTO, абстракции
│   ├── food-market.infrastructure/  ← EF Core, Identity, OpenIddict EF, внешние API
│   ├── food-market.api/             ← ASP.NET Core host: controllers, middleware, DI
│   ├── food-market.web/             ← React SPA
│   ├── food-market.shared/          ← DTO-контракты api ↔ pos
│   ├── food-market.public/          ← Astro static (маркетинг food-market.kz)
│   ├── food-market.pos.core/        ← логика POS (без UI)
│   └── food-market.pos/             ← WPF UI (net8.0-windows)
├── tests/
│   ├── food-market.UnitTests/       ← xUnit + InMemoryDB
│   ├── food-market.IntegrationTests/← xUnit + Testcontainers Postgres
│   ├── e2e/                         ← Playwright (TS), бьёт по test.admin.food-market.kz
│   └── load/                        ← k6 (Sprint 12)
├── deploy/                          ← docker-compose, Dockerfile.*, systemd-юниты
└── docs/                            ← вы здесь

Слои (Clean Architecture)

Слой Зависит от Что лежит
domain ничего POCO-сущности, enum'ы, доменные интерфейсы (ITenantEntity, IVersionedEntity).
application domain + shared MediatR IRequest/IRequestHandler, DTO, абстракции (IFiscalProvider, IEmailSender, IStockService, ITenantContext), FluentValidation валидаторы.
infrastructure application + domain AppDbContext, Identity-таблицы, OpenIddict EF store, реализации абстракций, HTTP-клиенты к внешним API (Webkassa, MoySklad, MailKit, Telegram).
api всё перечисленное выше ASP.NET Core host: контроллеры, middleware, DI-проводка, фоновые джобы (Hangfire), Realtime hub'ы (SignalR), сидеры.

Правило одностороннего направления зависимостей: домен не знает про EF и ASP.NET, application — про конкретные провайдеры. Это позволило прикрутить ОФД (Sprint 11) одним интерфейсом + четырьмя реализациями, без правок контроллеров кроме одной точки вызова.

Модули backend

Domain (src/food-market.domain/)

  • Common/Entity.cs — базовая Entity с Id/CreatedAt/UpdatedAt.
  • Common/TenantEntity.csITenantEntity (обязательный OrganizationId), TenantEntity (база), IOptionalTenantEntity (системные справочники с OrganizationId?).
  • Common/IVersionedEntity.cs — оптимистичная блокировка через PG xmin (Xmin поле).
  • Бизнес-сущности по поддоменам: Catalog/ (Product, Counterparty, ProductGroup, …), Inventory/ (Stock, StockMovement, Loss, Transfer, Inventory), Purchases/ (Supply, Enter, SupplierReturn), Sales/ (RetailSale, RetailSaleLine, Demand, LoyaltyCard, LoyaltyProgram, Promotion), Organizations/ (Organization, Employee, EmployeeRole, OrgAuditLog, SuperAdminAuditLog), Platform/ (PlatformSettings — singleton SMTP-конфиг).

Application (src/food-market.application/)

  • CQRS на MediatR — пока partial: образцы в Purchases/Commands/CreateSupplyCommand.cs, Sales/Commands/PostRetailSaleCommand.cs, Sales/Queries/GetSalesReportQuery.cs. Большинство контроллеров пока «толстые» (исторически до TD-1).
  • Абстракции:
    • Common/Tenancy/ITenantContextOrganizationId, IsSuperAdmin, IsTenantOverride, UserId.
    • Common/Email/IEmailSender — отправка через текущий SMTP-конфиг.
    • Common/Fiscal/IFiscalProvider + IFiscalProviderFactory (Sprint 11).
    • Inventory/IStockService — единая точка списания/начисления остатка (любая операция, меняющая склад, идёт через ApplyMovementAsync).
  • FluentValidation валидаторы рядом с DTO; глобально подключаются через AddValidatorsFromAssemblyContaining<Program>().

Infrastructure (src/food-market.infrastructure/)

  • Persistence/AppDbContext.cs — единый DbContext (тенанта + Identity + OpenIddict EF store). Query-filter применяется через reflection ко всем ITenantEntity (см. MULTI-TENANCY.md).
  • Persistence/Configurations/*.cs — EF Core fluent configs по поддоменам.
  • Persistence/Migrations/ — миграции пишутся вручную (см. CLAUDE.md / memory feedback_ef_migrations), снапшот не синхронизируется с моделью (используется только dotnet ef migrations add, который не вызывается в этом проекте).
  • Persistence/OrgAuditInterceptor.cs — EF ISaveChangesInterceptor, пишет каждую Add/Update/Delete в org_audit_log (JSONB diff).
  • Identity/ — кастомные User, Role для ASP.NET Identity.
  • Email/MailKitEmailSender.cs — SMTP через MailKit, конфиг из PlatformSettings (читается на каждой отправке через scope).
  • Fiscal/IFiscalProvider реализации: Mock + Webkassa (полный) + Kassa24/OfdSolo (skeleton). См. ofd-integration.md.
  • Inventory/StockService.cs — единственное место, где двигаются остатки. Бизнес-инвариант: stock = SUM(stock_movements) per (productId, storeId).
  • Integrations/MoySklad/ — HTTP-клиент + конвертер для импорта каталога.

Api (src/food-market.api/)

  • Program.cs — composition root (~570 строк, поделён логическими блоками; см. секцию «Composition root» ниже).
  • Controllers/ — REST-API. Структура совпадает с маршрутами:
    • Auth//api/auth/* (signup, forgot-password, 2FA).
    • Catalog//api/catalog/{products,counterparties,…}.
    • Purchases//api/purchases/{supplies,supplier-returns}.
    • Sales//api/sales/{retail,demands}.
    • Inventory//api/inventory/{stock,enters,losses,transfers,inventories}.
    • Reports//api/reports/{sales,stock,profit,abc}.
    • Dashboard//api/dashboard/{top-products,low-stock,recent-sales,margin}.
    • Loyalty/, Promotions/ — Sprint 9.
    • Organizations/ — настройки орги, сотрудники, роли, ОФД.
    • Pos//api/pos/v1/* для WPF POS (sync, idempotency).
    • SuperAdmin//api/super-admin/* (управление платформой).
    • Admin//api/admin/* (per-org admin tools: cleanup, demo-seed, moysklad-import, audit-log просмотр).
    • Search/ — глобальный /api/search/global (Cmd+K).
    • Telegram/ — bind owner-chat, статус.
    • Uploads/ — multipart upload изображений.
  • Infrastructure/:
    • Tenancy/HttpContextTenantContext.cs — реализация ITenantContext через IHttpContextAccessor + AsyncLocal-override для background tasks.
    • Tenancy/SuperAdminOverrideClaimsTransformer.cs — добавляет Admin/Cashier/Storekeeper роли SuperAdmin'у с активным X-Org-Override, чтобы [Authorize(Roles="Admin")] не отшил его.
    • Tenancy/ReadonlyOverrideMiddleware.cs — в режиме override без X-Org-Override-Reason блочит любую мутацию (читать всё, писать ничего; писать — только в edit-mode с reason).
    • Tenancy/SuperAdminEditAuditFilter.cs — глобальный action-filter, при mutate-в-override пишет в super_admin_audit_log.
    • Authorization/RequiresPermissionAttribute.cs + PermissionAuthorizationPolicyProvider
      • PermissionAuthorizationHandler — permission-based авторизация. [RequiresPermission("ProductsEdit")] → policy perm:ProductsEditRolePermissions.ProductsEdit булева на EmployeeRole.
    • Validation/ValidationFilter.cs — FluentValidation → 400 ProblemDetails (RFC 7807).
    • RateLimiting/AuthRateLimiterExtensions.cs — 5/мин + 20/час на /connect/token, /api/auth/signup по IP+username.
    • Observability/LogEnrichmentMiddleware.cs — кладёт CorrelationId/OrgId/UserId в Serilog LogContext, каждая запись в журнале получает эти лейблы.
    • Observability/DbMetricsInterceptor.cs — EF интерсептор, Prometheus food_market_db_query_duration_seconds.
    • Observability/AppMetrics.cs — статические Counter'ы (Posted/Unposted per docType, FiscalRegistered, …).
    • Health/DatabaseReadyHealthCheck.csSELECT 1 + проверка __EFMigrationsHistory.
    • Security/OpenIddictKeyConfigurator.cs — в dev — persistent RSA в App_Data/oidc-keys/*; в stage/prod — X509 PFX из конфига (см. openiddict-keys.md).
  • Realtime/NotificationsHub.cs + NotificationsPublisher.cs — SignalR-хаб /hubs/notifications, группы per-org. События: SalePosted, LowStock, ImportProgress.
  • Background/:
    • HangfireJobsConfigurator — регистрирует recurring jobs при старте: prune-stock-movements (03:30), prune-audit-log (03:45), weekly-summary (пн 07:00), low-stock-alert (08:00), telegram-owner-daily-summary (06:00).
    • HousekeepingJobs — pg-cleanup'ы.
    • EmailNotificationJobs — weekly-summary + low-stock email.
    • OwnerDailySummaryJob — Telegram-сводка владельцу.
    • ReferencePriceRefreshJob — пересчёт Product.ReferencePrice каждые 30 дней без приёмок.
  • Seed/:
    • SystemReferenceSeeder — справочники (страны, валюты, единицы).
    • OpenIddictClientSeeder — регистрирует client food-market-web.
    • DevDataSeeder — dev-only admin user (SuperAdmin).
    • DemoTenantSeeder / YearDemoSeeder — заполняют tenant демо-данными (Sprint 5 / Sprint 10).

Web (src/food-market.web/)

  • Vite + React 19 + TS 6, Tailwind v4. Маршрутизация — React Router 6.
  • src/lib/api.ts — axios instance с auto-refresh токена.
  • src/lib/auth.ts — login/logout, store токена в localStorage.
  • src/components/ — общие виджеты (Field, Button, Skeleton, CommandPalette, DashboardWidgets).
  • src/pages/ — страницы (один файл per route).
  • TanStack Query — кеширование API-вызовов, инвалидация по SignalR.
  • AG Grid Community — большие списки (товары, контрагенты, отчёты).

POS (src/food-market.pos*/)

  • pos.core/ — логика без UI: оффлайн-буфер, sync, расчёт чека.
  • pos/ — WPF UI, CommunityToolkit.Mvvm, SQLite, Refit + Polly, System.IO.Ports для весов CAS.
  • Sync: батчем по 50 чеков через POST /api/pos/v1/batch с Idempotency-Key. Сервер дедупит через pos_batch_acks уникальный индекс (OrganizationId, IdempotencyKey).

Composition root (Program.cs)

Логические блоки в порядке регистрации:

  1. Serilog bootstrap (до builder).
  2. CORS (Cors:AllowedOrigins из конфига).
  3. HttpContextAccessor + ITenantContext + IClaimsTransformation (SuperAdmin override роли).
  4. EF Core: AppDbContext (Npgsql, OpenIddict, два interceptor'а).
  5. Identity + OpenIddict server (password + refresh, rolling refresh, leeway = 0).
  6. Authentication/Authorization policies (AdminAccess, perm:*).
  7. Rate-limiter (/connect/token, /api/auth/signup).
  8. HealthChecks (database тег ready).
  9. IEmailSender (Singleton + scope для DbContext).
  10. IFiscalProvider + 3 HttpClient + IFiscalProviderFactory.
  11. MediatR (assembly scan), FluentValidation.
  12. MoySklad HttpClient + import service.
  13. Hangfire server + storage (PG), HangfireJobsConfigurator хостед.
  14. SignalR + INotificationsPublisher.
  15. Telegram-бот HttpClient (если token задан).
  16. Сидеры: OpenIddictClientSeeder, SystemReferenceSeeder, DevDataSeeder хостед; DemoTenantSeeder/YearDemoSeeder scoped.
  17. Build() → middleware pipeline (Serilog→CORS→HttpMetrics→ RateLimiter→hubs-token-fix→AuthN→AuthZ→LogEnrichment→ ReadonlyOverride→StaticFiles[/uploads]→Swagger→MapControllers→ MapHub→MapMetrics→HangfireDashboard→HealthChecks).
  18. На старте: db.Database.Migrate() (идемпотентно).
  19. app.Run().

Поток: signup → bootstrap → первая продажа

1. POST /api/auth/signup { email, password, organizationName, phone }
   ─→ создание Organization (Entity, не tenant-scoped)
   ─→ создание AppUser + добавление в роль "Admin"
   ─→ создание Employee с AdminRole и всеми permission'ами
   ─→ создание главного Store (isMain=true) + RetailPoint
   ─→ создание PriceType "Розничная" (isRetail=true, isSystem=true)
   ─→ копирование системных UnitOfMeasure (OrganizationId=null) на org

2. POST /connect/token { grant_type=password, username, password }
   ─→ OpenIddict проверяет, выдаёт access_token + refresh_token
   ─→ access_token содержит claim org_id и role

3. GET /api/me  (web bootstrap)
   ─→ возвращает { sub, email, roles, orgId, hasLiveOrg, hasActiveEmployee }
   ─→ фронт роутит на /dashboard или /no-organization (orphan-fallback)

4. POST /api/catalog/products  { name, prices, barcodes, ... }
   ─→ ValidationFilter (FluentValidation)
   ─→ controller → _db.Products.Add(...) (OrganizationId stamped в SaveChanges)
   ─→ возвращает Product DTO

5. POST /api/purchases/supplies + POST /{id}/post
   ─→ post идёт под Serializable tx
   ─→ для каждой строки StockService.ApplyMovementAsync(+qty, MovementType.Supply)
   ─→ Stock row для (productId, storeId) либо создаётся, либо обновляется
   ─→ commit, AppMetrics.IncrementPosted("supply")
   ─→ SignalR: NotificationsHub → группа org → событие SupplyPosted

6. POST /api/sales/retail + POST /{id}/post
   ─→ Serializable tx, проверка остатка ≥ 0 для каждой строки
   ─→ StockService.ApplyMovementAsync(-qty, MovementType.RetailSale)
   ─→ commit, AppMetrics.IncrementPosted("retail-sale")
   ─→ best-effort TryFiscalizeAsync (Sprint 11) — отдельно, после commit
   ─→ SignalR: SalePosted (dashboard виджеты инвалидируют queries)

База данных

Postgres 14+ для dev (brew systemwide), Postgres 16 в Docker для stage/prod. Названия таблиц snake_case через явный ToTable("…").

Ключевые таблицы

Таблица Назначение
organizations Корневой tenant. Не tenant-scoped.
users, roles, user_roles ASP.NET Identity.
employees, employee_roles, role_permissions Сотрудники tenant'а + кастомные роли с булевыми флагами прав.
products, product_prices, product_barcodes, product_images, product_groups Каталог товаров.
counterparties Поставщики + покупатели (тип=Supplier/Individual/Legal).
stores, retail_points, units_of_measure, currencies, price_types, countries Справочники.
stocks, stock_movements Остатки + история движений. stocks — кеш SUM(stock_movements).
supplies, supply_lines, enters, enter_lines, supplier_returns, supplier_return_lines Приходные документы.
losses, loss_lines, transfers, transfer_lines, inventory_docs, inventory_lines Внутренний учёт.
retail_sales, retail_sale_lines Чеки розницы + строки чека. Sprint 11: ОФД-снапшоты на retail_sales (FiscalNumber, FiscalQrCode, …).
demands, demand_lines Опт-отгрузки.
loyalty_programs, loyalty_cards, promotions Sprint 9.
pos_batch_acks Идемпотентность POS-синка (UNIQUE OrganizationId, IdempotencyKey).
org_audit_log JSONB-diff каждой mutate-операции tenant'а.
super_admin_audit_log Действия SuperAdmin'а (особенно в режиме «открыто как…»).
platform_settings Singleton: SMTP-конфиг платформы.
system_settings Singleton: per-tenant фичи (не путать с platform).
import_jobs История импортов MoySklad.

OpenIddict хранит openiddict_applications/authorizations/tokens/scopes. Hangfire — hangfire.* (в своей схеме).

Concurrency

IVersionedEntity сущности (Supply, RetailSale, Demand, Enter, Loss, Transfer, InventoryDoc, SupplierReturn) включают PG xmin через UseXminAsConcurrencyToken(). Параллельные апдейты одного документа получают DbUpdateConcurrencyException, контроллер возвращает 409.

Post-операции, изменяющие остаток, идут под IsolationLevel.Serializable (см. RetailSalesController.Post, SuppliesController.Post, …) — это защищает от race в SUM(stock_movements)-инварианте.

Внешние интеграции

Сервис Где Состояние
MoySklad Infrastructure/Integrations/MoySklad/ Импорт товаров, контрагентов, остатков. Per-org token в Organization.MoySkladToken.
SMTP Infrastructure/Email/MailKitEmailSender.cs Платформенный SMTP в PlatformSettings (SuperAdmin настраивает). Используется для invite, forgot-password, weekly-summary.
Telegram Bot Api/Integrations/Telegram/ Owner-сводка. Per-org OwnerTelegramChatId. Bot token в env.
ОФД (Webkassa / Kassa24 / ОФД-Соло) Infrastructure/Fiscal/ Sprint 11 scaffolding. Per-org провайдер + креды.
MinIO (S3) Api/Storage/StorageBootstrap.cs Опциональный сторадж изображений. Если не настроен — /uploads volume на FS.

Тесты

  • Unit (tests/food-market.UnitTests/) — xUnit + InMemory EF + чистые юниты на валидаторы, payload-builder'ы, MediatR-handler'ы.
  • Integration (tests/food-market.IntegrationTests/) — xUnit + Testcontainers Postgres (postgres:16-alpine). Полный API через WebApplicationFactory<Program>. Shared ApiFactory через ApiCollection (один контейнер на сессию xunit). Memory note: test_suites_setup — Ryuk выключен (TCN не тянет с docker-hub), rate-limiter eager-config через env-переменную.
  • E2E (tests/e2e/) — Playwright (TS) против stage https://test.admin.food-market.kz. Используется в verify-suite'ах по спринтам.
  • Load (tests/load/) — k6 (Sprint 12). См. docs/performance-baseline.md.

Sprint 13-22 changes (быстрая сводка)

Sprint Что добавлено / изменено
13 (security) SecurityHeadersMiddleware (CSP, X-Frame, HSTS); rate-limit на signup (3/h IP) и forgot-password (3/h email + 10/h IP); SensitiveOpsAudit сервис для logged-ops; POST /api/me/sessions/revoke-all через IOpenIddictAuthorizationManager; Hangfire-dashboard под SuperAdminHangfireFilter + nginx /hangfire location. Также: dedicated PG-роль food_market_server_app для legacy back.food-market.kz (без superuser).
14 (perf) Phase14a индексы (composite + partial с INCLUDE на retail_sales); N+1 fix в SalesReportController.FetchAsync; React.lazy на 30 редких страниц + Recharts lazy → bundle 1456→706 KB (51%); ImageSharp генерирует thumb/medium WebP при загрузке + <ProductImage> с <picture> srcset; Npgsql pool (Min=10/Max=100/AutoPrepare=20); JobTimingFilter для Hangfire-jobs.
15 (a11y + tests) useFocusTrap (WCAG 2.4.3/2.1.2) на Modal + ConfirmDialog; axe-core spec-suite (10 страниц, 0 critical); aria-label на icon-only back-links + role="alert" на form errors; coverage Application 67%→83%, Domain 11%→79%; property-based tests на StockService (Σ movements ≡ Stock); verified backup-recovery drill RTO ~25s.
16 (regression) Regression suite 35 Playwright flows + 60 visual snapshots; nightly stage-verify cron; Forgejo workflow regression; README badges; factories для test-data.
17 (onboarding) /onboarding-wizard (4 шага + skip + localStorage.fm.wizardCompleted); HelpTooltip + 13 topics; /help knowledge base из 7 markdown'ов; FeedbackWidget (bug/suggestion/question + Telegram fallback); /admin/diagnostic (7 параллельных проверок); /whats-new из CHANGELOG.md; EmptyStateWithDemo.
18 (TODO cleanup) P0 race в GenerateNumberAsync через PostgreSQL advisory lock (pg_advisory_xact_lock(orgHash, docTypeHash)); WhatsNewBanner в AppLayout; color contrast WCAG-AA (19 файлов); useFormatCurrency() hook; audit-log UI filters (Кто/Дата с/по); NotificationCenter (bell-icon SignalR-popover).
19 (power UX) Phase19a: Product.IsArchived + IsAvailableForSale (partial-index). POST /api/catalog/products/bulk-update {ids, op, params} — 5 операций (price-adjust %/абсолют, change-group, archive/unarchive, toggle-sale) одной транзакцией. SavedPresets chips (UserPreset jsonb). QuickActionsPalette (Cmd+J отдельно от Cmd+K). InlinePriceCell dblclick → input optimistic + revert. CSV import 1000 строк транзакцией. ExportButton (CSV/XLSX) на 5 контроллерах. Keyboard nav в DataTable (↑↓/Enter/Space/Delete).
20 (Mapster + maintenance) TD-3: MapsterConfig.cs + .ProjectToType<TDto>(MapsterConfig.Config) вместо ручных Select-expression'ов. SSO scaffold: Microsoft.AspNetCore.Authentication.Google + .MicrosoftAccount (conditional registration); ExternalAuthController (503 если не настроено, 501 callback с email для invite-flow). 3 новых cleanup-job'a (org-audit-log >90д, drafts >30д, refresh-tokens revoked >7д). DatabaseMaintenanceJobs.VacuumTopTablesAsync (топ-5 таблиц, weekly). DiskMonitoringJob ежечасно + Telegram-alert <1GB + Prom-gauge food_market_disk_free_bytes{mount}. ~/nightly-perf-check.sh baseline-comparison через /metrics. Astro layout: gtag/Yandex.Metrika placeholders + docs/analytics.md.
21 (prod toolchain) deploy/check-prod-readiness.sh (backup<60min, disk≥5GB, /health, .env), prod-deploy.sh (blue-green :8088 + nginx upstream switch), prod-rollback.sh (atomic), post-deploy-smoke.sh (10 шагов JSON через python3 — на stage 10/10 ✓), db-schema-diff.sh (pg_dump через ssh+docker exec, sed-нормализация, diff -u), generate-release-notes.sh (git log → markdown group by prefix), .forgejo/workflows/auto-tag.yml (v.). Все скрипты — --dry-run.
22 (data tooling) Phase22a: org_exports таблица (jsonb config-like, unique download token). POST /api/org/export → Hangfire OrgExportJob собирает ZIP с JSON-файлами по каждой сущности → IObjectStorage + DownloadToken 64-hex + 24h TTL + email-notify. POST /api/catalog/products/import/1c-csv (Windows-1251 + auto-detect разделитель + русские заголовки). deploy/anonymize-prod.sh (PII обфускация: email→user{N}@example.kz, phone→+7700111{N:04}, passwords→тестовый hash, BIN/IIN синтетические, MoySkladToken=NULL). DbSchemaDocsJob weekly → db-schema-generated.md с mermaid ER-диаграммой. `POST /api/admin/audit-log/export?format=csv

Production readiness (после 22 спринтов)

Реализовано полностью

  • Backend: auth (OpenIddict password+refresh+revoke), multi-tenant (query-filter + advisory locks), все 8 типов документов (Supply/Enter/Loss/Transfer/Inventory/RetailSale/Demand/SupplierReturn+CustomerReturn) с проводкой через Serializable transactions и ОФД-snapshot полями.
  • Каталог: products + barcodes + prices + images (thumb/medium WebP), groups (иерархия с Path), counterparties, units, currencies, countries, stores, retail-points.
  • Reports: Sales / Stock / Profit / ABC с CSV+XLSX export'ом, all multi-tenant.
  • Background: Hangfire с 10 recurring jobs (housekeeping, email-notify, telegram-summary, vacuum, disk-monitor, db-schema-docs).
  • Observability: Prometheus /metrics (HTTP + DB query duration + business counters + disk gauge), Serilog structured logging, /health/{live,ready} с DB-проверкой.
  • A11y: WCAG 2 AA color contrast, focus-trap в modal, axe-core spec-suite 0 critical, keyboard-nav (Cmd+K, Cmd+J, table ↑↓/Enter/Space/Delete).
  • Tests: 80%+ coverage на Application, integration tests с Testcontainers, e2e Playwright 44 specs зелёные на stage, k6 load baseline.
  • Web: React 19 + Vite + TS, AG Grid Community, TanStack Query, 200 KB initial bundle (gzip), inline-edit, bulk-операции, CSV import/export, SavedPresets, Cmd+J QuickActions, NotificationCenter.
  • POS: WPF на .NET 8, оффлайн-буфер SQLite, синк через /api/pos/v1/* с идемпотентным batch-ack, ОФД-провайдеры (Mock работает, реальные — scaffolding).
  • DevOps: backup-таймер с retention 30d, stage→prod toolchain (7 скриптов из Sprint 21), auto-tag workflow, anonymize-prod для безопасных stage-дампов.

Scaffolding (готово к подключению, но не активно)

Что Где Что нужно от user'а
SSO Google Authentication:Google:ClientId/Secret OAuth credentials с Google Cloud Console
SSO Microsoft Authentication:Microsoft:ClientId/Secret OAuth credentials с Azure App Registration
ОФД Webkassa OrganizationFiscal.{Endpoint,Login,Password,CashboxId} Договор + кассовый аппарат + creds
ОФД Kassa24 / ОФД-Соло то же Договоры с провайдерами
MoySklad sync Organization.MoySkladToken Per-org OAuth token у клиента
Telegram alerts Monitoring:SuperAdminTelegramChatIds Chat-id'ы суперадминов
Yandex.Metrika / GA4 env PUBLIC_YM_ID / PUBLIC_GA_ID Счётчики у клиента
SMTP PlatformSettings.Smtp* SendGrid / Mailgun / Yandex300 креды
MinIO storage Storage:Minio:Endpoint/AccessKey/SecretKey S3-совместимый bucket (опц.)

Не реализовано (требует отдельного решения)

  • Прод-деплой — toolchain готов (deploy/prod-deploy.sh), но реальный сервер не настроен (DNS, certbot, /etc/nginx/conf.d/food-market-upstream.conf).
  • SSO callback flow/api/auth/external/callback возвращает 501 с email; нужен invite-flow + tokens-issuance.
  • Kazakh-перевод — i18n keys на русском; для прод-релиза в РК нужен носитель языка.
  • POS Windows-тест — POS-проект собирается на macOS/Linux но требует Windows для UI-тестов.
  • Down-миграции — EF Migration.Down() есть в коде, но не валидированы для прод-данных (data loss risk).
  • Public marketing site SEOfood-market.kz (Astro) собирается, но не задеплоен.

Файловая структура (актуальная)

food-market/
├── src/
│   ├── food-market.domain/           # POCO + interfaces + enums
│   ├── food-market.application/      # DTO + handlers + mapping (Sprint 20)
│   ├── food-market.infrastructure/   # EF + Identity + OpenIddict + integrations
│   ├── food-market.api/              # Controllers + middleware + Hangfire jobs + storage
│   ├── food-market.web/              # React admin SPA
│   ├── food-market.public/           # Astro marketing site
│   ├── food-market.shared/           # POS↔API DTO-контракты
│   ├── food-market.pos.core/         # POS-логика (UI-agnostic)
│   └── food-market.pos/              # WPF (.NET 8 Windows)
├── tests/
│   ├── food-market.UnitTests/        # xUnit + InMemory EF
│   ├── food-market.IntegrationTests/ # xUnit + Testcontainers Postgres
│   ├── e2e/                          # Playwright + ad-hoc smoke scenarios
│   └── load/                         # k6 (retail-sales-parallel, signup-burst, …)
├── deploy/
│   ├── docker-compose.yml            # Postgres + api + web + (registry)
│   ├── Dockerfile.{api,web,public}
│   ├── nginx.conf                    # SPA + reverse-proxy
│   ├── backup.sh / food-market-backup.* # systemd-timer ежедневный бэкап
│   ├── check-prod-readiness.sh       # Sprint 21
│   ├── prod-deploy.sh                # Sprint 21
│   ├── prod-rollback.sh              # Sprint 21
│   ├── post-deploy-smoke.sh          # Sprint 21
│   ├── db-schema-diff.sh             # Sprint 21
│   ├── generate-release-notes.sh     # Sprint 21
│   └── anonymize-prod.sh             # Sprint 22
├── docs/                             # 30+ markdown файлов
├── .forgejo/workflows/               # CI (ci.yml, regression.yml, auto-tag.yml, …)
└── food-market.sln

Релиз-цикл

  1. Локально: dotnet build + dotnet test + pnpm build.
  2. git push origin main (Forgejo на 127.0.0.1:3000 — primary remote, GitHub — mirror, memory feedback_forgejo_primary).
  3. ~/deploy-stage.sh — docker build api+web → push в локальный registry 192.168.1.193:5001 → ssh на prod-vm → docker compose pull && up -d.
  4. Health check на https://test.admin.food-market.kz/health/ready.
  5. Verify на stage (Playwright или ручной чек).
  6. Prod-деплой — пока ручной (TBD, нужен план от user'а).

Что ещё прочитать