From 2d1cdd85194e10acfa03af8bd6acc2045fa18e4c Mon Sep 17 00:00:00 2001 From: nns Date: Fri, 9 Oct 2026 02:19:37 +0500 Subject: [PATCH] =?UTF-8?q?docs(stock):=20=D0=BF=D1=80=D0=BE=D0=B3=D1=80?= =?UTF-8?q?=D0=B5=D1=81=D1=81=20negative-stock=20=E2=80=94=20=D1=87=D1=82?= =?UTF-8?q?=D0=BE=20=D1=81=D0=B4=D0=B5=D0=BB=D0=B0=D0=BD=D0=BE/=D0=BE?= =?UTF-8?q?=D1=82=D0=BB=D0=BE=D0=B6=D0=B5=D0=BD=D0=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 --- docs/negative-stock-alerts-progress.md | 61 ++++++++++++++++---------- 1 file changed, 39 insertions(+), 22 deletions(-) diff --git a/docs/negative-stock-alerts-progress.md b/docs/negative-stock-alerts-progress.md index f9f6034..80d31dc 100644 --- a/docs/negative-stock-alerts-progress.md +++ b/docs/negative-stock-alerts-progress.md @@ -25,28 +25,45 @@ инвентаризации по минусовым товарам), с авто-закрытием при возврате остатка ≥ 0 и эскалацией. (Источник по МойСкладу: dev.moysklad.ru/doc/api/remap/1.2 report/stock stockMode; webhooks — на сущности.) -## План / статус -0. [ ] Сверка с МойСкладом (отчёт/вебхуки/разрешение минуса). -1. [ ] Событие при проведении документа → остаток < 0: создать/обновить предупреждение - (товар, склад, остаток, документ-причина). Остаток ≥ 0 → закрыть автоматически. -2. [ ] Подсказка причины: есть непроведённая приёмка/черновик → «возможно, не проведена приёмка П-…». -3. [ ] Без спама: агрегация (1 уведомление/склад/N мин, по умолч. 15), тихие часы, получатели - Администратор + Кладовщики склада, вкл/выкл в настройках орг и профиле. -4. [ ] Действие «Пересчитать эти товары»: черновик инвентаризации по складу с этими товарами. -5. [ ] Каналы: веб (колокольчик + центр уведомлений + Web Push/VAPID), Telegram (бот), - мобильное API (GET /api/notifications, read, создать инвентаризацию), отчёт «Отрицательные». - APNs — только заглушка (device tokens + IPushSender), без платного Apple Dev. -6. [ ] Тесты unit/integration/e2e; деплой; скриншоты. -7. [ ] Снять блокировку продажи в минус: касса/POS-синк/офлайн, розница веб, опт, списание, - перемещение — проведение в минус РАЗРЕШЕНО (убрать 409 oversell; деньги — счёт в минус запрещён). -8. [ ] Касса: предупреждение в строке («на остатке 2 шт — уйдёт в минус»), жёлтый индикатор в шапке - «N товаров в минусе», офлайн по локальным остаткам. -9. [ ] Админка: постоянный баннер «N товаров в минусе — проведите инвентаризацию» + «Пересчитать»; - красная пометка в списках товаров/остатков. -10. [ ] Мобильное API/iOS: счётчик + список в /api/me или /api/notifications. -11. [ ] Эскалация: минус не закрыт за N часов (по умолч. 24) → повторное уведомление владельцу/админу; - на главной — «Просроченные минусы». -12. [~] ОТЛОЖЕНО (поправка владельца) — предварительная себестоимость НЕ делаем. +## План / статус (легенда: [x] готово · [~] частично/заложено · [ ] отложено) +0. [x] Сверка с МойСкладом (см. выше): у них — только фильтр отчёта `stockMode=negativeOnly`, push/события нет. +1. [x] Событие при проведении документа → остаток < 0: `NegativeStockAlert` (товар, склад, остаток, + документ-причина: тип/номер/автор/время), единая точка — `StockService.ApplyMovementAsync`. + Остаток ≥ 0 → закрывается автоматически. Миграция Phase39, частичный уникальный индекс. +2. [x] Подсказка причины: API списка отдаёт `hint` «возможно, не проведена приёмка П-… от <поставщик>» + (если по товару есть черновик приёмки). +3. [~] Без спама: счётчик/баннер — реалтайм-запрос (poll 60с), не спамит. Поля `NotifiedAt`/`EscalatedAt` + в сущности заложены под фоновую агрегацию/рассылку. **Отложено:** фоновая job (email/telegram + одним сообщением на склад, тихие часы, получатели по ролям/привязке, тумблеры в настройках). +4. [x] «Пересчитать эти товары»: `POST /api/negative-stock/recount` → черновик инвентаризации по складу + ровно с минусовыми товарами (BookQty = текущий отрицательный остаток), открывается из баннера. +5. [~] Каналы: **веб** — постоянный баннер + фильтр/пометка в остатках ✓; отчёт «Отрицательные» (фильтр + `negativeOnly`) ✓. **Мобильное API** — `GET /api/negative-stock` + `/count` готовы (iOS читает) ✓. + **Отложено:** центр уведомлений (realtime-событие NegativeStock), Telegram-рассылка, Web Push + (VAPID + SW), APNs-заглушка (device tokens + IPushSender). +6. [~] Тесты: integration **2/2** (минус разрешён → алерт → recount → авто-закрытие; фильтр negativeOnly) ✓; + full-chain шаг 9/13 переписаны (oversell/отмена приёмки → проведено + предупреждение) ✓. + **Отложено:** unit на агрегацию/дебаунс (нет job), e2e в браузере на баннер. Деплой/скрины — ниже. +7. [x] Снята блокировка продажи в минус: розница, касса (POS-синк), опт, списание, перемещение, + возврат поставщику, отмена приёмки. Деньги (минус счёта) — не трогали (другое правило). +8. [ ] **Отложено** (десктопная Avalonia-касса): предупреждение в строке + жёлтый индикатор в шапке + + офлайн по локальным остаткам. (Отдельная поверхность; iOS/касса — Mac-Claude.) +9. [x] Админка: постоянный баннер «N товаров в минусе — нужна инвентаризация» (сворачивается, крестиком + не закрыть) + «Пересчитать эти товары»; в остатках — красная пометка + фильтр «Только минусы». +10. [~] Мобильное API/iOS: `/api/negative-stock/count` + список готовы. Баннер в приложении — Mac-Claude. +11. [~] Эскалация: в API есть флаг `overdue` (минус висит дольше N часов, по умолч. 24). **Отложено:** + фоновое повторное уведомление владельцу/админу + блок «Просроченные минусы» на главной. +12. [ ] ОТЛОЖЕНО (поправка владельца 09.10) — предварительная себестоимость НЕ делаем. + +## Что сделано в этой сессии (ядро, оттестировано и выложено) +Разрешение минуса + детекция/авто-закрытие предупреждений + API (список/счётчик/recount/фильтр) + +веб-баннер и пометки в остатках + integration-тесты 2/2 + full-chain 9/13. Деплой на стенд. + +## Что отложено (заложен фундамент, нужен отдельный проход) +Фоновая рассылка (email/telegram, агрегация/тихие часы/получатели/настройки), эскалация-job и +«просроченные минусы», центр уведомлений (realtime-событие), Web Push (VAPID), APNs-заглушка +(device tokens + IPushSender), индикаторы в десктопной Avalonia-кассе (в т.ч. офлайн). +**DONE не ставим — не все пункты закрыты.** ## Что НЕ трогаем Прод, shared-postgres, global.json, gateway nginx, sklad. Расчёт себестоимости (п.12 отложен).