food-market/src/food-market.infrastructure/Persistence/Migrations/20260422110503_Phase2c_RetailSale.cs
nurdotnet 61558179e3 phase2c: RetailSale document — посты в stock как минусовые движения
Domain (foodmarket.Domain.Sales):
- RetailSale: Number "ПР-{yyyy}-{NNNNNN}", Date, Status (Draft/Posted),
  Store/RetailPoint/Customer/Currency, Subtotal/DiscountTotal/Total,
  Payment (Cash/Card/BankTransfer/Bonus/Mixed) + PaidCash/PaidCard split,
  CashierUserId, Notes, Lines.
- RetailSaleLine: ProductId, Quantity, UnitPrice, Discount, LineTotal,
  VatPercent (snapshot), SortOrder.
- PaymentMethod enum.

EF: retail_sales + retail_sale_lines, unique index (tenant,Number),
indexes by date/status/cashier. Migration Phase2c_RetailSale.

API /api/sales/retail (Authorize):
- GET list with filters status/store/from/to/search.
- GET {id} with lines joined to products + units, customer/retail-point
  names resolved.
- POST create draft (lines optional, totals computed server-side).
- PUT update — replaces lines wholesale; rejected if Posted.
- DELETE — drafts only.
- POST {id}/post — creates -qty StockMovements via IStockService for each
  line (decreasing stock), Type=RetailSale; flips to Posted, stamps PostedAt.
- POST {id}/unpost — reverses with +qty movements tagged "retail-sale-reversal".
- Auto-numbering scoped per tenant + year.

Web:
- types: RetailSaleStatus, PaymentMethod, RetailSaleListRow, RetailSaleLineDto,
  RetailSaleDto.
- /sales/retail list (number, date+time, status badge, store, cashier point,
  customer (or "аноним"), payment method, line count, total).
- /sales/retail/new + /:id edit page mirrors Supply edit page UX:
  sticky top bar (Back / Save / Post / Unpost / Delete), reqs grid with
  date/store/customer/currency/payment/paid-cash/paid-card, lines table
  with inline qty/price/discount + Subtotal/Discount/К оплате footer.
- ProductPicker reused. On line add, picks retail price from product's
  prices list (matches "розн" in priceTypeName) or first.
- Sidebar new group "Продажи" → "Розничные чеки" (ShoppingCart).

Posting cycle ready: Supply (+stock) → ... → RetailSale (-stock).
В Stock и Движения видно текущее состояние и историю.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 16:07:37 +05:00

192 lines
9.3 KiB
C#

