diff --git a/docs/mobile-api.md b/docs/mobile-api.md index b469ab5..f6532f7 100644 --- a/docs/mobile-api.md +++ b/docs/mobile-api.md @@ -138,6 +138,15 @@ deprecated после обновления iOS (iOS обновит Mac-Claude). (204) — все. - Создать инвентаризацию по предупреждениям — `POST /api/negative-stock/recount` `{ storeId }` (см. выше). +### Web Push (VAPID) — браузер/PWA +Для веб-админки и PWA (в т.ч. iOS Safari на домашнем экране). Ключи VAPID генерируются +на сервере и хранятся в platform_settings. +- `GET /api/web-push/public-key` → `{ publicKey }` — для `pushManager.subscribe`. +- `POST /api/web-push/subscribe` body `{ endpoint, keys: { p256dh, auth } }` (204) — идемпотентно. +- `POST /api/web-push/unsubscribe` body `{ endpoint }` (204). +Доставка — из фонового джоба минусов; payload JSON `{title, body, url}`, его разбирает +service worker (обработчики `push`/`notificationclick`). + ### Push-токены устройств (заложено; реальный APNs позже) - `POST /api/me/device-tokens` body `{ platform: "ios"|"android"|"web", token }` — регистрация токена (идемпотентно по токену). `DELETE /api/me/device-tokens?token=…` — отзыв. diff --git a/docs/negative-stock-alerts-progress.md b/docs/negative-stock-alerts-progress.md index d553950..1fe84d5 100644 --- a/docs/negative-stock-alerts-progress.md +++ b/docs/negative-stock-alerts-progress.md @@ -39,25 +39,29 @@ 4. [x] «Пересчитать эти товары»: `POST /api/negative-stock/recount` → черновик инвентаризации по складу ровно с минусовыми товарами (BookQty = текущий отрицательный остаток), открывается из баннера, центра уведомлений и блока «Просроченные минусы». -5. [~] Каналы: **веб** — постоянный баннер + фильтр/пометка в остатках ✓; **центр уведомлений** +5. [x] Каналы: **веб** — постоянный баннер + фильтр/пометка в остатках ✓; **центр уведомлений** (колокольчик, API-backed: `/api/notifications` список/счётчик/read/read-all, живой через SignalR - `NotificationCreated`) ✓; отчёт «Отрицательные» ✓. **Telegram** — рассылка владельцу org ✓. - **Мобильное API** — `GET /api/notifications` + `/count` + `POST …/read` + recount ✓; `GET - /api/negative-stock` + `/count` ✓. **APNs-заглушка** — `device_tokens` (Phase40) + `IPushSender`/ - `StubPushSender` ✓. **Отложено:** Web Push (VAPID + SW push-обработчик) — браузерный канал. -6. [~] Тесты: unit тихих часов **3/3** ✓ (вся юнит-сьют 297/297 — попутно починен SQLite-харнесс); - integration **3/3** (минус→алерт→recount→авто-закрытие; фильтр negativeOnly; **джоб → - уведомление админу + дебаунс + read-all**) ✓; **e2e-браузер** (`stage-ui-negative-stock.spec.ts`: - баннер + «Пересчитать» → черновик + фильтр) ✓; full-chain 9/13 ✓. Скрины стенда ✓ — баннер, - фильтр, центр уведомлений, настройки. **Отложено:** e2e-браузер именно на колокольчик (зависит - от cron-джоба; покрыт integration-тестом + скрином). + `NotificationCreated`) ✓; отчёт «Отрицательные» ✓. **Web Push (VAPID)** — ключи генерятся и хранятся + в platform_settings (Phase42), `/api/web-push/{public-key,subscribe,unsubscribe}`, SW-обработчики + push/notificationclick (am-v4), кнопка «Вкл. push» в колокольчике; работает в т.ч. iOS Safari PWA ✓. + **Telegram** — рассылка владельцу org ✓. **Мобильное API** — `/api/notifications` (+count/read) + + recount ✓; `GET /api/negative-stock` + `/count` ✓. **APNs-заглушка** — `device_tokens` (Phase40) + + `IPushSender`/`StubPushSender` ✓. Прим.: фактическую доставку Web Push в браузер нельзя проверить + headless (нужен живой push-сервис) — покрыт путь генерации ключа/подписки/отправки. +6. [x] Тесты: unit **3/3** тихих часов (вся юнит-сьют **299/299**, попутно починен SQLite-харнесс + + тесты POS-остатков); integration **4/4** (минус→алерт→recount→авто-закрытие; фильтр negativeOnly; + **джоб → уведомление админу + дебаунс + read-all**; **Web Push** ключ/подписка); POS **4/4** (снимок + остатков/перезапись/NegativeCount); **e2e-браузер 2/2** (баннер+«Пересчитать»+фильтр; **колокольчик** + с ожиданием cron-джоба) ✓; full-chain 9/13 ✓. Деплой на стенд + скрины: баннер, фильтр, центр + уведомлений, настройки, Push-тумблер, **касса (индикатор + предупреждение в строке)**. 7. [x] Снята блокировка продажи в минус: розница, касса (POS-синк), опт, списание, перемещение, возврат поставщику, отмена приёмки. Деньги (минус счёта) — не трогали (другое правило). -8. [ ] **Отложено** (десктопная Avalonia-касса): предупреждение в строке + жёлтый индикатор в шапке + - офлайн по локальным остаткам. Требует предварительного задела: POS-каталог (`PosProduct`/ - `ProductSyncDto`) сейчас **не синхронизирует остатки** — нет поля количества; офлайн-предупреждение - невозможно без добавления остатков в sync-протокол. Онлайн-индикатор мог бы поллить - `/api/negative-stock/count`. Отдельная поверхность (SDK-пин, headless-UI). +8. [x] Касса (Avalonia): при количестве больше остатка — предупреждение прямо в строке + «на остатке N шт — уйдёт в минус» (жёлтое, НЕ блокирует продажу); в шапке — постоянный + жёлтый индикатор «N в минусе — нужна инвентаризация» по складу кассы, по тапу — список (Flyout). + Работает **офлайн** по локальному снимку остатков: сервер уже слал остатки в sync (`StockSyncDto`), + касса теперь их хранит (таблица `stock`, перезапись каждый синк). Обновляется при старте/после + чека/после синка. Тесты PosStore 4/4, скрин кассы снят. 9. [x] Админка: постоянный баннер «N товаров в минусе — нужна инвентаризация» (сворачивается, крестиком не закрыть) + «Пересчитать эти товары»; в остатках — красная пометка + фильтр «Только минусы». 10. [x] Мобильное API/iOS: счётчик `/api/notifications/count` + список `/api/notifications` + `/api/ @@ -67,19 +71,22 @@ с «Пересчитать». Флаг `overdue` в API. 12. [ ] ОТЛОЖЕНО (поправка владельца 09.10) — предварительная себестоимость НЕ делаем. -## Что сделано (ядро + уведомления, оттестировано и выложено) +## Что сделано (всё в рамках задачи, оттестировано и выложено) Пасс 1: разрешение минуса + детекция/авто-закрытие + API (список/счётчик/recount/фильтр) + веб-баннер/ пометки + APNs-задел. Пасс 2: персистентный центр уведомлений (`notifications`, Phase41) + REST + фоновый джоб агрегации/эскалации (интервал/тихие часы/получатели по ролям) + Telegram + настройки орг. -+ блок «Просроченные минусы» + unit 3/3 + integration 3/3. Выложено на стенд, скрины сняты. ++ блок «Просроченные минусы». Пасс 3: **касса** (индикатор + предупреждение в строке + офлайн, Avalonia) + +**Web Push (VAPID)** (Phase42, SW push/notificationclick, кнопка «Вкл. push») + e2e-браузер колокольчика. +Итог: unit 299/299, integration 4/4, POS 4/4, e2e-браузер 2/2. Выложено на стенд, скрины сняты. -## Что отложено (нужен отдельный проход) -- **Web Push (VAPID)** — браузерный push-канал (ключи + SW push/notificationclick + подписка). -- **Avalonia-касса (п.8)** — индикатор/предупреждение; сначала нужен задел: остатки в POS-sync - (у `PosProduct` нет количества). Отдельная десктоп-поверхность. -- **Реальная отправка push** (APNs/FCM) — нужен платный Apple Developer; заглушка в DI готова. -- e2e-браузер на сам колокольчик (зависит от cron-джоба) — покрыт integration + скрином. -**DONE не ставим — остаются п.5 (Web Push VAPID) и п.8 (десктоп-касса).** +## Остаётся за рамками этой задачи +- **Пункт 12 (предварительная себестоимость)** — ОТЛОЖЕН владельцем (поправка 09.10), не делаем. +- **Реальная отправка APNs/FCM push на iOS** — нужен платный Apple Developer; задел (device_tokens + + IPushSender + StubPushSender) готов, подменяется реальной реализацией без правок вызова. +- Прим.: фактическую доставку Web Push в реальный браузер нельзя проверить в headless-CI (нужен живой + push-сервис) — реализация полная (web-push-csharp, RFC 8291), проверен путь ключ/подписка/отправка. + +**Все пункты 1–11 выполнены (п.12 отложен владельцем) → DONE.** ## Что НЕ трогаем Прод, shared-postgres, global.json, gateway nginx, sklad. Расчёт себестоимости (п.12 отложен). @@ -95,3 +102,8 @@ - 09.10 — задел push (3547e82): `device_tokens` (Phase40) + `IPushSender`/`StubPushSender` + `/api/me/device-tokens`, описано в `docs/mobile-api.md`. Выложено на стенд, Phase39+Phase40 применились (`/api/negative-stock/count` → 200). Скрины баннера + фильтра минусов сняты. +- 09.10 — пасс 3: **касса** (0fd04d4) — остатки в PosStore (таблица stock), предупреждение в строке + + жёлтый индикатор в шапке (офлайн), PosStore-тесты 4/4, скрин кассы. **Web Push VAPID** (560d52e) — + platform_settings-ключи (Phase42), `/api/web-push/*`, SW am-v4, кнопка «Вкл. push»; стенд: public-key + 87 симв., subscribe/unsubscribe 204. **e2e колокольчика** (ff42db7) — ждёт cron-джоб, 2/2 зелёные. + Все пункты 1–11 закрыты (п.12 отложен владельцем).