food-market/docs/TZ-доработка.md
nns 35d70c5d80 docs: ТЗ на доработку и тестирование (полный аудит 2026-05-22)
Два документа после полного обхода кодовой базы:
- docs/TZ-доработка.md — что нужно сделать, P0/P1/P2 приоритеты,
  дорожная карта по спринтам, технический долг
- docs/TZ-тестирование.md — сценарии тестирования по модулям,
  multi-tenant изоляция, регрессионный чек-лист, стратегия
  покрытия unit/integration/E2E

Сводка готовности: ядро (auth/catalog/Supply/RetailSale/multi-tenancy)
85-95%, но критичные пробелы: ОФД, складские документы Enter/Loss/
Transfer/Inventory, Demand, отчёты, POS-приложение, observability.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-22 15:30:04 +05:00

30 KiB
Raw Permalink Blame History

ТЗ на доработку Food Market

Дата составления: 2026-05-22 Автор анализа: Claude Opus 4.7 Базируется на полном обходе кодовой базы ~/food-market (backend + web + public + tests + deploy).


1. Текущее состояние системы

1.1. Сводная таблица готовности по модулям

Модуль / слой Статус Готовность Ключевой комментарий
food-market.domain готово 95% 26 сущностей, мультитенантность через ITenantEntity/IOptionalTenantEntity, чисто (нет TODO/HACK).
food-market.infrastructure готово 90% EF Core 8, query filters, MailKit SMTP, StockService, MoySkladClient, 34 миграции.
food-market.api (контроллеры) готово 85% 27 контроллеров, ~120 endpoint'ов, OpenIddict (password + refresh), CRUD полный.
food-market.application 🟡 частично 60% Только DTO + интерфейсы, нет MediatR handlers — вся логика в контроллерах.
food-market.web (админка) готово 95% 35 страниц, темная тема, адаптив, RU-локаль, onBlur-валидация.
food-market.public (сайт) готово 90% Astro 4: landing, тарифы, блог, KB, legal; SignupForm → API.
food-market.shared (POS контракты) нет 0% Только .csproj, ни одного CS-файла.
food-market.pos.core + food-market.pos скелет 5% Пустой WPF-проект, только зависимости в .csproj.
POS Sync API нет 0% Нет /api/pos/sync, нет /api/pos/sales bulk, нет WebSocket.
Documents: Supply / RetailSale готово 100% Полный цикл (Draft → Post → Unpost), Stock + Movement, Cost (скользящее среднее).
Documents: Inventory / Loss / Enter / Transfer нет 0% Нет контроллеров и страниц. Domain-сущности тоже не определены.
Documents: Demand (оптовая отгрузка) нет 0% Только enum MovementType.WholesaleSale, контроллера/сущности нет.
Reports нет 5% Есть /api/sales/retail/stats для дашборда, отдельных отчётов нет.
MoySklad интеграция 🟡 частично 50% Импорт товаров и контрагентов ; нет Demand/Payment sync, нет webhook'ов.
OpenIddict auth готово 100% Password + refresh_token; org_id, role, sub claims; persistent dev-ключи.
Multi-tenancy готово 95% Query filters + HttpContextTenantContext; SuperAdmin override read-only/edit.
Permission-based authz (RolePermissions) 🟡 частично 30% 30+ флагов в БД, но контроллеры проверяют только Roles (Admin/Cashier и т.д.).
SuperAdmin Console готово 95% Organizations CRUD, audit log, archive/restore, platform settings (SMTP); биллинг-KPI заглушка.
Hangfire 🟡 частично 40% ReferencePriceRefreshJob ; нет dashboard, нет scheduled cleanup, нет retry.
Email (SMTP) готово 100% MailKit, DataProtection-шифрование пароля, forgot-password flow.
Платёжные интеграции (Kaspi/Halyk/Jusan) нет 0% Упомянуты только в маркетинге; есть PaymentMethod enum, реальных шлюзов нет.
ОФД (фискализация чеков РК) нет 0% Поля FiscalSerial/FiscalRegNumber есть в RetailPoint, отправки чеков нет.
Маркетплейсы (Ozon, Wildberries, Kaspi Magazin) нет 0% Только маркетинговые баннеры.
CI/CD (Forgejo Actions) готово 90% docker-api/web/public + smoke-тест /health после деплоя; self-hosted runner.
Docker / docker-compose (stage) готово 95% postgres:16 + api + web + public + persistent volumes + local registry.
E2E тесты 🟡 частично 40% Один сценарий full-cycle (12 шагов), отчёт в md; нет регрессии и параллелизма.
Backend unit/integration тесты нет 0% Совсем. В CI стоит || echo "No tests yet".
Logging / Serilog готово 90% Console + File с ротацией 14 дней; нет structured fields для бизнес-событий.
Health checks (детальные) 🟡 частично 20% Только /health → {status:ok}; нет проверки БД, SMTP, диска.
Метрики / observability нет 0% Нет Prometheus/AppInsights/OpenTelemetry.
Rate limiting 🟡 частично 15% Только в forgot-password (3/час/IP, in-memory).
Backup БД 🟡 частично 60% deploy/backup.sh есть, но не привязан к cron/timer, restore-скрипта нет.