using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace foodmarket.Infrastructure.Persistence.Migrations
{
/// <inheritdoc />
public partial class Phase2c_RetailSale : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "retail_sales",
schema: "public",
columns: table => new
{
Id = table.Column<Guid>(type: "uuid", nullable: false),
Number = table.Column<string>(type: "character varying(50)", maxLength: 50, nullable: false),
Date = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
Status = table.Column<int>(type: "integer", nullable: false),
StoreId = table.Column<Guid>(type: "uuid", nullable: false),
RetailPointId = table.Column<Guid>(type: "uuid", nullable: true),
CustomerId = table.Column<Guid>(type: "uuid", nullable: true),
CashierUserId = table.Column<Guid>(type: "uuid", nullable: true),
CurrencyId = table.Column<Guid>(type: "uuid", nullable: false),
Subtotal = table.Column<decimal>(type: "numeric(18,4)", precision: 18, scale: 4, nullable: false),
DiscountTotal = table.Column<decimal>(type: "numeric(18,4)", precision: 18, scale: 4, nullable: false),
Total = table.Column<decimal>(type: "numeric(18,4)", precision: 18, scale: 4, nullable: false),
Payment = table.Column<int>(type: "integer", nullable: false),
PaidCash = table.Column<decimal>(type: "numeric(18,4)", precision: 18, scale: 4, nullable: false),
PaidCard = table.Column<decimal>(type: "numeric(18,4)", precision: 18, scale: 4, nullable: false),
Notes = table.Column<string>(type: "character varying(1000)", maxLength: 1000, nullable: true),
PostedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
PostedByUserId = table.Column<Guid>(type: "uuid", nullable: true),
CreatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
UpdatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
OrganizationId = table.Column<Guid>(type: "uuid", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_retail_sales", x => x.Id);
table.ForeignKey(
name: "FK_retail_sales_counterparties_CustomerId",
column: x => x.CustomerId,
principalSchema: "public",
principalTable: "counterparties",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
table.ForeignKey(
name: "FK_retail_sales_currencies_CurrencyId",
column: x => x.CurrencyId,
principalSchema: "public",
principalTable: "currencies",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
table.ForeignKey(
name: "FK_retail_sales_retail_points_RetailPointId",
column: x => x.RetailPointId,
principalSchema: "public",
principalTable: "retail_points",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
table.ForeignKey(
name: "FK_retail_sales_stores_StoreId",
column: x => x.StoreId,
principalSchema: "public",
principalTable: "stores",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
});
migrationBuilder.CreateTable(
name: "retail_sale_lines",
schema: "public",
columns: table => new
{
Id = table.Column<Guid>(type: "uuid", nullable: false),
RetailSaleId = table.Column<Guid>(type: "uuid", nullable: false),
ProductId = table.Column<Guid>(type: "uuid", nullable: false),
Quantity = table.Column<decimal>(type: "numeric(18,4)", precision: 18, scale: 4, nullable: false),
UnitPrice = table.Column<decimal>(type: "numeric(18,4)", precision: 18, scale: 4, nullable: false),
Discount = table.Column<decimal>(type: "numeric(18,4)", precision: 18, scale: 4, nullable: false),
LineTotal = table.Column<decimal>(type: "numeric(18,4)", precision: 18, scale: 4, nullable: false),
VatPercent = table.Column<decimal>(type: "numeric(5,2)", precision: 5, scale: 2, nullable: false),
SortOrder = table.Column<int>(type: "integer", nullable: false),
CreatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
UpdatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
OrganizationId = table.Column<Guid>(type: "uuid", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_retail_sale_lines", x => x.Id);
table.ForeignKey(
name: "FK_retail_sale_lines_products_ProductId",
column: x => x.ProductId,
principalSchema: "public",
principalTable: "products",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
table.ForeignKey(
name: "FK_retail_sale_lines_retail_sales_RetailSaleId",
column: x => x.RetailSaleId,
principalSchema: "public",
principalTable: "retail_sales",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateIndex(
name: "IX_retail_sale_lines_OrganizationId_ProductId",
schema: "public",
table: "retail_sale_lines",
columns: new[] { "OrganizationId", "ProductId" });
migrationBuilder.CreateIndex(
name: "IX_retail_sale_lines_ProductId",
schema: "public",
table: "retail_sale_lines",
column: "ProductId");
migrationBuilder.CreateIndex(
name: "IX_retail_sale_lines_RetailSaleId",
schema: "public",
table: "retail_sale_lines",
column: "RetailSaleId");
migrationBuilder.CreateIndex(
name: "IX_retail_sales_CurrencyId",
schema: "public",
table: "retail_sales",
column: "CurrencyId");
migrationBuilder.CreateIndex(
name: "IX_retail_sales_CustomerId",
schema: "public",
table: "retail_sales",
column: "CustomerId");
migrationBuilder.CreateIndex(
name: "IX_retail_sales_OrganizationId_CashierUserId",
schema: "public",
table: "retail_sales",
columns: new[] { "OrganizationId", "CashierUserId" });
migrationBuilder.CreateIndex(
name: "IX_retail_sales_OrganizationId_Date",
schema: "public",
table: "retail_sales",
columns: new[] { "OrganizationId", "Date" });
migrationBuilder.CreateIndex(
name: "IX_retail_sales_OrganizationId_Number",
schema: "public",
table: "retail_sales",
columns: new[] { "OrganizationId", "Number" },
unique: true);
migrationBuilder.CreateIndex(
name: "IX_retail_sales_OrganizationId_Status",
schema: "public",
table: "retail_sales",
columns: new[] { "OrganizationId", "Status" });
migrationBuilder.CreateIndex(
name: "IX_retail_sales_RetailPointId",
schema: "public",
table: "retail_sales",
column: "RetailPointId");
migrationBuilder.CreateIndex(
name: "IX_retail_sales_StoreId",
schema: "public",
table: "retail_sales",
column: "StoreId");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "retail_sale_lines",
schema: "public");
migrationBuilder.DropTable(
name: "retail_sales",
schema: "public");
}
}
}