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>
59 lines
3.3 KiB
C#
59 lines
3.3 KiB
C#
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||
using Microsoft.EntityFrameworkCore.Migrations;
|
||
using foodmarket.Infrastructure.Persistence;
|
||
|
||
#nullable disable
|
||
|
||
namespace foodmarket.Infrastructure.Persistence.Migrations
|
||
{
|
||
/// <summary>Phase39 — предупреждения об отрицательных остатках
|
||
/// (<c>negative_stock_alerts</c>). Создаётся/обновляется автоматически при уходе
|
||
/// остатка в минус, закрывается при возврате ≥ 0. Частичный уникальный индекс
|
||
/// гарантирует максимум один ОТКРЫТЫЙ алерт на (org, store, product). Идемпотентно.</summary>
|
||
[DbContext(typeof(AppDbContext))]
|
||
[Migration("20261009010000_Phase39_NegativeStockAlerts")]
|
||
public partial class Phase39_NegativeStockAlerts : Migration
|
||
{
|
||
protected override void Up(MigrationBuilder b)
|
||
{
|
||
b.Sql(@"
|
||
CREATE TABLE IF NOT EXISTS public.negative_stock_alerts (
|
||
""Id"" uuid PRIMARY KEY,
|
||
""OrganizationId"" uuid NOT NULL,
|
||
""StoreId"" uuid NOT NULL,
|
||
""ProductId"" uuid NOT NULL,
|
||
""Quantity"" numeric(18,4) NOT NULL,
|
||
""CauseDocType"" varchar(50),
|
||
""CauseDocId"" uuid,
|
||
""CauseDocNumber"" varchar(50),
|
||
""CauseUserId"" uuid,
|
||
""CauseAt"" timestamp with time zone,
|
||
""FirstDetectedAt"" timestamp with time zone NOT NULL,
|
||
""LastUpdatedAt"" timestamp with time zone NOT NULL,
|
||
""ResolvedAt"" timestamp with time zone,
|
||
""NotifiedAt"" timestamp with time zone,
|
||
""EscalatedAt"" timestamp with time zone,
|
||
""CreatedAt"" timestamp with time zone NOT NULL,
|
||
""UpdatedAt"" timestamp with time zone,
|
||
CONSTRAINT ""FK_negative_stock_alerts_stores_StoreId"" FOREIGN KEY (""StoreId"") REFERENCES public.stores(""Id"") ON DELETE CASCADE,
|
||
CONSTRAINT ""FK_negative_stock_alerts_products_ProductId"" FOREIGN KEY (""ProductId"") REFERENCES public.products(""Id"") ON DELETE CASCADE
|
||
);
|
||
|
||
CREATE INDEX IF NOT EXISTS ""IX_negative_stock_alerts_OrganizationId_ResolvedAt""
|
||
ON public.negative_stock_alerts (""OrganizationId"", ""ResolvedAt"");
|
||
CREATE INDEX IF NOT EXISTS ""IX_negative_stock_alerts_OrganizationId_StoreId_ProductId""
|
||
ON public.negative_stock_alerts (""OrganizationId"", ""StoreId"", ""ProductId"");
|
||
-- Максимум один ОТКРЫТЫЙ алерт на (org, store, product).
|
||
CREATE UNIQUE INDEX IF NOT EXISTS ""UX_negative_stock_alerts_open""
|
||
ON public.negative_stock_alerts (""OrganizationId"", ""StoreId"", ""ProductId"")
|
||
WHERE ""ResolvedAt"" IS NULL;
|
||
");
|
||
}
|
||
|
||
protected override void Down(MigrationBuilder b)
|
||
{
|
||
b.Sql(@"DROP TABLE IF EXISTS public.negative_stock_alerts;");
|
||
}
|
||
}
|
||
}
|