# Simple Mode — прогресс Задача: по умолчанию у всех организаций «Простой режим» — только базовые функции розничного учёта. Включить «Расширенный режим» может только SuperAdmin и только для конкретной организации. Данные при переключении не удаляются; при повторном включении всё видно. Старт: 2026-10-04. ## Чек-лист - [x] 1. Domain + миграция `Organization.IsAdvancedMode` (bool, default false, backfill false) - [x] 2. API `PUT /api/super-admin/organizations/{id}/advanced-mode` + аудит + `/api/me.advancedMode`; org-admin → 403 - [x] 3. Серверная защита: `[RequiresAdvancedMode]` → 403 ProblemDetails на скрытых модулях - [x] 4. Web: меню, маршруты, дашборд фильтруются; прямой переход → страница «Недоступно» - [x] 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` — скрытые маршруты обёрнуты в ``: - `/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). ### 2026-10-04 — Шаг 5 ✓ - Везде формула простого режима: `simpleMode = me && !me.advancedMode && !roles.includes('SuperAdmin')`. SuperAdmin не ощущает — ему доступно всё. - **Карточка товара** (`ProductEditPage.tsx`) в простом режиме прячет (значения в state сохраняются, при save не стираются): - «Артикул» (генерируется автоматически, Название на 3 колонки) - «Описание», «Страна происхождения», «Эталонная цена» - «Ставка НДС», чекбокс «В том числе НДС» - «Валюта закупки» (мульти-валюта) - Чекбоксы «Услуга», «Маркируемый» - «Максимальный остаток» — остаётся только «Минимальный» - Все не-retail типы цен в блоке «Цены продажи» — рендерится только retail/system price type. - **Контрагенты** (`CounterpartiesPage.tsx`) — в простом режиме скрыты Юр. название, Страна, Email, Банк/Р.счёт/БИК, Контактное лицо. Остаются: Название, Тип лица, БИН/ИИН, Адрес, Телефон, Заметки. - **Приёмка** (`SupplyEditPage.tsx`) — колонка «Валюта» всегда скрыта в простом режиме (одна валюта по умолчанию). - Другие документы (Enter, Loss, Inventory, SupplierReturn) используют те же `org.ShowXxx` / `org.MultiCurrencyEnabled` флаги — уже скрывают advanced-поля для магазинов с «простым» org-настройками. Доп. список спрятанного за `simpleMode` (из шагов 4/5, не вошёл напрямую в чек-лист): - `AppLayout` → `LanguageSwitcher` - `DashboardPage` → виджеты «Топ товаров» и «Маржа» Что отложено (новая функциональность, в этой задаче не трогаем): - `Counterparty.IsSupplier` / `IsBuyer` — задача просит галочки «Поставщик/Покупатель», но на `Counterparty` таких bool-колонок нет. Это новая миграция и расширение API — отдельная задача. - `pnpm run build` — ✓ (2.36s, 0 TS errors).