- 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>
7.3 KiB
7.3 KiB
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: ActionTypeEnableAdvancedMode/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/LoyaltyProgramsControllerPromotions/PromotionsControllerReports/AbcReportControllerAdmin/OrgAuditLogControllerTelegram/TelegramBindingControllerCommon/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).