docs(s29): simple-mode user guide + стейдж задеплоен + e2e 7/7 ✓ (шаг 8/8)
- deploy-stage.sh → stage поднят с новым кодом; /health/ready зелёный.
- Smoke: /api/me на стейдже содержит advancedMode.
- E2E stage-simple-mode — 7/7 passed, включая Playwright-шаг
«Недоступно в простом режиме».
- docs/simple-mode.md: пользовательская документация с таблицей
простого режима, перечнем расширенных модулей, инструкцией для
SuperAdmin (API + UI), ProblemDetails-примером и ссылкой на тесты.
- tests/e2e/scenarios/stage-simple-mode.yml: заквочен title с «:» для
js-yaml. steps.ts: правильный ключ localStorage SPA ('fm.access_token').
docs/simple-mode-progress.md — все 8 пунктов [x], итог.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
7f8750f30f
commit
8572875e7f
|
|
@ -16,7 +16,7 @@ SuperAdmin и только для конкретной организации.
|
|||
- [x] 5. Упрощённые формы (товар / контрагент / документы) — скрыть «не нужные» поля, значения сохраняются
|
||||
- [x] 6. SuperAdmin UI — бейдж «Простой/Расширенный» + переключатель с подтверждением
|
||||
- [x] 7. Тесты (unit + integration + e2e); патч quality-watchdog при необходимости
|
||||
- [ ] 8. Деплой стейджа + `docs/simple-mode.md`
|
||||
- [x] 8. Деплой стейджа + `docs/simple-mode.md`
|
||||
|
||||
## Простой режим — что видно
|
||||
|
||||
|
|
@ -179,3 +179,18 @@ SuperAdmin и только для конкретной организации.
|
|||
- `sm07_ui_shows_not_available_page` — Playwright: простой админ на /sales/demands видит текст «Недоступно в простом режиме»
|
||||
|
||||
**Quality-watchdog:** проверка показала, что smoke дёргает только `GET /api/catalog/price-types?pageSize=10` (не гейтится — только мутации). Патч смока **не требуется**.
|
||||
|
||||
### 2026-10-04 — Шаг 8 ✓
|
||||
|
||||
- `git push origin main` (Forgejo).
|
||||
- `~/deploy-stage.sh` — build+push api/web, redeploy на prod-vm → `/health/ready` зелёный.
|
||||
- Smoke на `https://test.admin.food-market.kz`:
|
||||
- `/api/me` для SuperAdmin содержит `"advancedMode": false` ✓
|
||||
- E2E сценарий `stage-simple-mode` — **7/7 passed** (включая Playwright-шаг «Недоступно в простом режиме»).
|
||||
- `docs/simple-mode.md` создан: пользовательская документация с таблицей простого режима, перечнем модулей расширенного, инструкцией для SuperAdmin (API + UI), текстом ProblemDetails и ссылкой на тесты.
|
||||
|
||||
## Итог
|
||||
|
||||
**8/8 пунктов чек-листа ✓.** Код в main (dc885f3..). Стейдж обновлён, тесты зелёные (unit 6/6, integration 7/7, e2e 7/7). Данные скрытых модулей сохраняются при переключении режима — это проверено явно в integration и e2e («пресет виден после on→off→on»).
|
||||
|
||||
Не трогал (по правилам задачи): прод `admin.food-market.kz`, `global.json`, POS WPF, gateway nginx. Новые функции (кассовые смены, оплаты, ценники, весы, `Counterparty.IsSupplier/IsBuyer`-галочки) **не добавлял** — следующий этап.
|
||||
|
|
|
|||
154
docs/simple-mode.md
Normal file
154
docs/simple-mode.md
Normal file
|
|
@ -0,0 +1,154 @@
|
|||
# Simple / Advanced mode
|
||||
|
||||
Sprint 29, 2026-10-04.
|
||||
|
||||
Food Market по умолчанию работает в «простом» режиме — виден только
|
||||
минимальный набор функций розничного учёта, достаточный для
|
||||
обычного магазина у дома. «Расширенный» режим включает дополнительные
|
||||
модули (оптовые отгрузки, перемещения, лояльность и т.д.) и доступен
|
||||
только после явного включения оператором платформы (SuperAdmin).
|
||||
|
||||
Переключатель в настройках магазина отсутствует намеренно —
|
||||
администратор организации не может ни включить, ни выключить
|
||||
расширенный режим себе сам.
|
||||
|
||||
## Простой режим — что видно
|
||||
|
||||
| Раздел | Что внутри |
|
||||
|---|---|
|
||||
| **Главная** | KPI «Выручка сегодня / неделя / месяц», средний чек, график продаж, «Последние продажи», «Низкий остаток». |
|
||||
| **Товары** | Карточка товара без артикула (генерируется автоматически), описания, нескольких типов цен, страны происхождения, НДС-галок, «Услуги», «Маркируемый», максимального остатка. Остаются: Название, Штрихкоды, Группа, Единица измерения, Цена продажи (одна), Себестоимость (только чтение), Минимальный остаток, Фасовка (шт/вес/разливной), Фото. |
|
||||
| **Группы товаров** | Полный доступ. |
|
||||
| **Единицы измерения** | Полный доступ. |
|
||||
| **Контрагенты** | Единый список. Поля: Название, Тип лица, БИН/ИИН, Адрес, Телефон, Заметки. |
|
||||
| **Склад** | Приёмка, Оприходование, Списание, Инвентаризация, Возврат поставщику, Остатки. |
|
||||
| **Продажи** | Продажи (POS), Возвраты покупателей. |
|
||||
| **Отчёты** | Продажи, Прибыль, Остатки на дату. |
|
||||
| **Настройки** | Магазин, Склады, Кассы (RetailPoints), Сотрудники, Роли, ОФД, Импорт товаров (CSV / 1С / МойСклад). |
|
||||
| **Профиль** | Смена пароля, 2FA. |
|
||||
|
||||
Весь интерфейс по-русски (переключателя языка нет).
|
||||
|
||||
## Расширенный режим — что добавляется
|
||||
|
||||
- Оптовые отгрузки (`Demand`)
|
||||
- Перемещения между точками (`Transfer`)
|
||||
- Лояльность — программы и карты
|
||||
- Промокоды / акции
|
||||
- ABC-анализ
|
||||
- Аудит организации (`/audit-log`)
|
||||
- Несколько типов цен (не только retail)
|
||||
- Bulk-операции в таблицах + сохранённые пресеты фильтров
|
||||
- Telegram-бот владельца (ежедневная сводка)
|
||||
- SSO (вход через Google / Microsoft)
|
||||
- Выбор языка
|
||||
- Виджеты «Топ товаров» и «Маржа» на дашборде
|
||||
|
||||
На карточках/документах появляются дополнительные поля из `Organization`
|
||||
(«Описание», «Эталонная цена», «Валюта закупки», «НДС», «Услуга»,
|
||||
«Маркируемый», «Макс. остаток»), которые управляются существующими
|
||||
org-level `Show*` флагами.
|
||||
|
||||
## Как SuperAdmin включает / выключает
|
||||
|
||||
### Через API
|
||||
|
||||
```http
|
||||
PUT /api/super-admin/organizations/{id}/advanced-mode
|
||||
Authorization: Bearer <token>
|
||||
Content-Type: application/json
|
||||
|
||||
{ "enabled": true }
|
||||
```
|
||||
|
||||
Ответ `200 OK`:
|
||||
|
||||
```json
|
||||
{ "id": "...", "isAdvancedMode": true, "changed": true }
|
||||
```
|
||||
|
||||
Повторный вызов с тем же состоянием → `changed: false` и запись в
|
||||
аудит **не** делается (идемпотентность).
|
||||
|
||||
Переключение архивной организации → `409 Conflict`.
|
||||
Эндпоинт разрешён только роли `SuperAdmin`; админ орги получает `403`.
|
||||
|
||||
### Через SuperAdmin UI
|
||||
|
||||
1. Открыть `/super-admin/organizations`.
|
||||
2. В списке у каждой строки колонка **«Режим»** — бейдж «Простой»
|
||||
(slate) или «Расширенный» (violet).
|
||||
3. В экшн-колонке рядом с «Архивировать» — иконка-искра. Клик →
|
||||
модалка подтверждения с описанием последствий.
|
||||
4. Подтверждение → `PUT` на эндпоинт выше. После успеха список
|
||||
автоматически перегружается.
|
||||
|
||||
### Запись в журнал
|
||||
|
||||
Каждое реальное переключение пишется в `super_admin_audit_log`:
|
||||
|
||||
```json
|
||||
{
|
||||
"actionType": "EnableAdvancedMode", // или DisableAdvancedMode
|
||||
"organizationId": "...",
|
||||
"description": "Включен расширенный режим «Org Name»",
|
||||
"changesJson": { "before": false, "after": true },
|
||||
"ipAddress": "...",
|
||||
"superAdminUserId": "..."
|
||||
}
|
||||
```
|
||||
|
||||
Просмотр: `/super-admin/audit-log?organizationId=...` или UI
|
||||
«SuperAdmin → Журнал».
|
||||
|
||||
## Что происходит с данными при выключении
|
||||
|
||||
**Ничего не удаляется.** Серверный фильтр
|
||||
`[RequiresAdvancedMode]` возвращает `403 ProblemDetails` на скрытых
|
||||
эндпоинтах; данные остаются в БД. При повторном включении всё снова
|
||||
видно и доступно без миграций и восстановлений.
|
||||
|
||||
Пример `ProblemDetails` при 403:
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "https://food-market.kz/errors/advanced-mode-required",
|
||||
"title": "Функция доступна в расширенном режиме.",
|
||||
"detail": "Этот раздел недоступен в простом режиме магазина. Обратитесь к администратору платформы, чтобы включить расширенный режим.",
|
||||
"status": 403,
|
||||
"instance": "/api/sales/demands",
|
||||
"code": "advanced-mode-required"
|
||||
}
|
||||
```
|
||||
|
||||
Для UI это достаточный маркер, чтобы отличить этот 403 от обычного
|
||||
«нет прав» и отрисовать страницу «Недоступно в простом режиме» вместо
|
||||
общего сообщения.
|
||||
|
||||
## Как это устроено на бэкенде (кратко)
|
||||
|
||||
- `Organization.IsAdvancedMode` (bool, default `false`) — миграция
|
||||
`20261004100000_Phase29a_IsAdvancedMode`.
|
||||
- `[RequiresAdvancedMode]` — атрибут MVC
|
||||
(`TypeFilterAttribute` → `AdvancedModeFilter : IAsyncAuthorizationFilter`),
|
||||
читает флаг по `org_id`-claim через `AppDbContext`.
|
||||
SuperAdmin bypass; аноним пропускается (отработает `[Authorize]`).
|
||||
Кэш in-memory 30 с, инвалидация после переключения.
|
||||
- Список скрытых контроллеров / действий —
|
||||
см. `docs/simple-mode-progress.md` (шаг 3).
|
||||
- `/api/me` расширен полем `advancedMode: boolean`.
|
||||
|
||||
Веб:
|
||||
- `useMe()` отдаёт `advancedMode`.
|
||||
- `AppLayout.buildNav(roles, advancedMode)` фильтрует меню.
|
||||
- `AdvancedModeGuard` оборачивает скрытые маршруты и показывает
|
||||
«Недоступно в простом режиме» вместо белого экрана / 403 в консоли.
|
||||
- Карточка товара, контрагенты и приёмка в простом режиме прячут
|
||||
advanced-поля; значения существующих записей сохраняются и при
|
||||
переключении режима снова видны.
|
||||
|
||||
## Тесты
|
||||
|
||||
- Unit: `tests/food-market.UnitTests/AdvancedModeFilterTests.cs` (6/6).
|
||||
- Integration: `tests/food-market.IntegrationTests/AdvancedModeTests.cs` (7/7).
|
||||
- E2E: `tests/e2e/scenarios/stage-simple-mode.yml` (7/7 на стейдже).
|
||||
|
|
@ -235,10 +235,12 @@ export async function sm07_ui_shows_not_available_page({ ctx, step }: StepCtx) {
|
|||
const ctxBrowser = await browser.newContext({ ignoreHTTPSErrors: true })
|
||||
const page = await ctxBrowser.newPage()
|
||||
|
||||
// Прокидываем access-token через localStorage, чтобы миновать форму логина.
|
||||
// Прокидываем access-token через localStorage под реальным ключом SPA
|
||||
// (см. src/food-market.web/src/lib/auth.ts — ACCESS = 'fm.access_token'),
|
||||
// чтобы миновать форму логина.
|
||||
await page.goto(ADMIN_BASE + '/login')
|
||||
await page.evaluate((tok) => {
|
||||
localStorage.setItem('accessToken', tok)
|
||||
localStorage.setItem('fm.access_token', tok)
|
||||
}, org.token)
|
||||
|
||||
await page.goto(ADMIN_BASE + '/sales/demands')
|
||||
|
|
|
|||
|
|
@ -35,4 +35,4 @@ steps:
|
|||
- id: sm06_archived_org_cannot_toggle
|
||||
title: Архивную org переключать нельзя (409)
|
||||
- id: sm07_ui_shows_not_available_page
|
||||
title: UI: простой админ на /sales/demands видит страницу «Недоступно»
|
||||
title: "UI: простой админ на /sales/demands видит страницу «Недоступно»"
|
||||
|
|
|
|||
Loading…
Reference in a new issue