1.2. Что точно работает (готово к продакшен-использованию)

  • Регистрация → онбординг → ежедневная работа магазина (товары, цены, приёмки, розничные продажи, остатки).
  • Управление пользователями и ролями, soft-delete, передача владельца, восстановление пароля по email.
  • SuperAdmin-консоль платформы (создание/архивирование организаций, SMTP, аудит).
  • Импорт каталога из МойСклад (товары + контрагенты, асинхронный job с прогрессом).
  • Полный stage-стенд на docker-compose с локальным registry и автодеплоем через Forgejo Actions.

1.3. Где точно не получится запуститься без доработки

  • Невозможно работать с физическим магазином без ККМ-фискализации (РК требует чеки в ОФД).
  • Невозможно вести полноценный складской учёт — нет инвентаризации, оприходования, списания, перемещения.
  • Нет аналитики/отчётов — только сводка на дашборде, ABC-анализа, отчёта по поставщикам/прибыли нет.
  • Нет POS-приложения — главная ценность проекта (offline-касса на Windows) — пустой проект.
  • Нет защиты от перебора паролей в основных endpoint'ах (login/signup), только в forgot-password.

2. ТЗ на доработку по приоритетам

Приоритет P0 — блокеры запуска в продакшен

# Задача Что сделать Зачем
P0-1 Production-сертификаты OpenIddict Заменить App_Data/openiddict-dev-key.xml на реальные RSA/X.509 сертификаты, читать из KeyVault или secrets. Сейчас токены подписываются dev-ключом без шифрования access-token. В проде это утечка claims.
P0-2 HTTPS на nginx Настроить TLS-termination на reverse-proxy (Let's Encrypt через certbot), форсировать HTTPS-only, добавить HSTS. OAuth/refresh_token нельзя гонять по HTTP.
P0-3 Rate limiting на login/signup Добавить Microsoft.AspNetCore.RateLimiting (sliding window) на /connect/token, /api/auth/signup. 5 попыток/минута/IP, 20/час/IP. Перебор паролей и DOS публичного signup.
P0-4 Health check БД Расширить /health на /health/live (alive) + /health/ready (DB ping, миграции применены). Использовать Microsoft.Extensions.Diagnostics.HealthChecks. Сейчас docker-compose healthcheck возвращает 200 даже когда БД упала — стейдж не падает корректно.
P0-5 Permission-based authorization В RolePermissions (Domain) уже 30+ флагов. Реализовать PermissionHandler (IAuthorizationHandler) + атрибут [RequiresPermission("ProductsEdit")], проверять в контроллерах вместо [Authorize(Roles=...)]. Без этого все Admin'ы организации имеют полные права, кастомные роли (Менеджер/Кладовщик/Кассир) — фикция.
P0-6 Автоматический backup БД Создать systemd-timer (food-market-backup.timer) на ежедневный запуск deploy/backup.sh, добавить restore-инструкцию в docs/. Хранить 30 дней локально + копия в S3/MinIO. Сейчас бэкап делается вручную, восстановления не отрепетировали.
P0-7 ОФД фискализация РК Интегрировать одного оператора (например, Webkassa или ОФД-Соло, КГД РК), отправлять RetailSale.Post чек, сохранять QR-код и фискальный номер в RetailSale.FiscalQrCode/FiscalNumber. В РК продажа без чека ОФД — административное правонарушение. Без этого нельзя продавать.
P0-8 .env.example + документация secrets Описать все required env-переменные (ConnectionStrings__DefaultConnection, Cors__AllowedOrigins, Smtp__*, OpenIddict__Issuer, OFD__Token). Обновить docs/stage-setup.md. Сейчас новый деплой не задокументирован. Передача знаний из головы — узкое место.
P0-9 Чек-листы перед релизом Документ docs/release-checklist.md: миграции применены, бэкап свежий, smoke-тесты прошли, E2E full-cycle зелёный, мониторинг здоров. Снижает риск выкатки в проде сломанной версии.

Приоритет P1 — важные функциональные пробелы

# Задача Что сделать Зачем
P1-1 Документ «Оприходование» (Enter) Domain-сущность Enter + EnterLine (как Supply, но без поставщика). Контроллер CRUD + Post/Unpost. UI-страницы /inventory/enters. Создаёт StockMovement с типом Enter. Нужно вводить начальные остатки и излишки инвентаризации без поставщика.
P1-2 Документ «Списание» (Loss) Domain-сущность Loss + LossLine (причина: брак, истечение срока, бой, недостача). Контроллер + UI. StockMovement тип WriteOff. Списание брака — обязательная функция магазина.
P1-3 Документ «Перемещение» (Transfer) Domain Transfer + TransferLine (FromStore → ToStore). Контроллер с атомарной транзакцией (списание + поступление). UI-форма. В сети магазинов товар постоянно перемещается между складами.
P1-4 Документ «Инвентаризация» (Inventory) Domain Inventory + InventoryLine (book qty, actual qty, diff). Контроллер с импортом текущих остатков + Post создаёт корректирующее движение InventoryAdjustment. UI-форма с CSV-импортом фактического количества. Регулярная сверка остатков — обязательно для розницы.
P1-5 Документ «Оптовая отгрузка» (Demand) Domain Demand + DemandLine (покупатель, способ оплаты — наличные/безнал, цена опт.). Контроллер. UI-страницы. StockMovement тип WholesaleSale. Часть клиентов работает с юрлицами, отгрузка по накладной с НДС.
P1-6 Возврат от покупателя (CustomerReturn) Расширить RetailSale опцией «Возврат» (по чеку или без). Domain enum MovementType.CustomerReturn уже есть. UI: кнопка «Создать возврат» из посту-проведённой продажи. Закон о защите прав потребителей в РК требует приёма возвратов.
P1-7 Возврат поставщику (SupplierReturn) По аналогии с CustomerReturn для Supply. UI: «Возврат поставщику» из проведённой приёмки. Брак, неликвид, отказ от партии.
P1-8 Отчёт «Продажи» /api/reports/sales с группировкой по периодам (день/неделя/месяц), товарам, кассирам, кассам, способам оплаты. UI: страница /reports/sales с фильтром периода и экспортом в CSV/XLSX. Без отчёта по продажам управлять бизнесом невозможно.
P1-9 Отчёт «Остатки на дату» /api/reports/stock с восстановлением остатков на любую дату через StockMovement журнал. UI с экспортом. Налоговый учёт, инвентаризация.
P1-10 Отчёт «Прибыль» /api/reports/profit — выручка - себестоимость по периодам/группам/товарам. Используем Cost snapshot из RetailSaleLine. Главный показатель магазина.
P1-11 Отчёт «ABC-анализ» Топ товаров по выручке/прибыли/маржинальности за период. Группа A/B/C по правилу Парето. Управление ассортиментом.
P1-12 POS Sync API Endpoints: GET /api/pos/sync?since={ts} (товары, цены, остатки, контрагенты с изменениями после ts); POST /api/pos/sales (батч продаж с idempotency-key). Контракты в food-market.shared. Без этого POS-приложение не может синхронизироваться с сервером.
P1-13 POS WPF MVP Минимальный UI: логин кассира (привязка к RetailPoint), список товаров/поиск по штрихкоду, корзина, оплата (нал/карта), печать чека (с ОФД), оффлайн-буфер на SQLite, фоновая синхронизация. Главная фича проекта по позиционированию.
P1-14 MoySklad — Demand sync Импорт оптовых отгрузок (демандов) из МойСклад. Расширить MoySkladImportService. Текущая интеграция только односторонняя для каталога; продажи не синхронизируются.
P1-15 MoySklad — webhook на изменения Получать webhook'и от МойСклад при изменении товаров, автоматически обновлять каталог (вместо ручного «Импортировать сейчас»). Двусторонняя живая синхронизация.
P1-16 Hangfire dashboard Подключить Hangfire.Dashboard с авторизацией только для SuperAdmin. Добавить scheduled jobs: ежедневный cleanup StockMovement (старше 2 лет), audit-log (старше 90 дней), eтиничные jobs (e.g. рассылка email). Сейчас jobs запускаются только вручную через AdminCleanupController; нет видимости.
P1-17 Метрики Prometheus Подключить prometheus-net.AspNetCore (/metrics endpoint). Базовый набор: http_requests_total, http_request_duration, db_query_duration, business: sales_count, supply_posted_count, errors_total. Без observability нельзя гнать прод.
P1-18 Аудит мутаций tenant'а Расширить SuperAdminAuditLog на обычные org-мутации (OrgAuditLog): кто, когда, что изменил в Supply/Sale/Product/Counterparty. Хранить diff JSON. Розница часто судится с сотрудниками по поводу пропавших товаров — нужны доказательства.
P1-19 OpenAPI спецификация Включить Swashbuckle.AspNetCore. Опубликовать /swagger/v1/swagger.json (только в Dev) и сгенерировать TypeScript-клиент для food-market.web. Удалит ручной труд по типизации API в фронте и POS.
P1-20 Unit-тесты критичной логики Покрыть xUnit'ом: StockService.ApplyMovement, расчёт Cost при SuppliesController.Post, расчёт автонаценки по ProductGroup.MarkupPercent, валидация платежа RetailSalesController.Post, multi-tenant query filter. Без этих тестов любое изменение логики Supply/Sale = потенциально баг с минусовыми остатками или потерями денег.
P1-21 Integration-тесты на тестовой БД Testcontainers.PostgreSql + WebApplicationFactory. Покрыть: signup-flow, supply post→unpost, retail sale post с overselling, tenant isolation (org A vs org B), permission проверки. Регрессия на каждый коммит в CI.
P1-22 Email-нотификации Готовый MailKit-сервис расширить шаблонами: приглашение сотрудника (с временным паролем), еженедельный отчёт владельцу, low-stock alert. Хранить шаблоны в Resources/EmailTemplates/*.html. Сейчас email отправляется только при forgot-password.

Приоритет P2 — желательные улучшения

# Задача Что сделать Зачем
P2-1 Платёжный шлюз Kaspi Pay Интеграция Kaspi Pay QR (касса показывает QR, покупатель оплачивает с приложения, callback фиксирует оплату в RetailSale). Самый популярный способ безнала в РК.
P2-2 Платёжные шлюзы банков Halyk Epay, Jusan Pay, Forte Pay (POS-терминал API или e-commerce). Альтернативы Kaspi.
P2-3 Интеграция с маркетплейсами Ozon Seller API, Wildberries, Kaspi Magazin — синхронизация остатков и цен (исходящая), импорт заказов (входящая). Расширение каналов продаж.
P2-4 2FA для админов TOTP (Google Authenticator) для роли Admin и SuperAdmin. Использовать Identity.AddDefaultTokenProviders + AuthenticatorTokenProvider. Защита платёжного функционала.
P2-5 SSO (Google/Microsoft) Расширить OpenIddict внешними провайдерами для логина персонала. UX для офисных сотрудников.
P2-6 Многоязычность (en/kz) Подключить react-i18next в web, выделить русские строки в locales/ru.json. Перевести интерфейс на казахский (государственное требование). Государство РК требует госязык в публичных интерфейсах.
P2-7 WebSocket / SignalR для real-time Push-уведомления на дашборд (новая продажа), кассе (изменение цены), импортах (вместо polling). UX + снижение нагрузки от polling.
P2-8 Аналитика на public-сайте Google Analytics или Yandex.Metrika, A/B тесты pricing'а, события signup-конверсии. Маркетинг.
P2-9 Mobile-приложение (PWA или React Native) Просмотр остатков, продаж, KPI для владельца. UX для владельцев.
P2-10 Распознавание чеков (OCR) Загрузка фото чека от поставщика → распознавание → автозаполнение Supply. Уменьшение ручного ввода.
P2-11 Электронные счёт-фактуры (ЭСФ) Интеграция с ИС ЭСФ КГД РК (выпуск счетов-фактур для юрлиц). Часть оптовых клиентов требует ЭСФ.
P2-12 Бонусные программы / скидочные карты Domain: LoyaltyProgram, LoyaltyCard. Списание/начисление в RetailSale. Удержание клиентов.
P2-13 Промокоды / акции Domain: Promotion, правила (категория, период, % скидки). UI-настройка из админки. Маркетинг для магазина.
P2-14 Telegram-бот для владельца Ежедневная сводка выручки, low-stock alerts. UX для владельцев.
P2-15 Multi-storage для изображений Сейчас файлы лежат в /app/uploads (volume). Перевести на S3-совместимое хранилище (MinIO/Yandex.Cloud). Масштабируемость, отказоустойчивость.

3. Дорожная карта (рекомендованная последовательность)

Спринт 1 — Стабилизация (2-3 недели)

Цель: безопасно выкатить текущий функционал в прод.

  • P0-1 → P0-9 (все блокеры запуска)
  • P1-20, P1-21 (юнит/интеграционные тесты на текущую логику)
  • P1-18 (аудит мутаций tenant'а)

Критерий готовности: прод-стенд работает с HTTPS, rate-limit'ы установлены, бэкап автоматический, фискализация ОФД работает, права RolePermissions проверяются.

Спринт 2 — Складской учёт (3-4 недели)

Цель: полноценное складское ядро ERP.

  • P1-1 (Enter), P1-2 (Loss), P1-3 (Transfer), P1-4 (Inventory)
  • P1-6 (CustomerReturn), P1-7 (SupplierReturn)
  • P1-16 (Hangfire dashboard + scheduled cleanup)

Критерий готовности: магазин может вести полный складской учёт без обходных путей.

Спринт 3 — Отчёты и аналитика (2 недели)

  • P1-8 (Sales report), P1-9 (Stock on date), P1-10 (Profit), P1-11 (ABC)
  • P1-19 (OpenAPI / Swagger)

Критерий готовности: владелец видит, как идёт бизнес, без выгрузки в Excel.

Спринт 4 — POS (4-6 недель)

  • P1-12 (POS Sync API), food-market.shared контракты
  • P1-13 (POS WPF MVP)
  • P1-17 (метрики Prometheus + Grafana dashboard)

Критерий готовности: касса работает оффлайн, синхронизируется с сервером, печатает фискальные чеки.

Спринт 5 — Оптовые продажи + MoySklad full sync (2-3 недели)

  • P1-5 (Demand)
  • P1-14 (MoySklad Demand sync), P1-15 (webhook'и)
  • P1-22 (email-шаблоны)

Критерий готовности: клиент, работающий с юрлицами через МойСклад, может полностью перейти на Food Market.

Спринт 6+ — Интеграции и фичи (P2)

P2-1 Kaspi Pay → P2-3 маркетплейсы → P2-6 локализация → P2-11 ЭСФ → P2-12/13 лояльность/акции.


4. Технический долг (для рефакторинга)

Не блокирует функциональность, но затрудняет развитие.

# Что Почему важно
TD-1 CQRS через MediatR — перенести бизнес-логику из контроллеров в Command/Query handlers. Сейчас невозможно переиспользовать логику между API/POS/Hangfire. Контроллеры по 500 строк.
TD-2 FluentValidation — заменить inline-валидацию в контроллерах на отдельные Validator<T>. Сейчас валидация перемешана с бизнес-логикой, тестировать сложно.
TD-3 Mapster — выделить mapping в отдельные MapperConfig. Сейчас projection'ы инлайнятся в LINQ-запросы, переиспользования нет.
TD-4 Структурные log-fields в Serilog — добавить org_id, user_id, correlation_id в log scope. Сейчас в логах сложно найти конкретного пользователя/организацию.
TD-5 ImportJobRegistry в БД — сейчас in-memory ConcurrentDictionary. При рестарте API теряется. Перевести на таблицу ImportJobs. Жизненный цикл job'а >5 минут — рестарт обычное дело.
TD-6 Concurrency-токены на документахRowVersion (xmin/timestamp) на Supply/RetailSale, чтобы исключить race condition при параллельной правке. Сейчас два кассира могут испортить один чек.

5. Сводка по оценке готовности

┌──────────────────────────────────┬──────────────┬─────────────┐
│ Категория                        │ Готовность   │ Состояние   │
├──────────────────────────────────┼──────────────┼─────────────┤
│ Авторизация и multi-tenancy      │ 95%          │ ✅ готово   │
│ Каталог товаров                  │ 95%          │ ✅ готово   │
│ Документы (Supply, RetailSale)   │ 100%         │ ✅ готово   │
│ Документы (Inventory/Loss/...)   │ 0%           │ ❌ нет      │
│ Отчёты                           │ 5%           │ ❌ нет      │
│ POS                              │ 5%           │ ❌ нет      │
│ MoySklad                         │ 50%          │ 🟡 частично │
│ Платежи и фискализация           │ 0%           │ ❌ нет      │
│ Инфраструктура (CI/CD, Docker)   │ 90%          │ ✅ готово   │
│ Безопасность (HTTPS, rate-limit) │ 30%          │ 🟡 частично │
│ Observability (метрики, аудит)   │ 20%          │ 🟡 частично │
│ Тестирование                     │ 40%          │ 🟡 частично │
└──────────────────────────────────┴──────────────┴─────────────┘

  Общая готовность к продакшен-запуску:  60-65%
  - Для MVP "магазин на одном POS-терминале": требуется ОФД + базовые складские документы.
  - Для полноценного ERP: требуется выполнение P0+P1.
  - Для конкуренции с МойСклад: требуется ещё и P2.