food-market/src/food-market.infrastructure/Persistence/Migrations/20261008030000_Phase34_ShiftMoneyMovements.cs
nns 1e6d7fb2ab fix(money): движения смены попадают в остатки счетов + запрет отрицательного остатка
Раньше продажи/возвраты/внесения/выплаты/расхождение смены не влияли на остаток
кассы (касса уходила в −, эквайринг оставался 0). Теперь при закрытии смены
MoneyService.MaterializeShiftMovementsAsync создаёт Posted-движения money_transactions
по типам (Category): продажи нал/карта, возвраты нал/карта, внесение, выплата,
излишек/недостача — привязанные к смене, идемпотентно. Остаток кассы/эквайринга и
отчёт «Деньги» теперь верны автоматически (всё читает money_transactions).

Отрицательный остаток: расход (CashOut/PaymentOut/TransferOut) больше остатка счёта
и выплата из смены больше наличных смены → 409 insufficient-funds; настройка
Organization.AllowNegativeBalance (по умолч. выкл., меняет только Admin).

Миграция Phase34 (idempotent): money_transactions.Category/.CashShiftId,
organizations.AllowNegativeBalance.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 03:11:09 +05:00

56 lines
2.8 KiB
C#

using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using foodmarket.Infrastructure.Persistence;
#nullable disable
namespace foodmarket.Infrastructure.Persistence.Migrations
{
/// <summary>Phase34 — движения кассовой смены попадают в деньги.
///
/// Добавляет:
/// <list type="bullet">
/// <item>money_transactions.<c>Category</c> (integer, DEFAULT 0 = Manual) —
/// тип движения: розничные продажи/возвраты нал/карта, внесения/выплаты,
/// излишек/недостача смены.</item>
/// <item>money_transactions.<c>CashShiftId</c> (uuid, nullable) —
/// смена, породившая движение (материализуется при закрытии).</item>
/// <item>organizations.<c>AllowNegativeBalance</c> (boolean, DEFAULT false) —
/// разрешить ли уход счетов в минус.</item>
/// </list>
///
/// Идемпотентно (IF NOT EXISTS). Данные не пересчитываем: остаток счёта
/// вычисляемый (Opening + Σ Posted money_transactions); движения прошлых
/// закрытых смен материализуются ретроспективно отдельным backfill-методом
/// в DevDataSeeder (idempotent, по CashShiftId).</summary>
[DbContext(typeof(AppDbContext))]
[Migration("20261008030000_Phase34_ShiftMoneyMovements")]
public partial class Phase34_ShiftMoneyMovements : Migration
{
protected override void Up(MigrationBuilder b)
{
b.Sql(@"
ALTER TABLE public.money_transactions
ADD COLUMN IF NOT EXISTS ""Category"" integer NOT NULL DEFAULT 0;
ALTER TABLE public.money_transactions
ADD COLUMN IF NOT EXISTS ""CashShiftId"" uuid;
CREATE INDEX IF NOT EXISTS ""IX_money_transactions_CashShiftId""
ON public.money_transactions (""CashShiftId"")
WHERE ""CashShiftId"" IS NOT NULL;
ALTER TABLE public.organizations
ADD COLUMN IF NOT EXISTS ""AllowNegativeBalance"" boolean NOT NULL DEFAULT false;
");
}
protected override void Down(MigrationBuilder b)
{
b.Sql(@"
DROP INDEX IF EXISTS public.""IX_money_transactions_CashShiftId"";
ALTER TABLE public.money_transactions DROP COLUMN IF EXISTS ""CashShiftId"";
ALTER TABLE public.money_transactions DROP COLUMN IF EXISTS ""Category"";
ALTER TABLE public.organizations DROP COLUMN IF EXISTS ""AllowNegativeBalance"";
");
}
}
}