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>
48 lines
2.2 KiB
C#
48 lines
2.2 KiB
C#
using foodmarket.Domain.Sales;
|
|
using Microsoft.EntityFrameworkCore;
|
|
|
|
namespace foodmarket.Infrastructure.Persistence.Configurations;
|
|
|
|
public static class SalesConfigurations
|
|
{
|
|
public static void ConfigureSales(this ModelBuilder b)
|
|
{
|
|
b.Entity<RetailSale>(e =>
|
|
{
|
|
e.ToTable("retail_sales");
|
|
e.Property(x => x.Number).HasMaxLength(50).IsRequired();
|
|
e.Property(x => x.Notes).HasMaxLength(1000);
|
|
e.Property(x => x.Subtotal).HasPrecision(18, 4);
|
|
e.Property(x => x.DiscountTotal).HasPrecision(18, 4);
|
|
e.Property(x => x.Total).HasPrecision(18, 4);
|
|
e.Property(x => x.PaidCash).HasPrecision(18, 4);
|
|
e.Property(x => x.PaidCard).HasPrecision(18, 4);
|
|
|
|
e.HasOne(x => x.Store).WithMany().HasForeignKey(x => x.StoreId).OnDelete(DeleteBehavior.Restrict);
|
|
e.HasOne(x => x.RetailPoint).WithMany().HasForeignKey(x => x.RetailPointId).OnDelete(DeleteBehavior.Restrict);
|
|
e.HasOne(x => x.Customer).WithMany().HasForeignKey(x => x.CustomerId).OnDelete(DeleteBehavior.Restrict);
|
|
e.HasOne(x => x.Currency).WithMany().HasForeignKey(x => x.CurrencyId).OnDelete(DeleteBehavior.Restrict);
|
|
|
|
e.HasMany(x => x.Lines).WithOne(l => l.RetailSale).HasForeignKey(l => l.RetailSaleId).OnDelete(DeleteBehavior.Cascade);
|
|
|
|
e.HasIndex(x => new { x.OrganizationId, x.Number }).IsUnique();
|
|
e.HasIndex(x => new { x.OrganizationId, x.Date });
|
|
e.HasIndex(x => new { x.OrganizationId, x.Status });
|
|
e.HasIndex(x => new { x.OrganizationId, x.CashierUserId });
|
|
});
|
|
|
|
b.Entity<RetailSaleLine>(e =>
|
|
{
|
|
e.ToTable("retail_sale_lines");
|
|
e.Property(x => x.Quantity).HasPrecision(18, 4);
|
|
e.Property(x => x.UnitPrice).HasPrecision(18, 4);
|
|
e.Property(x => x.Discount).HasPrecision(18, 4);
|
|
e.Property(x => x.LineTotal).HasPrecision(18, 4);
|
|
e.Property(x => x.VatPercent).HasPrecision(5, 2);
|
|
|
|
e.HasOne(x => x.Product).WithMany().HasForeignKey(x => x.ProductId).OnDelete(DeleteBehavior.Restrict);
|
|
e.HasIndex(x => new { x.OrganizationId, x.ProductId });
|
|
});
|
|
}
|
|
}
|