food-market/docs/simple-mode-progress.md
nns 67c1b590ad feat(s29): web — простой режим скрывает меню / маршруты / виджеты (шаг 4/8)
- AdvancedModeGuard — render-guard: простой режим + не-SuperAdmin → страница
  «Недоступно в простом режиме» с текстом + кнопкой «На главную».
  SuperAdmin пропускается, чтобы можно было проверить работу после
  переключения.
- useMe (+ локальные MeResponse) расширены полем advancedMode: boolean.
- AppLayout.buildNav(roles, advancedMode) — в простом режиме скрыты:
  priceTypes, transfers, demands, promotions, loyaltyPrograms/Cards,
  reportAbc, auditLog, LanguageSwitcher.
- App.tsx — скрытые маршруты обёрнуты в <AdvancedModeGuard>:
  /catalog/price-types, /inventory/transfers*, /sales/demands*,
  /loyalty/programs, /loyalty/cards, /promotions, /audit-log,
  /reports/abc.
- DashboardPage — в простом режиме скрыты виджеты «Топ товаров» и
  «Маржа». Остаются KPI, график продаж, «Последние продажи», «Низкий
  остаток».
- Данные скрытых модулей НЕ удаляются при выключении режима; при
  повторном включении всё видно.

pnpm run build — ✓ 2.6s, 0 TS errors.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-10-04 11:43:10 +05:00

9.2 KiB
Raw Blame History

Simple Mode — прогресс

Задача: по умолчанию у всех организаций «Простой режим» — только базовые функции розничного учёта. Включить «Расширенный режим» может только SuperAdmin и только для конкретной организации. Данные при переключении не удаляются; при повторном включении всё видно.

Старт: 2026-10-04.

Чек-лист

  • 1. Domain + миграция Organization.IsAdvancedMode (bool, default false, backfill false)
  • 2. API PUT /api/super-admin/organizations/{id}/advanced-mode + аудит + /api/me.advancedMode; org-admin → 403
  • 3. Серверная защита: [RequiresAdvancedMode] → 403 ProblemDetails на скрытых модулях
  • 4. Web: меню, маршруты, дашборд фильтруются; прямой переход → страница «Недоступно»
  • 5. Упрощённые формы (товар / контрагент / документы) — скрыть «не нужные» поля, значения сохраняются
  • 6. SuperAdmin UI — бейдж «Простой/Расширенный» + переключатель с подтверждением
  • 7. Тесты (unit + integration + e2e); патч quality-watchdog при необходимости
  • 8. Деплой стейджа + docs/simple-mode.md

Простой режим — что видно

  • Главная: выручка за сегодня, что заканчивается, последние продажи
  • Товары и группы товаров
  • Контрагенты (единый список — поставщики и покупатели)
  • Склад: Приёмка, Оприходование, Списание, Инвентаризация, Возврат поставщику, Остатки
  • Продажи: Продажи, Возвраты покупателей
  • Отчёты: Продажи, Прибыль, Остатки на дату
  • Сотрудники и роли, Настройки магазина (включая ОФД), Импорт товаров
  • 2FA в профиле — остаётся

Расширенный режим — что добавляется

  • Оптовые отгрузки (Demand)
  • Перемещения между точками
  • Лояльность
  • Промокоды / акции
  • ABC-анализ
  • Аудит организации
  • Несколько типов цен (в простом — одна цена продажи)
  • Bulk-операции и сохранённые пресеты в таблицах
  • Telegram-бот владельца
  • SSO
  • Переключатель языка (в простом только русский)
  • Продвинутые виджеты дашборда

Журнал

2026-10-04 — Шаг 1 ✓

  • Organization.IsAdvancedMode (bool, default false) + XML-док, что переключает только SuperAdmin.
  • Миграция 20261004100000_Phase29a_IsAdvancedMode — идемпотентный ADD COLUMN IF NOT EXISTS с DEFAULT false; все существующие орги получают false.
  • Snapshot обновлён (AppDbContextModelSnapshot.cs).
  • Применено на dev-БД: 5/5 организаций IsAdvancedMode = false.
  • dotnet build food-market.api — ✓ (0 error, warnings — старые, не наши).

