Commit graph

4 commits

Author SHA1 Message Date
nns 9dc8f6e044 fix(inventory): записывать автора движения (CauseUserId) для всех документов
CauseUserId в уведомлениях о минусах был null: StockService писал автора только
если вызывающий явно передал CreatedByUserId в черновике, а большинство
документов (касса/списание/перемещение/приёмка/…) его не передавали. Теперь
автор берётся из черновика, иначе — из текущего пользователя (ITenantContext.UserId)
в единой точке ApplyMovementAsync, поэтому пишется и в StockMovement, и в алерт
для всех типов документов. Тест: минус от списания → causeUserId = текущий юзер.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-09 09:52:33 +05:00
nns 560d52e8cd feat(notifications): Web Push (VAPID) — браузерный push о минусах
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
Канал Web Push для центра уведомлений (item 5). Ключи VAPID генерируются один раз
и хранятся в platform_settings (без ручной настройки секретов); публичный отдаётся
фронту. Таблица web_push_subscriptions (Phase42) + /api/web-push/{public-key,
subscribe,unsubscribe}. Отправка через web-push-csharp из фонового джоба минусов
(best-effort, протухшие подписки чистятся). SW: обработчики push/notificationclick
(am-v3→am-v4), клик открывает нужный путь. Кнопка «Вкл. push» в колокольчике.
Работает в т.ч. в iOS Safari для PWA на домашнем экране.

Тесты: integration — публичный ключ стабилен, подписка идемпотентна, 400/unsubscribe
(4/4 группы); unit 299/299.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-09 03:49:32 +05:00
nns 29a951a6ff feat(notifications): центр уведомлений + агрегация/эскалация минусов + каналы
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
Персистентные уведомления (таблица notifications, Phase41) + REST
/api/notifications (список/счётчик/read/read-all) → центр уведомлений в вебе
(колокольчик, API-backed) и мобильном API.

Фоновый джоб NegativeStockNotificationJob (cron */5): агрегирует новые минусы
в ОДНО уведомление на склад (дебаунс по NotifiedAt + интервал орг., по умолч.
15 мин), уважает тихие часы (Asia/Almaty); эскалация просроченных (>N часов,
по умолч. 24) владельцу/админам. Получатели — админы, владелец, кладовщики
(право InventoryEdit). Каналы: SignalR (живой колокольчик), Telegram (владельцу),
push-заглушка.

Настройки организации: NegStockNotify{Enabled,IntervalMinutes,QuietFrom/ToHour,
EscalateHours} + GET/PUT /api/organization/settings + секция в настройках веба.
Блок «Просроченные минусы» на дашборде.

Тесты: unit тихих часов 3/3; integration — джоб создаёт уведомление админу,
дебаунс, read-all 3/3 (вся группа NegativeStockTests зелёная).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-09 02:56:59 +05:00
nns 9ba1edb236 feat(stock): разрешение продажи в минус + предупреждения об отрицательных остатках
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
Решение владельца 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