docs(stock): прогресс+mobile-api — все пункты 1–11 закрыты (п.12 отложен владельцем)
Some checks are pending
Auto-tag / Create date-tag (push) Waiting to run

Пасс 3: касса (индикатор+предупреждение+офлайн), Web Push (VAPID), e2e колокольчика.
Итог: unit 299/299, integration 4/4, POS 4/4, e2e-браузер 2/2.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
nns 2026-10-09 04:03:01 +05:00
parent ff42db7e97
commit 0dc8703712
2 changed files with 46 additions and 25 deletions

View file

@ -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=…` — отзыв.

View file

@ -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 отложен владельцем).