food-market/docs/negative-stock-alerts-progress.md
nns 9ba1edb236
Some checks are pending
Auto-tag / Create date-tag (push) Waiting to run
CI / Backend (.NET 8) (push) Waiting to run
CI / Web (React + Vite) (push) Waiting to run
CI / POS (WPF, Windows) (push) Waiting to run
Docker API / Build + push API (push) Waiting to run
Docker API / Deploy API on stage (push) Blocked by required conditions
Docker Web / Build + push Web (push) Waiting to run
Docker Web / Deploy Web on stage (push) Blocked by required conditions
feat(stock): разрешение продажи в минус + предупреждения об отрицательных остатках
Решение владельца 08.10: продавать/списывать в минус РАЗРЕШЕНО, но с постоянным
предупреждением и требованием провести инвентаризацию.

- Снята блокировка «недостаточно остатка» (409 oversell): розница, касса (POS-синк),
  опт-отгрузка, списание, перемещение, возврат поставщику, отмена приёмки. Деньги
  (минус счёта) — по-прежнему запрещены (другое правило).
- NegativeStockAlert (сущность + миграция Phase39): создаётся/обновляется при уходе
  остатка < 0 (единая точка — StockService), авто-закрытие при возврате ≥ 0. Частичный
  уникальный индекс: один открытый алерт на (org, store, product). Причина: тип/номер
  документа/автор/время.
- API /api/negative-stock: список (с подсказкой «возможно, не проведена приёмка П-…»),
  /count (для баннеров), /recount (черновик инвентаризации по минусам склада). Отчёт
  «Остатки» — фильтр negativeOnly.
- Веб: постоянный баннер «N товаров в минусе — нужна инвентаризация» (сворачивается,
  крестиком не закрыть) + «Пересчитать эти товары»; в остатках — красная пометка и
  фильтр «Только минусы».
- Тесты: integration 2/2 (минус разрешён → алерт → recount → авто-закрытие; фильтр);
  full-chain шаг 9/13 переписаны (oversell/отмена приёмки → проведено + предупреждение).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 02:18:29 +05:00

56 lines
6.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Уведомления об отрицательных остатках → инвентаризация
Идея владельца 08.10.2026: «когда у товара количество становится отрицательным, система и
мобильное приложение должны отправлять PUSH о том, что необходимо произвести инвентаризацию
по таким товарам, и сразу предоставить список таких товаров».
Решение владельца 08.10: **разрешать продажу в минус нужно**, но с постоянным предупреждением
везде (касса, админка, приложение) и требованием срочно провести инвентаризацию.
Поправка 09.10: **пункт 12 (предварительная себестоимость) НЕ делаем** — тема отложена;
себестоимость продаж в минус оставляем как сейчас, расчёт не трогаем.
## Сверка с МойСкладом (что есть у них)
- **Отчёт «Остатки»** (`report/stock`) с параметром `stockMode`: значения `all`, `positiveOnly`,
`negativeOnly`, `underminimum`, `nonEmpty`, `empty`. Т.е. отрицательные остатки — это **только
фильтр в отчёте** (`stockMode=negativeOnly`). Отдельного «уведомления» нет.
- **Вебхуки** МойСклада — на CRUD-события сущностей (создание/изменение/удаление документов и
справочников). Порога «остаток ушёл в минус» среди событий **нет** — вебхук на изменение
документа есть, но «стало отрицательно» как события нет.
- Минус **разрешён** настройкой («разрешить отрицательные остатки»); push/требования пересчёта нет.
**Вывод:** у МойСклада — пассивный отчёт-фильтр. Мы делаем **лучше**: проактивные предупреждения
(событие при уходе в минус), постоянные баннеры/индикаторы в кассе/админке/приложении, push
(web/telegram, APNs-заглушка), подсказка причины, и действие «Пересчитать» (авто-черновик
инвентаризации по минусовым товарам), с авто-закрытием при возврате остатка ≥ 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. [~] ОТЛОЖЕНО (поправка владельца) — предварительная себестоимость НЕ делаем.
## Что НЕ трогаем
Прод, shared-postgres, global.json, gateway nginx, sklad. Расчёт себестоимости (п.12 отложен).
## Журнал
- 09.10 — старт: прогресс-док, сверка с МойСкладом, обследование кода.