food-market/docs/simple-mode-progress.md
nns 921bca076c feat(s29): [RequiresAdvancedMode] + гейтинг скрытых модулей (шаг 3/8)
- RequiresAdvancedModeAttribute (TypeFilterAttribute → IAsyncAuthorizationFilter):
  читает Organization.IsAdvancedMode по org_id-claim через AppDbContext,
  кэш 30 с, инвалидация после переключения. SuperAdmin bypass. На отказ
  возвращает 403 ProblemDetails с code=advanced-mode-required.
- Атрибут навешен на контроллеры:
  Sales/DemandsController, Inventory/TransfersController,
  Loyalty/LoyaltyCardsController + LoyaltyProgramsController,
  Promotions/PromotionsController, Reports/AbcReportController,
  Admin/OrgAuditLogController, Telegram/TelegramBindingController,
  Common/UserPresetsController.
- И на отдельные действия:
  Catalog/PriceTypesController POST/PUT/DELETE (GET доступен — товары
  используют retail-прайс),
  Catalog/ProductsController POST /bulk-update.
- SuperAdmin toggle (шаг 2) инвалидирует кэш фильтра после SaveChangesAsync.
- SSO (Auth/ExternalAuthController) гейтится UI, а не атрибутом: endpoint
  инициируется анонимом без org_id в токене.

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

7.3 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).