2026-10-04 — Шаг 2 ✓

  • PUT /api/super-admin/organizations/{id}/advanced-mode { "enabled": bool } в SuperAdminOrganizationsController — [Authorize(Roles="SuperAdmin")]. Админ орги получает 403 от стандартного Authorize-фильтра (роль Admin ≠ SuperAdmin); в контроллере сверху уже [Authorize(Roles="SuperAdmin")] — не нужно отдельного Forbid().
  • Запись в SuperAdminAuditLog: ActionType EnableAdvancedMode / DisableAdvancedMode, Description, Changes {"before":…,"after":…}, IpAddress. Идемпотентный переключатель: если состояние совпадает — 200 без аудит-записи.
  • Архивную орг переключать нельзя — 409.
  • /api/me отдаёт advancedMode (bool). SuperAdmin без org → false (это ОК — он работает через /api/super-admin/...).
  • OrgRow / OrgDetail в SuperAdmin list / detail — добавлено IsAdvancedMode.
  • Маршрут: /api/super-admin/... (с дефисом), так как существующий контроллер уже использует этот стиль; задача упоминала superadmin без дефиса — выбрал существующий стиль.
  • dotnet build food-market.api — ✓ (0 error).

2026-10-04 — Шаг 3 ✓

  • src/food-market.api/Infrastructure/Authorization/RequiresAdvancedModeAttribute.cs:
    • [RequiresAdvancedMode] — TypeFilterAttribute вокруг AdvancedModeFilter : IAsyncAuthorizationFilter.
    • Читает IsAdvancedMode по org_id-claim через AppDbContext (без доверия к клиентским заголовкам). Кэш in-memory 30 с, инвалидация после переключения.
    • SuperAdmin bypass (чтобы он мог проверять скрытые модули через «войти как»).
    • Отказ — 403 ProblemDetails с type, title, detail на русском и extensions.code = "advanced-mode-required" — фронт различает это 403 от обычного «нет прав».
  • Атрибут навешен на (контроллеры):
    • Sales/DemandsController (оптовые отгрузки)
    • Inventory/TransfersController (перемещения)
    • Loyalty/LoyaltyCardsController, Loyalty/LoyaltyProgramsController
    • Promotions/PromotionsController
    • Reports/AbcReportController
    • Admin/OrgAuditLogController
    • Telegram/TelegramBindingController
    • Common/UserPresetsController (пресеты фильтров)
  • И на отдельные действия:
    • Catalog/PriceTypesController — POST / PUT / DELETE (чтение остаётся — товары используют системный retail-прайс)
    • Catalog/ProductsController.BulkUpdate (POST /products/bulk-update)
  • Auth/ExternalAuthController (SSO) — атрибут НЕ навешен: endpoint инициирует OAuth-челлендж для анонима, org_id-claim ещё не выставлен. Контроль — на уровне UI (не показывать кнопку «Войти через Google/Microsoft» в простом режиме). Также SSO в простом режиме не ломается при включении — просто кнопка появляется.
  • SuperAdminOrganizationsController.SetAdvancedMode вызывает AdvancedModeFilter.InvalidateCache(orgId) после SaveChangesAsync, чтобы следующий запрос этой org сразу увидел новое значение.
  • dotnet build food-market.api — ✓ (0 error).

2026-10-04 — Шаг 4 ✓

  • src/food-market.web/src/components/AdvancedModeGuard.tsx — render-guard: me.advancedMode = false + не-SuperAdmin → страница «Недоступно в простом режиме» с текстом + кнопкой «На главную». SuperAdmin и расширенный режим — пропускает.
  • useMe.ts + локальные MeResponse в AppLayout/DashboardPage расширены полем advancedMode: boolean.
  • AppLayout.buildNav(roles, advancedMode) — в простом режиме скрыто:
    • nav.priceTypes (справочник типов цен)
    • nav.transfers (перемещения между точками)
    • nav.demands / nav.promotions / nav.loyaltyPrograms / nav.loyaltyCards
    • nav.reportAbc
    • nav.auditLog
    • LanguageSwitcher (в простом режиме интерфейс только по-русски)
  • App.tsx — скрытые маршруты обёрнуты в <AdvancedModeGuard>:
    • /catalog/price-types
    • /inventory/transfers, /inventory/transfers/new, /inventory/transfers/:id
    • /sales/demands, /sales/demands/new, /sales/demands/:id
    • /loyalty/programs, /loyalty/cards, /promotions
    • /audit-log
    • /reports/abc
  • DashboardPage — в простом режиме скрыты виджеты «Топ товаров» и «Маржа». Остаются KPI (выручка today/week/month, средний чек), график продаж, «Последние продажи», «Низкий остаток» — ровно то, что в чек-листе.
  • Данные НЕ удаляются при выключении режима: это только UI-гейтинг + серверный 403; при повторном включении всё видно.
  • pnpm run build — ✓ (2.60s, 0 TS errors).