diff --git a/docs/simple-mode-progress.md b/docs/simple-mode-progress.md new file mode 100644 index 0000000..bd1a579 --- /dev/null +++ b/docs/simple-mode-progress.md @@ -0,0 +1,55 @@ +# Simple Mode — прогресс + +Задача: по умолчанию у всех организаций «Простой режим» — только базовые +функции розничного учёта. Включить «Расширенный режим» может только +SuperAdmin и только для конкретной организации. Данные при переключении +не удаляются; при повторном включении всё видно. + +Старт: 2026-10-04. + +## Чек-лист + +- [x] 1. Domain + миграция `Organization.IsAdvancedMode` (bool, default false, backfill false) +- [ ] 2. API `PUT /api/superadmin/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 — старые, не наши). diff --git a/src/food-market.domain/Organizations/Organization.cs b/src/food-market.domain/Organizations/Organization.cs index 57f76a8..3ea8b2c 100644 --- a/src/food-market.domain/Organizations/Organization.cs +++ b/src/food-market.domain/Organizations/Organization.cs @@ -87,6 +87,16 @@ public class Organization : Entity /// просто захламляет карточку. public bool ShowDescriptionOnProduct { get; set; } + /// Расширенный режим для организации. По умолчанию false + /// — виден только «простой» набор функций (базовый розничный учёт). + /// Включить может только SuperAdmin через + /// PUT /api/superadmin/organizations/{id}/advanced-mode; в + /// настройках магазина переключателя нет. При выключении данные + /// скрытых модулей не удаляются; при повторном включении всё видно. + /// Серверная защита — атрибут [RequiresAdvancedMode] на + /// контроллерах скрытых модулей. + public bool IsAdvancedMode { get; set; } + // ─ Sprint 11: ОФД (фискализация чеков) ──────────────────────────── // Каждая организация выбирает СВОЕГО ОФД-оператора. Креды per-tenant: // у одной фирмы может быть Webkassa, у другой — Касса24. ApiKey/Secret diff --git a/src/food-market.infrastructure/Persistence/AppDbContext.cs b/src/food-market.infrastructure/Persistence/AppDbContext.cs index 9045738..426eb65 100644 --- a/src/food-market.infrastructure/Persistence/AppDbContext.cs +++ b/src/food-market.infrastructure/Persistence/AppDbContext.cs @@ -111,6 +111,10 @@ protected override void OnModelCreating(ModelBuilder builder) b.Property(o => o.CountryCode).HasMaxLength(2).IsRequired(); b.Property(o => o.Bin).HasMaxLength(20); b.Property(o => o.MoySkladToken).HasMaxLength(200); + // Simple/Advanced mode: default false (простой режим). Миграция + // Phase29a_IsAdvancedMode добавляет колонку, backfilling всех + // существующих организаций в false. + b.Property(o => o.IsAdvancedMode).HasDefaultValue(false); b.HasOne(o => o.DefaultCurrency).WithMany().HasForeignKey(o => o.DefaultCurrencyId).OnDelete(DeleteBehavior.Restrict); b.HasIndex(o => o.Name); b.HasIndex(o => o.IsArchived); diff --git a/src/food-market.infrastructure/Persistence/Migrations/20261004100000_Phase29a_IsAdvancedMode.cs b/src/food-market.infrastructure/Persistence/Migrations/20261004100000_Phase29a_IsAdvancedMode.cs new file mode 100644 index 0000000..2b7518c --- /dev/null +++ b/src/food-market.infrastructure/Persistence/Migrations/20261004100000_Phase29a_IsAdvancedMode.cs @@ -0,0 +1,31 @@ +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using foodmarket.Infrastructure.Persistence; + +#nullable disable + +namespace foodmarket.Infrastructure.Persistence.Migrations +{ + /// Phase29a — колонка IsAdvancedMode на организациях. + /// Default false → все существующие и новые организации стартуют + /// в «простом» режиме, SuperAdmin переключает вручную через API. + /// Идемпотентно (ADD COLUMN IF NOT EXISTS) и без backfilling — default + /// в CREATE/ALTER COLUMN уже выставляет значение всем строкам. + [DbContext(typeof(AppDbContext))] + [Migration("20261004100000_Phase29a_IsAdvancedMode")] + public partial class Phase29a_IsAdvancedMode : Migration + { + protected override void Up(MigrationBuilder b) + { + b.Sql(@" + ALTER TABLE public.organizations + ADD COLUMN IF NOT EXISTS ""IsAdvancedMode"" boolean NOT NULL DEFAULT false; + "); + } + + protected override void Down(MigrationBuilder b) + { + b.Sql(@"ALTER TABLE public.organizations DROP COLUMN IF EXISTS ""IsAdvancedMode"";"); + } + } +} diff --git a/src/food-market.infrastructure/Persistence/Migrations/AppDbContextModelSnapshot.cs b/src/food-market.infrastructure/Persistence/Migrations/AppDbContextModelSnapshot.cs index 48178e1..8ddd768 100644 --- a/src/food-market.infrastructure/Persistence/Migrations/AppDbContextModelSnapshot.cs +++ b/src/food-market.infrastructure/Persistence/Migrations/AppDbContextModelSnapshot.cs @@ -1089,6 +1089,10 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("IsArchived") .HasColumnType("boolean"); + b.Property("IsAdvancedMode") + .HasColumnType("boolean") + .HasDefaultValue(false); + b.Property("ArchivedAt") .HasColumnType("timestamp with time zone");