From 9908a552d159b7147c35821aea73d34b8e74bf23 Mon Sep 17 00:00:00 2001 From: DeepnaPooja Date: Thu, 8 Oct 2026 10:37:19 +0530 Subject: [PATCH 1/9] Fix GRN pricing --- Distribution/Features/Grn/GrnController.cs | 3 +- Distribution/Features/Grn/GrnDTOs.cs | 45 +- Distribution/Features/Grn/GrnPricing.cs | 32 + Distribution/Features/Grn/GrnService.cs | 84 +- .../20261007063956_AddGrnPricing.Designer.cs | 4407 ++++++++++++++++ .../20261007063956_AddGrnPricing.cs | 105 + ...61007065602_AddGrnFreeQuantity.Designer.cs | 4412 +++++++++++++++++ .../20261007065602_AddGrnFreeQuantity.cs | 44 + .../Migrations/AppDBContextModelSnapshot.cs | 43 +- Distribution/Models/GoodsReceipt.cs | 9 + Distribution/Models/GoodsReceiptItem.cs | 17 + 11 files changed, 9171 insertions(+), 30 deletions(-) create mode 100644 Distribution/Features/Grn/GrnPricing.cs create mode 100644 Distribution/Migrations/20261007063956_AddGrnPricing.Designer.cs create mode 100644 Distribution/Migrations/20261007063956_AddGrnPricing.cs create mode 100644 Distribution/Migrations/20261007065602_AddGrnFreeQuantity.Designer.cs create mode 100644 Distribution/Migrations/20261007065602_AddGrnFreeQuantity.cs diff --git a/Distribution/Features/Grn/GrnController.cs b/Distribution/Features/Grn/GrnController.cs index 7f43379..a68c5a6 100644 --- a/Distribution/Features/Grn/GrnController.cs +++ b/Distribution/Features/Grn/GrnController.cs @@ -32,7 +32,8 @@ public class GrnController(GrnService service, ICurrentUserService currentUser) if (userId is null) return BadRequest(new { error = "ReceivedByUserId is required while auth is unavailable" }); try { - var inner = new CreateGrnRequest(request.WarehouseId, request.Items); + var inner = new CreateGrnRequest(request.WarehouseId, request.Items, + DiscountPercent: request.DiscountPercent, VatPercent: request.VatPercent); var result = await service.CreateAsync(request.PurchaseOrderId, inner, userId.Value, ct); return CreatedAtAction(nameof(GetById), new { id = result.Id }, result); } diff --git a/Distribution/Features/Grn/GrnDTOs.cs b/Distribution/Features/Grn/GrnDTOs.cs index d4b9692..6c5f7b4 100644 --- a/Distribution/Features/Grn/GrnDTOs.cs +++ b/Distribution/Features/Grn/GrnDTOs.cs @@ -13,20 +13,32 @@ public record GrnItemRequest( // Populated only when the item has TracksSerialNumbers set — must contain exactly AcceptedQuantity entries. List? SerialNumbers = null, // Set for a direct receipt line (no purchase order). - Guid? ItemId = null); + Guid? ItemId = null, + // Cost per base-UOM unit. Null on a PO line falls back to the PO line's unit cost; null on a direct line is 0. + decimal? UnitCost = null, + // Null falls back to the GRN-wide percentage. + decimal? DiscountPercent = null, + decimal? VatPercent = null, + // Free-issue units: stocked but not costed. Serial/batch entries must cover accepted + free. + int FreeQuantity = 0); public record CreateGrnRequest( Guid WarehouseId, List Items, // TEMPORARY: required while auth doesn't resolve a logged-in user server-side — see testbug.md #28. - Guid? ReceivedByUserId = null); + Guid? ReceivedByUserId = null, + // GRN-wide defaults for lines that don't set their own. + decimal? DiscountPercent = null, + decimal? VatPercent = null); public record CreateFlatGrnRequest( Guid PurchaseOrderId, Guid WarehouseId, List Items, // TEMPORARY: required while auth doesn't resolve a logged-in user server-side — see testbug.md #28. - Guid? ReceivedByUserId = null); + Guid? ReceivedByUserId = null, + decimal? DiscountPercent = null, + decimal? VatPercent = null); /// A goods receipt with no purchase order. SupplierId is optional. public record CreateDirectGrnRequest( @@ -34,7 +46,9 @@ public record CreateDirectGrnRequest( List Items, Guid? SupplierId = null, // TEMPORARY: required while auth doesn't resolve a logged-in user server-side — see testbug.md #28. - Guid? ReceivedByUserId = null); + Guid? ReceivedByUserId = null, + decimal? DiscountPercent = null, + decimal? VatPercent = null); public record GrnBatchLineResponse(string? BatchNumber, DateTime? ExpiryDate, int Quantity); @@ -47,7 +61,17 @@ public record GrnItemResponse( int AcceptedQuantity, int RejectedQuantity, List Batches, - List SerialNumbers); + List SerialNumbers, + decimal UnitCost, + decimal DiscountPercent, + decimal VatPercent, + // Accepted qty x unit cost, before discount. + decimal GrossAmount, + decimal DiscountAmount, + decimal VatAmount, + // Gross - discount + VAT. + decimal LineTotal, + int FreeQuantity); public record GrnResponse( Guid Id, @@ -59,4 +83,13 @@ public record GrnResponse( string? SupplierName, DateTime ReceivedAt, DateTime CreatedAt, - List Items); + List Items, + decimal DiscountPercent, + decimal VatPercent, + // Sum of line gross amounts. + decimal SubTotal, + decimal DiscountAmount, + // SubTotal - DiscountAmount (the base VAT is charged on). + decimal TaxableAmount, + decimal VatAmount, + decimal TotalAmount); diff --git a/Distribution/Features/Grn/GrnPricing.cs b/Distribution/Features/Grn/GrnPricing.cs new file mode 100644 index 0000000..da64363 --- /dev/null +++ b/Distribution/Features/Grn/GrnPricing.cs @@ -0,0 +1,32 @@ +namespace Distribution.Features.Grn; + +/// +/// GRN costing. Per line: gross = accepted qty x unit cost; discount = gross x disc%; +/// VAT = (gross - discount) x VAT%; total = gross - discount + VAT. Rejected units are never costed. +/// Each amount is rounded to 2 dp per line so the GRN totals equal the sum of what the lines show. +/// +public static class GrnPricing +{ + public static decimal Round(decimal value) => Math.Round(value, 2, MidpointRounding.AwayFromZero); + + public static (decimal Gross, decimal Discount, decimal Vat, decimal Total) Line( + int acceptedQty, decimal unitCost, decimal discountPercent, decimal vatPercent) + { + var gross = Round(acceptedQty * unitCost); + var discount = Round(gross * discountPercent / 100m); + var vat = Round((gross - discount) * vatPercent / 100m); + return (gross, discount, vat, gross - discount + vat); + } + + public static void ValidatePercent(decimal value, string label) + { + if (value < 0 || value > 100) + throw new InvalidOperationException($"{label} must be between 0 and 100."); + } + + public static void ValidateCost(decimal value, string label) + { + if (value < 0) + throw new InvalidOperationException($"{label} cannot be negative."); + } +} diff --git a/Distribution/Features/Grn/GrnService.cs b/Distribution/Features/Grn/GrnService.cs index dd5df45..40ebb0e 100644 --- a/Distribution/Features/Grn/GrnService.cs +++ b/Distribution/Features/Grn/GrnService.cs @@ -17,16 +17,51 @@ public class GrnService( { // Direct-receipt lines carry the item themselves; PO lines resolve it through the PO line. var item = i.Item ?? i.PurchaseOrderItem!.Item; + var (gross, discount, vat, total) = GrnPricing.Line(i.AcceptedQuantity, i.UnitCost, i.DiscountPercent, i.VatPercent); return new(i.Id, i.FK_PurchaseOrderItemId, item.Id, item.Sku, item.Name, i.AcceptedQuantity, i.RejectedQuantity, i.Batches.Select(b => new GrnBatchLineResponse(b.ItemBatch.BatchNumber, b.ItemBatch.ExpiryDate, b.Quantity)).ToList(), - i.Serials.Select(s => s.ItemSerialNumber.SerialNumber).ToList()); + i.Serials.Select(s => s.ItemSerialNumber.SerialNumber).ToList(), + i.UnitCost, i.DiscountPercent, i.VatPercent, gross, discount, vat, total, i.FreeQuantity); } - private static GrnResponse Map(Models.GoodsReceipt gr) => - new(gr.Id, gr.FK_PurchaseOrderId, gr.FK_ReceivedByUserId, gr.FK_WarehouseId, gr.Warehouse?.Name ?? string.Empty, + /// Units that actually enter stock: accepted plus free-issue. Serial/batch entries must cover this. + private static int StockQty(GrnItemRequest line) => line.AcceptedQuantity + line.FreeQuantity; + + private static GrnResponse Map(Models.GoodsReceipt gr) + { + var items = gr.Items.Where(i => i.IsActive).Select(MapItem).ToList(); + var subTotal = items.Sum(i => i.GrossAmount); + var discount = items.Sum(i => i.DiscountAmount); + var vat = items.Sum(i => i.VatAmount); + return new(gr.Id, gr.FK_PurchaseOrderId, gr.FK_ReceivedByUserId, gr.FK_WarehouseId, gr.Warehouse?.Name ?? string.Empty, gr.FK_SupplierId, gr.Supplier?.Name, - gr.ReceivedAt, gr.CreatedAt, gr.Items.Where(i => i.IsActive).Select(MapItem).ToList()); + gr.ReceivedAt, gr.CreatedAt, items, + gr.DiscountPercent, gr.VatPercent, + subTotal, discount, subTotal - discount, vat, subTotal - discount + vat); + } + + /// Validates and resolves the GRN-wide percentages that lines fall back to. + private static (decimal Discount, decimal Vat) ResolveHeader(decimal? discountPercent, decimal? vatPercent) + { + var discount = discountPercent ?? 0m; + var vat = vatPercent ?? 0m; + GrnPricing.ValidatePercent(discount, "GRN discount %"); + GrnPricing.ValidatePercent(vat, "GRN VAT %"); + return (discount, vat); + } + + /// Copies a line's resolved cost, discount % and VAT % onto the entity after validating them. + private static void ApplyPricing(Models.GoodsReceiptItem entity, GrnItemRequest line, decimal fallbackUnitCost, + decimal headerDiscount, decimal headerVat, string sku) + { + entity.UnitCost = line.UnitCost ?? fallbackUnitCost; + entity.DiscountPercent = line.DiscountPercent ?? headerDiscount; + entity.VatPercent = line.VatPercent ?? headerVat; + GrnPricing.ValidateCost(entity.UnitCost, $"Unit cost for item {sku}"); + GrnPricing.ValidatePercent(entity.DiscountPercent, $"Discount % for item {sku}"); + GrnPricing.ValidatePercent(entity.VatPercent, $"VAT % for item {sku}"); + } public async Task> GetAllAsync(Guid? purchaseOrderId, int page, int pageSize) { @@ -55,9 +90,9 @@ public class GrnService( { if (item.TracksSerialNumbers) { - if (line.AcceptedQuantity > 0 && (line.SerialNumbers is null || line.SerialNumbers.Count != line.AcceptedQuantity)) + if (StockQty(line) > 0 && (line.SerialNumbers is null || line.SerialNumbers.Count != StockQty(line))) throw new InvalidOperationException( - $"Item {item.Sku} tracks serial numbers — expected {line.AcceptedQuantity} serial number(s)."); + $"Item {item.Sku} tracks serial numbers — expected {StockQty(line)} serial number(s)."); if (line.SerialNumbers is { Count: > 0 } && line.SerialNumbers.Distinct().Count() != line.SerialNumbers.Count) throw new InvalidOperationException($"Duplicate serial numbers supplied for item {item.Sku}."); @@ -68,11 +103,11 @@ public class GrnService( } else if (item.TracksBatches || item.TracksExpiryDate) { - if (line.AcceptedQuantity > 0) + if (StockQty(line) > 0) { if (line.Batches is null || line.Batches.Count == 0) throw new InvalidOperationException($"Item {item.Sku} requires batch/expiry information."); - if (line.Batches.Sum(b => b.Quantity) != line.AcceptedQuantity) + if (line.Batches.Sum(b => b.Quantity) != StockQty(line)) throw new InvalidOperationException($"Batch quantities for item {item.Sku} must sum to the accepted quantity."); } @@ -96,7 +131,8 @@ public class GrnService( /// /// Validates the Po status, increments PurchaseOrderItem.ReceivedQuantity, writes StockBalance/ - /// StockMovement for AcceptedQuantity only (RejectedQuantity never touches stock), creates + /// StockMovement for AcceptedQuantity + FreeQuantity (RejectedQuantity never touches stock; free + /// units don't count against the PO's ordered quantity), creates /// ItemBatch/ItemSerialNumber records for tracked items, and transitions the Po to /// PartiallyReceived/Received. All in one unit of work. /// @@ -115,12 +151,16 @@ public class GrnService( if (request.Items is null || request.Items.Count == 0) throw new InvalidOperationException("A goods receipt must have at least one line item."); + var (headerDiscount, headerVat) = ResolveHeader(request.DiscountPercent, request.VatPercent); + var goodsReceipt = new Models.GoodsReceipt { FK_PurchaseOrderId = po.Id, FK_ReceivedByUserId = receivedByUserId, FK_WarehouseId = request.WarehouseId, - ReceivedAt = DateTime.Now + ReceivedAt = DateTime.Now, + DiscountPercent = headerDiscount, + VatPercent = headerVat }; var grLines = new List<(Models.GoodsReceiptItem Entity, GrnItemRequest Line, Models.Item Item)>(); @@ -136,7 +176,7 @@ public class GrnService( if (poItem.IsCancelled) throw new InvalidOperationException($"Line for item {poItem.FK_ItemId} is cancelled and cannot receive stock."); - if (line.AcceptedQuantity < 0 || line.RejectedQuantity < 0) + if (line.AcceptedQuantity < 0 || line.RejectedQuantity < 0 || line.FreeQuantity < 0) throw new InvalidOperationException("Accepted/rejected quantities cannot be negative."); if (line.AcceptedQuantity + line.RejectedQuantity + poItem.ReceivedQuantity > poItem.Quantity) @@ -160,8 +200,10 @@ public class GrnService( { FK_PurchaseOrderItemId = poItem.Id, AcceptedQuantity = line.AcceptedQuantity, + FreeQuantity = line.FreeQuantity, RejectedQuantity = line.RejectedQuantity }; + ApplyPricing(entity, line, poItem.UnitCost, headerDiscount, headerVat, poItem.Item.Sku); goodsReceipt.Items.Add(entity); grLines.Add((entity, line, poItem.Item)); } @@ -185,13 +227,17 @@ public class GrnService( if (request.SupplierId is not null && await supplierRepo.GetByIdAsync(request.SupplierId.Value) is not { IsActive: true }) throw new InvalidOperationException("Supplier not found."); + var (headerDiscount, headerVat) = ResolveHeader(request.DiscountPercent, request.VatPercent); + var goodsReceipt = new Models.GoodsReceipt { FK_PurchaseOrderId = null, FK_SupplierId = request.SupplierId, FK_ReceivedByUserId = receivedByUserId, FK_WarehouseId = request.WarehouseId, - ReceivedAt = DateTime.Now + ReceivedAt = DateTime.Now, + DiscountPercent = headerDiscount, + VatPercent = headerVat }; var grLines = new List<(Models.GoodsReceiptItem Entity, GrnItemRequest Line, Models.Item Item)>(); @@ -206,10 +252,10 @@ public class GrnService( if (item is null || !item.IsActive) throw new InvalidOperationException($"Item {line.ItemId} not found."); - if (line.AcceptedQuantity < 0 || line.RejectedQuantity < 0) + if (line.AcceptedQuantity < 0 || line.RejectedQuantity < 0 || line.FreeQuantity < 0) throw new InvalidOperationException("Accepted/rejected quantities cannot be negative."); - if (line.AcceptedQuantity + line.RejectedQuantity == 0) + if (line.AcceptedQuantity + line.RejectedQuantity + line.FreeQuantity == 0) throw new InvalidOperationException($"Line for item {item.Sku} has no quantity."); ValidateLineShape(item, line); @@ -228,9 +274,11 @@ public class GrnService( var entity = new Models.GoodsReceiptItem { FK_ItemId = item.Id, + FreeQuantity = line.FreeQuantity, AcceptedQuantity = line.AcceptedQuantity, RejectedQuantity = line.RejectedQuantity }; + ApplyPricing(entity, line, 0m, headerDiscount, headerVat, item.Sku); goodsReceipt.Items.Add(entity); grLines.Add((entity, line, item)); } @@ -255,7 +303,7 @@ public class GrnService( { await repo.AddAsync(goodsReceipt); - foreach (var (entity, line, item) in grLines.Where(x => x.Line.AcceptedQuantity > 0)) + foreach (var (entity, line, item) in grLines.Where(x => StockQty(x.Line) > 0)) { if (item.TracksSerialNumbers) { @@ -369,12 +417,12 @@ public class GrnService( FK_ItemId = item.Id, LocationId = warehouseId, LocationType = LocationType.Warehouse, - Quantity = line.AcceptedQuantity + Quantity = StockQty(line) }); } else { - balance.Quantity += line.AcceptedQuantity; + balance.Quantity += StockQty(line); balance.UpdatedAt = DateTime.Now; stockRepo.UpdateBalance(balance); } @@ -386,7 +434,7 @@ public class GrnService( FromLocationType = LocationType.External, ToLocationId = warehouseId, ToLocationType = LocationType.Warehouse, - Quantity = line.AcceptedQuantity, + Quantity = StockQty(line), Type = MovementType.PurchaseReceipt, ReferenceId = goodsReceipt.Id, CreatedBy = receivedByUserId diff --git a/Distribution/Migrations/20261007063956_AddGrnPricing.Designer.cs b/Distribution/Migrations/20261007063956_AddGrnPricing.Designer.cs new file mode 100644 index 0000000..70766e2 --- /dev/null +++ b/Distribution/Migrations/20261007063956_AddGrnPricing.Designer.cs @@ -0,0 +1,4407 @@ +// +using System; +using Distribution.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace Distribution.Migrations +{ + [DbContext(typeof(AppDBContext))] + [Migration("20261007063956_AddGrnPricing")] + partial class AddGrnPricing + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "9.0.0") + .HasAnnotation("Relational:MaxIdentifierLength", 64); + + MySqlModelBuilderExtensions.AutoIncrementColumns(modelBuilder); + + modelBuilder.Entity("Distribution.Models.Brand", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("ExternalErpId") + .HasColumnType("int"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.ToTable("Brands"); + + b.HasData( + new + { + Id = new Guid("21000000-0000-0000-0000-000000000001"), + CreatedAt = new DateTime(2026, 8, 20, 0, 0, 0, 0, DateTimeKind.Unspecified), + IsActive = true, + Name = "Coca-Cola" + }, + new + { + Id = new Guid("21000000-0000-0000-0000-000000000002"), + CreatedAt = new DateTime(2026, 8, 20, 0, 0, 0, 0, DateTimeKind.Unspecified), + IsActive = true, + Name = "Lays" + }); + }); + + modelBuilder.Entity("Distribution.Models.Category", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("AllowCustomerReturn") + .HasColumnType("tinyint(1)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("ExternalErpId") + .HasColumnType("int"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.ToTable("Categories"); + + b.HasData( + new + { + Id = new Guid("20000000-0000-0000-0000-000000000001"), + AllowCustomerReturn = false, + CreatedAt = new DateTime(2026, 8, 20, 0, 0, 0, 0, DateTimeKind.Unspecified), + IsActive = true, + Name = "Beverages" + }, + new + { + Id = new Guid("20000000-0000-0000-0000-000000000002"), + AllowCustomerReturn = false, + CreatedAt = new DateTime(2026, 8, 20, 0, 0, 0, 0, DateTimeKind.Unspecified), + IsActive = true, + Name = "Snacks" + }); + }); + + modelBuilder.Entity("Distribution.Models.Claim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Details") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("FK_AgencyUserId") + .HasColumnType("char(36)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("SettledAt") + .HasColumnType("datetime(6)"); + + b.Property("SettledByUserId") + .HasColumnType("char(36)"); + + b.Property("SettlementAmount") + .HasColumnType("decimal(65,30)"); + + b.Property("Status") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("FK_AgencyUserId"); + + b.HasIndex("SettledByUserId"); + + b.ToTable("Claims"); + + b.HasData( + new + { + Id = new Guid("53000000-0000-0000-0000-000000000001"), + CreatedAt = new DateTime(2026, 8, 27, 0, 0, 0, 0, DateTimeKind.Unspecified), + Details = "Distributor margin claim for Q3 promotional shortfall", + FK_AgencyUserId = new Guid("70000000-0000-0000-0000-000000000001"), + IsActive = true, + SettledAt = new DateTime(2026, 8, 28, 0, 0, 0, 0, DateTimeKind.Unspecified), + SettledByUserId = new Guid("29000000-0000-0000-0000-000000000006"), + SettlementAmount = 15000m, + Status = 1 + }); + }); + + modelBuilder.Entity("Distribution.Models.CreditNote", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("Amount") + .HasColumnType("decimal(65,30)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("FK_SaleReturnId") + .HasColumnType("char(36)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("IssuedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("FK_SaleReturnId"); + + b.ToTable("CreditNotes"); + + b.HasData( + new + { + Id = new Guid("54000000-0000-0000-0000-000000000001"), + Amount = 300m, + CreatedAt = new DateTime(2026, 8, 26, 0, 0, 0, 0, DateTimeKind.Unspecified), + FK_SaleReturnId = new Guid("51000000-0000-0000-0000-000000000001"), + IsActive = true, + IssuedAt = new DateTime(2026, 8, 26, 0, 0, 0, 0, DateTimeKind.Unspecified) + }); + }); + + modelBuilder.Entity("Distribution.Models.DeliveryRun", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CancelReason") + .HasColumnType("longtext"); + + b.Property("CancelTypeCode") + .HasColumnType("int"); + + b.Property("ClosedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("FK_DriverProfileId") + .HasColumnType("char(36)"); + + b.Property("FK_VehicleId") + .HasColumnType("char(36)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("Meterend") + .HasColumnType("int"); + + b.Property("Meterstart") + .HasColumnType("int"); + + b.Property("StartedAt") + .HasColumnType("datetime(6)"); + + b.Property("Status") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("FK_DriverProfileId"); + + b.HasIndex("FK_VehicleId"); + + b.ToTable("DeliveryRuns"); + + b.HasData( + new + { + Id = new Guid("31000000-0000-0000-0000-000000000001"), + ClosedAt = new DateTime(2026, 8, 25, 14, 0, 0, 0, DateTimeKind.Unspecified), + CreatedAt = new DateTime(2026, 8, 25, 7, 30, 0, 0, DateTimeKind.Unspecified), + FK_DriverProfileId = new Guid("30000000-0000-0000-0000-000000000001"), + FK_VehicleId = new Guid("28000000-0000-0000-0000-000000000001"), + IsActive = true, + Meterend = 10550, + Meterstart = 10500, + StartedAt = new DateTime(2026, 8, 25, 8, 0, 0, 0, DateTimeKind.Unspecified), + Status = 2 + }, + new + { + Id = new Guid("31000000-0000-0000-0000-000000000002"), + CreatedAt = new DateTime(2026, 8, 30, 8, 45, 0, 0, DateTimeKind.Unspecified), + FK_DriverProfileId = new Guid("30000000-0000-0000-0000-000000000002"), + FK_VehicleId = new Guid("28000000-0000-0000-0000-000000000002"), + IsActive = true, + Meterend = 0, + Meterstart = 20300, + StartedAt = new DateTime(2026, 8, 30, 9, 0, 0, 0, DateTimeKind.Unspecified), + Status = 0 + }); + }); + + modelBuilder.Entity("Distribution.Models.DeliveryTask", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("ConfirmedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CustomerConfirmed") + .HasColumnType("tinyint(1)"); + + b.Property("FK_RideId") + .HasColumnType("char(36)"); + + b.Property("FK_ShopPOId") + .HasColumnType("char(36)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("Note") + .HasColumnType("longtext"); + + b.Property("Sequence") + .HasColumnType("int"); + + b.Property("Status") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("FK_RideId"); + + b.HasIndex("FK_ShopPOId"); + + b.ToTable("DeliveryTasks"); + + b.HasData( + new + { + Id = new Guid("37000000-0000-0000-0000-000000000001"), + CreatedAt = new DateTime(2026, 8, 30, 10, 5, 0, 0, DateTimeKind.Unspecified), + CustomerConfirmed = false, + FK_RideId = new Guid("31000000-0000-0000-0000-000000000002"), + FK_ShopPOId = new Guid("33000000-0000-0000-0000-000000000002"), + IsActive = true, + Sequence = 1, + Status = 0 + }); + }); + + modelBuilder.Entity("Distribution.Models.DriverComplianceConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("EnforceLicenseExpiry") + .HasColumnType("tinyint(1)"); + + b.Property("EnforceMedicalClearance") + .HasColumnType("tinyint(1)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("UpdatedBy") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.ToTable("DriverComplianceConfigs"); + + b.HasData( + new + { + Id = new Guid("64000000-0000-0000-0000-000000000001"), + EnforceLicenseExpiry = true, + EnforceMedicalClearance = true, + UpdatedAt = new DateTime(2026, 8, 20, 0, 0, 0, 0, DateTimeKind.Unspecified), + UpdatedBy = new Guid("29000000-0000-0000-0000-000000000001") + }); + }); + + modelBuilder.Entity("Distribution.Models.DriverProfile", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("FK_UserId") + .HasColumnType("char(36)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("LicenseExpiry") + .HasColumnType("datetime(6)"); + + b.Property("LicenseNumber") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("MedicalClearanceExpiry") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("FK_UserId") + .IsUnique(); + + b.ToTable("DriverProfiles"); + + b.HasData( + new + { + Id = new Guid("30000000-0000-0000-0000-000000000001"), + CreatedAt = new DateTime(2026, 8, 20, 0, 0, 0, 0, DateTimeKind.Unspecified), + FK_UserId = new Guid("29000000-0000-0000-0000-000000000004"), + IsActive = true, + LicenseExpiry = new DateTime(2027, 6, 30, 0, 0, 0, 0, DateTimeKind.Unspecified), + LicenseNumber = "B1234567", + MedicalClearanceExpiry = new DateTime(2027, 1, 31, 0, 0, 0, 0, DateTimeKind.Unspecified) + }, + new + { + Id = new Guid("30000000-0000-0000-0000-000000000002"), + CreatedAt = new DateTime(2026, 8, 20, 0, 0, 0, 0, DateTimeKind.Unspecified), + FK_UserId = new Guid("29000000-0000-0000-0000-000000000005"), + IsActive = true, + LicenseExpiry = new DateTime(2027, 9, 30, 0, 0, 0, 0, DateTimeKind.Unspecified), + LicenseNumber = "B7654321", + MedicalClearanceExpiry = new DateTime(2027, 3, 31, 0, 0, 0, 0, DateTimeKind.Unspecified) + }); + }); + + modelBuilder.Entity("Distribution.Models.GoodsReceipt", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("DiscountPercent") + .HasPrecision(5, 2) + .HasColumnType("decimal(5,2)"); + + b.Property("FK_PurchaseOrderId") + .HasColumnType("char(36)"); + + b.Property("FK_ReceivedByUserId") + .HasColumnType("char(36)"); + + b.Property("FK_SupplierId") + .HasColumnType("char(36)"); + + b.Property("FK_WarehouseId") + .HasColumnType("char(36)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("ReceivedAt") + .HasColumnType("datetime(6)"); + + b.Property("VatPercent") + .HasPrecision(5, 2) + .HasColumnType("decimal(5,2)"); + + b.HasKey("Id"); + + b.HasIndex("FK_PurchaseOrderId"); + + b.HasIndex("FK_SupplierId"); + + b.HasIndex("FK_WarehouseId"); + + b.ToTable("GoodsReceipts"); + + b.HasData( + new + { + Id = new Guid("43000000-0000-0000-0000-000000000001"), + CreatedAt = new DateTime(2026, 8, 15, 0, 0, 0, 0, DateTimeKind.Unspecified), + DiscountPercent = 0m, + FK_PurchaseOrderId = new Guid("41000000-0000-0000-0000-000000000001"), + FK_ReceivedByUserId = new Guid("29000000-0000-0000-0000-000000000001"), + FK_WarehouseId = new Guid("27000000-0000-0000-0000-000000000001"), + IsActive = true, + ReceivedAt = new DateTime(2026, 8, 15, 0, 0, 0, 0, DateTimeKind.Unspecified), + VatPercent = 0m + }); + }); + + modelBuilder.Entity("Distribution.Models.GoodsReceiptItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("AcceptedQuantity") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("DiscountPercent") + .HasPrecision(5, 2) + .HasColumnType("decimal(5,2)"); + + b.Property("FK_GoodsReceiptId") + .HasColumnType("char(36)"); + + b.Property("FK_ItemId") + .HasColumnType("char(36)"); + + b.Property("FK_PurchaseOrderItemId") + .HasColumnType("char(36)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("RejectedQuantity") + .HasColumnType("int"); + + b.Property("UnitCost") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("VatPercent") + .HasPrecision(5, 2) + .HasColumnType("decimal(5,2)"); + + b.HasKey("Id"); + + b.HasIndex("FK_GoodsReceiptId"); + + b.HasIndex("FK_ItemId"); + + b.HasIndex("FK_PurchaseOrderItemId"); + + b.ToTable("GoodsReceiptItems"); + + b.HasData( + new + { + Id = new Guid("44000000-0000-0000-0000-000000000001"), + AcceptedQuantity = 200, + CreatedAt = new DateTime(2026, 8, 15, 0, 0, 0, 0, DateTimeKind.Unspecified), + DiscountPercent = 0m, + FK_GoodsReceiptId = new Guid("43000000-0000-0000-0000-000000000001"), + FK_PurchaseOrderItemId = new Guid("42000000-0000-0000-0000-000000000001"), + IsActive = true, + RejectedQuantity = 0, + UnitCost = 0m, + VatPercent = 0m + }, + new + { + Id = new Guid("44000000-0000-0000-0000-000000000002"), + AcceptedQuantity = 98, + CreatedAt = new DateTime(2026, 8, 15, 0, 0, 0, 0, DateTimeKind.Unspecified), + DiscountPercent = 0m, + FK_GoodsReceiptId = new Guid("43000000-0000-0000-0000-000000000001"), + FK_PurchaseOrderItemId = new Guid("42000000-0000-0000-0000-000000000002"), + IsActive = true, + RejectedQuantity = 2, + UnitCost = 0m, + VatPercent = 0m + }); + }); + + modelBuilder.Entity("Distribution.Models.GoodsReceiptItemBatch", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("FK_GoodsReceiptItemId") + .HasColumnType("char(36)"); + + b.Property("FK_ItemBatchId") + .HasColumnType("char(36)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("Quantity") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("FK_GoodsReceiptItemId"); + + b.HasIndex("FK_ItemBatchId"); + + b.ToTable("GoodsReceiptItemBatches"); + }); + + modelBuilder.Entity("Distribution.Models.GoodsReceiptItemSerial", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("FK_GoodsReceiptItemId") + .HasColumnType("char(36)"); + + b.Property("FK_ItemSerialNumberId") + .HasColumnType("char(36)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.HasKey("Id"); + + b.HasIndex("FK_GoodsReceiptItemId"); + + b.HasIndex("FK_ItemSerialNumberId"); + + b.ToTable("GoodsReceiptItemSerials"); + }); + + modelBuilder.Entity("Distribution.Models.Invoice", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .HasColumnType("char(36)"); + + b.Property("FK_ShopPOId") + .HasColumnType("char(36)"); + + b.Property("FinalizedAt") + .HasColumnType("datetime(6)"); + + b.Property("FinalizedBy") + .HasColumnType("char(36)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("PaymentStatus") + .HasColumnType("int"); + + b.Property("PrintedAt") + .HasColumnType("datetime(6)"); + + b.Property("PrintedBy") + .HasColumnType("char(36)"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("TotalAmount") + .HasColumnType("decimal(65,30)"); + + b.HasKey("Id"); + + b.HasIndex("FK_ShopPOId"); + + b.ToTable("Invoices"); + + b.HasData( + new + { + Id = new Guid("34500000-0000-0000-0000-000000000001"), + CreatedAt = new DateTime(2026, 8, 25, 8, 30, 0, 0, DateTimeKind.Unspecified), + CreatedBy = new Guid("29000000-0000-0000-0000-000000000002"), + FK_ShopPOId = new Guid("33000000-0000-0000-0000-000000000001"), + FinalizedAt = new DateTime(2026, 8, 25, 9, 0, 0, 0, DateTimeKind.Unspecified), + FinalizedBy = new Guid("29000000-0000-0000-0000-000000000001"), + IsActive = true, + PaymentStatus = 2, + PrintedAt = new DateTime(2026, 8, 25, 8, 45, 0, 0, DateTimeKind.Unspecified), + PrintedBy = new Guid("29000000-0000-0000-0000-000000000002"), + Status = 2, + TotalAmount = 3000m + }, + new + { + Id = new Guid("34500000-0000-0000-0000-000000000002"), + CreatedAt = new DateTime(2026, 8, 31, 9, 0, 0, 0, DateTimeKind.Unspecified), + CreatedBy = new Guid("29000000-0000-0000-0000-000000000003"), + FK_ShopPOId = new Guid("33000000-0000-0000-0000-000000000003"), + IsActive = true, + PaymentStatus = 1, + PrintedAt = new DateTime(2026, 8, 31, 9, 15, 0, 0, DateTimeKind.Unspecified), + PrintedBy = new Guid("29000000-0000-0000-0000-000000000003"), + Status = 1, + TotalAmount = 2400m + }); + }); + + modelBuilder.Entity("Distribution.Models.Item", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("AllowCustomWarrantyPeriod") + .HasColumnType("tinyint(1)"); + + b.Property("CostPrice") + .HasColumnType("decimal(65,30)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Description") + .HasColumnType("longtext"); + + b.Property("ExternalErpItemId") + .HasColumnType("int"); + + b.Property("FK_BaseUomId") + .HasColumnType("char(36)"); + + b.Property("FK_BrandId") + .HasColumnType("char(36)"); + + b.Property("FK_CategoryId") + .HasColumnType("char(36)"); + + b.Property("FK_SubCategoryId") + .HasColumnType("char(36)"); + + b.Property("HasWarranty") + .HasColumnType("tinyint(1)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("LastSyncedAt") + .HasColumnType("datetime(6)"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("ReorderLevel") + .HasColumnType("decimal(65,30)"); + + b.Property("SalePrice") + .HasColumnType("decimal(65,30)"); + + b.Property("Sku") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("SyncSource") + .HasColumnType("int"); + + b.Property("TracksBatches") + .HasColumnType("tinyint(1)"); + + b.Property("TracksExpiryDate") + .HasColumnType("tinyint(1)"); + + b.Property("TracksSerialNumbers") + .HasColumnType("tinyint(1)"); + + b.Property("WarrantyPeriodDays") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("FK_BaseUomId"); + + b.HasIndex("FK_BrandId"); + + b.HasIndex("FK_CategoryId"); + + b.HasIndex("FK_SubCategoryId"); + + b.ToTable("Items"); + + b.HasData( + new + { + Id = new Guid("23000000-0000-0000-0000-000000000001"), + AllowCustomWarrantyPeriod = false, + CreatedAt = new DateTime(2026, 8, 20, 0, 0, 0, 0, DateTimeKind.Unspecified), + Description = "Coca-Cola 500ml bottle", + FK_BaseUomId = new Guid("22000000-0000-0000-0000-000000000001"), + FK_BrandId = new Guid("21000000-0000-0000-0000-000000000001"), + FK_CategoryId = new Guid("20000000-0000-0000-0000-000000000001"), + HasWarranty = false, + IsActive = true, + Name = "Coca-Cola 500ml", + SalePrice = 60m, + Sku = "BEV-COKE-500", + SyncSource = 0, + TracksBatches = false, + TracksExpiryDate = false, + TracksSerialNumbers = false + }, + new + { + Id = new Guid("23000000-0000-0000-0000-000000000002"), + AllowCustomWarrantyPeriod = false, + CreatedAt = new DateTime(2026, 8, 20, 0, 0, 0, 0, DateTimeKind.Unspecified), + Description = "Coca-Cola 1.5L bottle", + FK_BaseUomId = new Guid("22000000-0000-0000-0000-000000000001"), + FK_BrandId = new Guid("21000000-0000-0000-0000-000000000001"), + FK_CategoryId = new Guid("20000000-0000-0000-0000-000000000001"), + HasWarranty = false, + IsActive = true, + Name = "Coca-Cola 1.5L", + SalePrice = 120m, + Sku = "BEV-COKE-1500", + SyncSource = 0, + TracksBatches = false, + TracksExpiryDate = false, + TracksSerialNumbers = false + }, + new + { + Id = new Guid("23000000-0000-0000-0000-000000000003"), + AllowCustomWarrantyPeriod = false, + CreatedAt = new DateTime(2026, 8, 20, 0, 0, 0, 0, DateTimeKind.Unspecified), + Description = "Lays Classic Salted 52g pack", + FK_BaseUomId = new Guid("22000000-0000-0000-0000-000000000001"), + FK_BrandId = new Guid("21000000-0000-0000-0000-000000000002"), + FK_CategoryId = new Guid("20000000-0000-0000-0000-000000000002"), + HasWarranty = false, + IsActive = true, + Name = "Lays Classic 52g", + SalePrice = 50m, + Sku = "SNK-LAYS-52", + SyncSource = 0, + TracksBatches = false, + TracksExpiryDate = false, + TracksSerialNumbers = false + }, + new + { + Id = new Guid("23000000-0000-0000-0000-000000000004"), + AllowCustomWarrantyPeriod = false, + CreatedAt = new DateTime(2026, 8, 20, 0, 0, 0, 0, DateTimeKind.Unspecified), + Description = "Lays Family Pack 150g", + FK_BaseUomId = new Guid("22000000-0000-0000-0000-000000000001"), + FK_BrandId = new Guid("21000000-0000-0000-0000-000000000002"), + FK_CategoryId = new Guid("20000000-0000-0000-0000-000000000002"), + HasWarranty = false, + IsActive = true, + Name = "Lays Family Pack 150g", + SalePrice = 150m, + Sku = "SNK-LAYS-FAM", + SyncSource = 0, + TracksBatches = false, + TracksExpiryDate = false, + TracksSerialNumbers = false + }); + }); + + modelBuilder.Entity("Distribution.Models.ItemBatch", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("BatchNumber") + .HasColumnType("varchar(255)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("ExpiryDate") + .HasColumnType("datetime(6)"); + + b.Property("FK_ItemId") + .HasColumnType("char(36)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("ManufactureDate") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("FK_ItemId", "BatchNumber"); + + b.ToTable("ItemBatches"); + }); + + modelBuilder.Entity("Distribution.Models.ItemSerialNumber", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CurrentLocationId") + .HasColumnType("char(36)"); + + b.Property("CurrentLocationType") + .HasColumnType("int"); + + b.Property("FK_ItemId") + .HasColumnType("char(36)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("ReceivedAt") + .HasColumnType("datetime(6)"); + + b.Property("SerialNumber") + .IsRequired() + .HasColumnType("varchar(255)"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("WarrantyExpiresAt") + .HasColumnType("datetime(6)"); + + b.Property("WarrantyPeriodDaysOverride") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("FK_ItemId", "SerialNumber") + .IsUnique(); + + b.ToTable("ItemSerialNumbers"); + }); + + modelBuilder.Entity("Distribution.Models.LoadingSheet", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("ConfirmedAt") + .HasColumnType("datetime(6)"); + + b.Property("ConfirmedBy") + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("FK_RideId") + .HasColumnType("char(36)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("PopulatedAt") + .HasColumnType("datetime(6)"); + + b.Property("PopulatedBy") + .HasColumnType("char(36)"); + + b.Property("Status") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("FK_RideId"); + + b.ToTable("LoadingSheets"); + + b.HasData( + new + { + Id = new Guid("35000000-0000-0000-0000-000000000001"), + ConfirmedAt = new DateTime(2026, 8, 25, 7, 45, 0, 0, DateTimeKind.Unspecified), + ConfirmedBy = new Guid("29000000-0000-0000-0000-000000000001"), + CreatedAt = new DateTime(2026, 8, 25, 7, 30, 0, 0, DateTimeKind.Unspecified), + FK_RideId = new Guid("31000000-0000-0000-0000-000000000001"), + IsActive = true, + PopulatedAt = new DateTime(2026, 8, 25, 7, 30, 0, 0, DateTimeKind.Unspecified), + PopulatedBy = new Guid("29000000-0000-0000-0000-000000000001"), + Status = 1 + }); + }); + + modelBuilder.Entity("Distribution.Models.LoadingSheetItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("FK_ItemId") + .HasColumnType("char(36)"); + + b.Property("FK_LoadingSheetId") + .HasColumnType("char(36)"); + + b.Property("FK_ShopPOItemId") + .HasColumnType("char(36)"); + + b.Property("FK_WarehouseId") + .HasColumnType("char(36)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("QuantityDispatched") + .HasColumnType("decimal(65,30)"); + + b.HasKey("Id"); + + b.HasIndex("FK_ItemId"); + + b.HasIndex("FK_LoadingSheetId"); + + b.HasIndex("FK_ShopPOItemId"); + + b.HasIndex("FK_WarehouseId"); + + b.ToTable("LoadingSheetItems"); + + b.HasData( + new + { + Id = new Guid("36000000-0000-0000-0000-000000000001"), + CreatedAt = new DateTime(2026, 8, 25, 7, 30, 0, 0, DateTimeKind.Unspecified), + FK_ItemId = new Guid("23000000-0000-0000-0000-000000000001"), + FK_LoadingSheetId = new Guid("35000000-0000-0000-0000-000000000001"), + FK_WarehouseId = new Guid("27000000-0000-0000-0000-000000000001"), + IsActive = true, + QuantityDispatched = 50m + }, + new + { + Id = new Guid("36000000-0000-0000-0000-000000000002"), + CreatedAt = new DateTime(2026, 8, 25, 7, 30, 0, 0, DateTimeKind.Unspecified), + FK_ItemId = new Guid("23000000-0000-0000-0000-000000000003"), + FK_LoadingSheetId = new Guid("35000000-0000-0000-0000-000000000001"), + FK_WarehouseId = new Guid("27000000-0000-0000-0000-000000000001"), + IsActive = true, + QuantityDispatched = 30m + }); + }); + + modelBuilder.Entity("Distribution.Models.LoadingSheetOrder", b => + { + b.Property("FK_LoadingSheetId") + .HasColumnType("char(36)"); + + b.Property("FK_ShopPOId") + .HasColumnType("char(36)"); + + b.HasKey("FK_LoadingSheetId", "FK_ShopPOId"); + + b.HasIndex("FK_ShopPOId"); + + b.ToTable("LoadingSheetOrders"); + }); + + modelBuilder.Entity("Distribution.Models.Payment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("Amount") + .HasColumnType("decimal(65,30)"); + + b.Property("ChequeDate") + .HasColumnType("datetime(6)"); + + b.Property("ChequeNumber") + .HasColumnType("longtext"); + + b.Property("CollectedAt") + .HasColumnType("datetime(6)"); + + b.Property("CollectedBy") + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("EntryMode") + .HasColumnType("int"); + + b.Property("FK_InvoiceId") + .HasColumnType("char(36)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("Method") + .HasColumnType("int"); + + b.Property("RecordedBy") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("FK_InvoiceId"); + + b.ToTable("Payments"); + + b.HasData( + new + { + Id = new Guid("47000000-0000-0000-0000-000000000001"), + Amount = 3000m, + CollectedAt = new DateTime(2026, 8, 25, 9, 30, 0, 0, DateTimeKind.Unspecified), + CollectedBy = new Guid("29000000-0000-0000-0000-000000000002"), + CreatedAt = new DateTime(2026, 8, 25, 9, 30, 0, 0, DateTimeKind.Unspecified), + EntryMode = 0, + FK_InvoiceId = new Guid("34500000-0000-0000-0000-000000000001"), + IsActive = true, + Method = 0 + }, + new + { + Id = new Guid("47000000-0000-0000-0000-000000000002"), + Amount = 1000m, + CollectedAt = new DateTime(2026, 8, 31, 9, 20, 0, 0, DateTimeKind.Unspecified), + CollectedBy = new Guid("29000000-0000-0000-0000-000000000003"), + CreatedAt = new DateTime(2026, 8, 31, 9, 20, 0, 0, DateTimeKind.Unspecified), + EntryMode = 0, + FK_InvoiceId = new Guid("34500000-0000-0000-0000-000000000002"), + IsActive = true, + Method = 0 + }, + new + { + Id = new Guid("47000000-0000-0000-0000-000000000003"), + Amount = 800m, + ChequeDate = new DateTime(2026, 8, 31, 0, 0, 0, 0, DateTimeKind.Unspecified), + ChequeNumber = "000456", + CollectedAt = new DateTime(2026, 8, 31, 11, 0, 0, 0, DateTimeKind.Unspecified), + CollectedBy = new Guid("29000000-0000-0000-0000-000000000003"), + CreatedAt = new DateTime(2026, 8, 31, 18, 0, 0, 0, DateTimeKind.Unspecified), + EntryMode = 1, + FK_InvoiceId = new Guid("34500000-0000-0000-0000-000000000002"), + IsActive = true, + Method = 1, + RecordedBy = new Guid("29000000-0000-0000-0000-000000000001") + }, + new + { + Id = new Guid("47000000-0000-0000-0000-000000000004"), + Amount = 300m, + CollectedAt = new DateTime(2026, 8, 31, 11, 5, 0, 0, DateTimeKind.Unspecified), + CollectedBy = new Guid("29000000-0000-0000-0000-000000000003"), + CreatedAt = new DateTime(2026, 8, 31, 11, 5, 0, 0, DateTimeKind.Unspecified), + EntryMode = 0, + FK_InvoiceId = new Guid("34500000-0000-0000-0000-000000000002"), + IsActive = true, + Method = 2 + }); + }); + + modelBuilder.Entity("Distribution.Models.Permission", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("Code") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Description") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.HasKey("Id"); + + b.ToTable("Permissions"); + }); + + modelBuilder.Entity("Distribution.Models.PriceList", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("EffectiveFrom") + .HasColumnType("datetime(6)"); + + b.Property("EffectiveTo") + .HasColumnType("datetime(6)"); + + b.Property("FK_RegionId") + .HasColumnType("char(36)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.HasIndex("FK_RegionId"); + + b.ToTable("PriceLists"); + + b.HasData( + new + { + Id = new Guid("39000000-0000-0000-0000-000000000001"), + CreatedAt = new DateTime(2026, 8, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), + EffectiveFrom = new DateTime(2026, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), + EffectiveTo = new DateTime(2026, 12, 31, 0, 0, 0, 0, DateTimeKind.Unspecified), + FK_RegionId = new Guid("24000000-0000-0000-0000-000000000001"), + IsActive = true, + Name = "Western Province Price List" + }); + }); + + modelBuilder.Entity("Distribution.Models.PriceListItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("AgencyMarginPercent") + .HasColumnType("decimal(65,30)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("FK_ItemId") + .HasColumnType("char(36)"); + + b.Property("FK_PriceListId") + .HasColumnType("char(36)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("MinPrice") + .HasColumnType("decimal(65,30)"); + + b.Property("Price") + .HasColumnType("decimal(65,30)"); + + b.Property("RetailPrice") + .HasColumnType("decimal(65,30)"); + + b.HasKey("Id"); + + b.HasIndex("FK_ItemId"); + + b.HasIndex("FK_PriceListId", "FK_ItemId") + .IsUnique(); + + b.ToTable("PriceListItems"); + + b.HasData( + new + { + Id = new Guid("40000000-0000-0000-0000-000000000001"), + CreatedAt = new DateTime(2026, 8, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), + FK_ItemId = new Guid("23000000-0000-0000-0000-000000000001"), + FK_PriceListId = new Guid("39000000-0000-0000-0000-000000000001"), + IsActive = true, + MinPrice = 55m, + Price = 60m + }, + new + { + Id = new Guid("40000000-0000-0000-0000-000000000002"), + CreatedAt = new DateTime(2026, 8, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), + FK_ItemId = new Guid("23000000-0000-0000-0000-000000000003"), + FK_PriceListId = new Guid("39000000-0000-0000-0000-000000000001"), + IsActive = true, + MinPrice = 45m, + Price = 50m + }); + }); + + modelBuilder.Entity("Distribution.Models.PurchaseOrder", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CancelReason") + .HasColumnType("longtext"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("FK_CreatedByUserId") + .HasColumnType("char(36)"); + + b.Property("FK_SupplierId") + .HasColumnType("char(36)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("Status") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("FK_SupplierId"); + + b.ToTable("PurchaseOrders"); + + b.HasData( + new + { + Id = new Guid("41000000-0000-0000-0000-000000000001"), + CreatedAt = new DateTime(2026, 8, 14, 0, 0, 0, 0, DateTimeKind.Unspecified), + FK_CreatedByUserId = new Guid("29000000-0000-0000-0000-000000000001"), + FK_SupplierId = new Guid("38000000-0000-0000-0000-000000000001"), + IsActive = true, + Status = 2 + }, + new + { + Id = new Guid("41000000-0000-0000-0000-000000000002"), + CreatedAt = new DateTime(2026, 8, 14, 0, 0, 0, 0, DateTimeKind.Unspecified), + FK_CreatedByUserId = new Guid("29000000-0000-0000-0000-000000000001"), + FK_SupplierId = new Guid("38000000-0000-0000-0000-000000000002"), + IsActive = true, + Status = 2 + }); + }); + + modelBuilder.Entity("Distribution.Models.PurchaseOrderItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CancelledQuantity") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("FK_ItemId") + .HasColumnType("char(36)"); + + b.Property("FK_PurchaseOrderId") + .HasColumnType("char(36)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("IsCancelled") + .HasColumnType("tinyint(1)"); + + b.Property("Quantity") + .HasColumnType("int"); + + b.Property("ReceivedQuantity") + .HasColumnType("int"); + + b.Property("UnitCost") + .HasColumnType("decimal(65,30)"); + + b.HasKey("Id"); + + b.HasIndex("FK_ItemId"); + + b.HasIndex("FK_PurchaseOrderId"); + + b.ToTable("PurchaseOrderItems"); + + b.HasData( + new + { + Id = new Guid("42000000-0000-0000-0000-000000000001"), + CancelledQuantity = 0, + CreatedAt = new DateTime(2026, 8, 14, 0, 0, 0, 0, DateTimeKind.Unspecified), + FK_ItemId = new Guid("23000000-0000-0000-0000-000000000001"), + FK_PurchaseOrderId = new Guid("41000000-0000-0000-0000-000000000001"), + IsActive = true, + IsCancelled = false, + Quantity = 200, + ReceivedQuantity = 200, + UnitCost = 40m + }, + new + { + Id = new Guid("42000000-0000-0000-0000-000000000002"), + CancelledQuantity = 0, + CreatedAt = new DateTime(2026, 8, 14, 0, 0, 0, 0, DateTimeKind.Unspecified), + FK_ItemId = new Guid("23000000-0000-0000-0000-000000000002"), + FK_PurchaseOrderId = new Guid("41000000-0000-0000-0000-000000000001"), + IsActive = true, + IsCancelled = false, + Quantity = 100, + ReceivedQuantity = 98, + UnitCost = 80m + }, + new + { + Id = new Guid("42000000-0000-0000-0000-000000000003"), + CancelledQuantity = 0, + CreatedAt = new DateTime(2026, 8, 14, 0, 0, 0, 0, DateTimeKind.Unspecified), + FK_ItemId = new Guid("23000000-0000-0000-0000-000000000003"), + FK_PurchaseOrderId = new Guid("41000000-0000-0000-0000-000000000002"), + IsActive = true, + IsCancelled = false, + Quantity = 150, + ReceivedQuantity = 150, + UnitCost = 35m + }); + }); + + modelBuilder.Entity("Distribution.Models.PurchaseReturn", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("FK_PurchaseOrderId") + .HasColumnType("char(36)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("Reason") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.HasIndex("FK_PurchaseOrderId"); + + b.ToTable("PurchaseReturns"); + + b.HasData( + new + { + Id = new Guid("45000000-0000-0000-0000-000000000001"), + CreatedAt = new DateTime(2026, 8, 16, 0, 0, 0, 0, DateTimeKind.Unspecified), + FK_PurchaseOrderId = new Guid("41000000-0000-0000-0000-000000000002"), + IsActive = true, + Reason = "Damaged in transit" + }); + }); + + modelBuilder.Entity("Distribution.Models.PurchaseReturnItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("FK_ItemId") + .HasColumnType("char(36)"); + + b.Property("FK_PurchaseReturnId") + .HasColumnType("char(36)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("Quantity") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("FK_ItemId"); + + b.HasIndex("FK_PurchaseReturnId"); + + b.ToTable("PurchaseReturnItems"); + + b.HasData( + new + { + Id = new Guid("46000000-0000-0000-0000-000000000001"), + CreatedAt = new DateTime(2026, 8, 16, 0, 0, 0, 0, DateTimeKind.Unspecified), + FK_ItemId = new Guid("23000000-0000-0000-0000-000000000003"), + FK_PurchaseReturnId = new Guid("45000000-0000-0000-0000-000000000001"), + IsActive = true, + Quantity = 10 + }); + }); + + modelBuilder.Entity("Distribution.Models.Region", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.HasIndex("Code") + .IsUnique(); + + b.ToTable("Regions"); + + b.HasData( + new + { + Id = new Guid("24000000-0000-0000-0000-000000000001"), + Code = "REG-0001", + CreatedAt = new DateTime(2026, 8, 20, 0, 0, 0, 0, DateTimeKind.Unspecified), + IsActive = true, + Name = "Western Province" + }, + new + { + Id = new Guid("24000000-0000-0000-0000-000000000002"), + Code = "REG-0002", + CreatedAt = new DateTime(2026, 8, 20, 0, 0, 0, 0, DateTimeKind.Unspecified), + IsActive = true, + Name = "Central Province" + }); + }); + + modelBuilder.Entity("Distribution.Models.RegionAgencyMap", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("FK_AgencyUserId") + .HasColumnType("char(36)"); + + b.Property("FK_RegionId") + .HasColumnType("char(36)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.HasKey("Id"); + + b.HasIndex("FK_AgencyUserId"); + + b.HasIndex("FK_RegionId", "FK_AgencyUserId") + .IsUnique(); + + b.ToTable("RegionAgencyMaps"); + + b.HasData( + new + { + Id = new Guid("57000000-0000-0000-0000-000000000001"), + CreatedAt = new DateTime(2026, 8, 20, 0, 0, 0, 0, DateTimeKind.Unspecified), + FK_AgencyUserId = new Guid("70000000-0000-0000-0000-000000000001"), + FK_RegionId = new Guid("24000000-0000-0000-0000-000000000001"), + IsActive = true + }); + }); + + modelBuilder.Entity("Distribution.Models.RegionalBudget", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("BudgetAmount") + .HasColumnType("decimal(65,30)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("FK_RegionId") + .HasColumnType("char(36)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("Period") + .IsRequired() + .HasColumnType("varchar(255)"); + + b.HasKey("Id"); + + b.HasIndex("FK_RegionId", "Period") + .IsUnique(); + + b.ToTable("RegionalBudgets"); + + b.HasData( + new + { + Id = new Guid("58000000-0000-0000-0000-000000000001"), + BudgetAmount = 200000m, + CreatedAt = new DateTime(2026, 8, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), + FK_RegionId = new Guid("24000000-0000-0000-0000-000000000001"), + IsActive = true, + Period = "2026-08" + }); + }); + + modelBuilder.Entity("Distribution.Models.RegionalTarget", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("FK_RegionId") + .HasColumnType("char(36)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("Period") + .IsRequired() + .HasColumnType("varchar(255)"); + + b.Property("TargetValue") + .HasColumnType("decimal(65,30)"); + + b.HasKey("Id"); + + b.HasIndex("FK_RegionId", "Period") + .IsUnique(); + + b.ToTable("RegionalTargets"); + + b.HasData( + new + { + Id = new Guid("59000000-0000-0000-0000-000000000001"), + CreatedAt = new DateTime(2026, 8, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), + FK_RegionId = new Guid("24000000-0000-0000-0000-000000000001"), + IsActive = true, + Period = "2026-08", + TargetValue = 500000m + }); + }); + + modelBuilder.Entity("Distribution.Models.RegionalTaxRule", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("FK_RegionId") + .HasColumnType("char(36)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("RuleConfig") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.HasIndex("FK_RegionId") + .IsUnique(); + + b.ToTable("RegionalTaxRules"); + + b.HasData( + new + { + Id = new Guid("60000000-0000-0000-0000-000000000001"), + CreatedAt = new DateTime(2026, 8, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), + FK_RegionId = new Guid("24000000-0000-0000-0000-000000000001"), + IsActive = true, + RuleConfig = "{\"rate\": 0.15}" + }); + }); + + modelBuilder.Entity("Distribution.Models.Role", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("Level") + .HasColumnType("int"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.ToTable("Roles"); + + b.HasData( + new + { + Id = new Guid("10000000-0000-0000-0000-000000000001"), + CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), + IsActive = true, + Level = 10, + Name = "SalesRep" + }, + new + { + Id = new Guid("10000000-0000-0000-0000-000000000002"), + CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), + IsActive = true, + Level = 20, + Name = "Supervisor" + }, + new + { + Id = new Guid("10000000-0000-0000-0000-000000000003"), + CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), + IsActive = true, + Level = 30, + Name = "AreaManager" + }, + new + { + Id = new Guid("10000000-0000-0000-0000-000000000004"), + CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), + IsActive = true, + Level = 40, + Name = "AreaSupervisor" + }, + new + { + Id = new Guid("10000000-0000-0000-0000-000000000005"), + CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), + IsActive = true, + Level = 50, + Name = "RegionalManager" + }, + new + { + Id = new Guid("10000000-0000-0000-0000-000000000006"), + CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), + IsActive = true, + Level = 60, + Name = "RegionalAgency" + }); + }); + + modelBuilder.Entity("Distribution.Models.RolePermission", b => + { + b.Property("FK_RoleId") + .HasColumnType("char(36)"); + + b.Property("FK_PermissionId") + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.HasKey("FK_RoleId", "FK_PermissionId"); + + b.HasIndex("FK_PermissionId"); + + b.ToTable("RolePermissions"); + }); + + modelBuilder.Entity("Distribution.Models.Route", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("EndPoint") + .HasColumnType("longtext"); + + b.Property("FK_OwnerUserId") + .HasColumnType("char(36)"); + + b.Property("FK_RegionId") + .HasColumnType("char(36)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("Length") + .HasColumnType("decimal(65,30)"); + + b.Property("RouteName") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("StartPoint") + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.HasIndex("FK_OwnerUserId"); + + b.HasIndex("FK_RegionId"); + + b.ToTable("Routes"); + + b.HasData( + new + { + Id = new Guid("25000000-0000-0000-0000-000000000001"), + CreatedAt = new DateTime(2026, 8, 20, 0, 0, 0, 0, DateTimeKind.Unspecified), + EndPoint = "Dehiwala", + FK_OwnerUserId = new Guid("29000000-0000-0000-0000-000000000002"), + FK_RegionId = new Guid("24000000-0000-0000-0000-000000000001"), + IsActive = true, + Length = 18.5m, + RouteName = "Colombo City Route", + StartPoint = "Colombo Fort" + }, + new + { + Id = new Guid("25000000-0000-0000-0000-000000000002"), + CreatedAt = new DateTime(2026, 8, 20, 0, 0, 0, 0, DateTimeKind.Unspecified), + EndPoint = "Negombo", + FK_OwnerUserId = new Guid("29000000-0000-0000-0000-000000000002"), + FK_RegionId = new Guid("24000000-0000-0000-0000-000000000001"), + IsActive = true, + Length = 32.0m, + RouteName = "Negombo Coastal Route", + StartPoint = "Wattala" + }, + new + { + Id = new Guid("25000000-0000-0000-0000-000000000003"), + CreatedAt = new DateTime(2026, 8, 20, 0, 0, 0, 0, DateTimeKind.Unspecified), + EndPoint = "Peradeniya", + FK_OwnerUserId = new Guid("29000000-0000-0000-0000-000000000003"), + FK_RegionId = new Guid("24000000-0000-0000-0000-000000000002"), + IsActive = true, + Length = 12.0m, + RouteName = "Kandy Town Route", + StartPoint = "Kandy Lake" + }, + new + { + Id = new Guid("25000000-0000-0000-0000-000000000004"), + CreatedAt = new DateTime(2026, 8, 20, 0, 0, 0, 0, DateTimeKind.Unspecified), + EndPoint = "Matale", + FK_OwnerUserId = new Guid("29000000-0000-0000-0000-000000000003"), + FK_RegionId = new Guid("24000000-0000-0000-0000-000000000002"), + IsActive = true, + Length = 26.0m, + RouteName = "Matale Route", + StartPoint = "Kandy" + }); + }); + + modelBuilder.Entity("Distribution.Models.RouteVisitLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CheckInAt") + .HasColumnType("datetime(6)"); + + b.Property("CheckInLatitude") + .HasColumnType("decimal(65,30)"); + + b.Property("CheckInLongitude") + .HasColumnType("decimal(65,30)"); + + b.Property("CheckOutAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("FK_RouteId") + .HasColumnType("char(36)"); + + b.Property("FK_StoreId") + .HasColumnType("char(36)"); + + b.Property("FK_UserId") + .HasColumnType("char(36)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("IsDeviationFlagged") + .HasColumnType("tinyint(1)"); + + b.HasKey("Id"); + + b.HasIndex("FK_RouteId"); + + b.HasIndex("FK_StoreId"); + + b.HasIndex("FK_UserId"); + + b.ToTable("RouteVisitLogs"); + + b.HasData( + new + { + Id = new Guid("66000000-0000-0000-0000-000000000001"), + CheckInAt = new DateTime(2026, 8, 25, 9, 0, 0, 0, DateTimeKind.Unspecified), + CheckInLatitude = 6.9344m, + CheckInLongitude = 79.8428m, + CheckOutAt = new DateTime(2026, 8, 25, 9, 25, 0, 0, DateTimeKind.Unspecified), + CreatedAt = new DateTime(2026, 8, 25, 9, 0, 0, 0, DateTimeKind.Unspecified), + FK_RouteId = new Guid("25000000-0000-0000-0000-000000000001"), + FK_StoreId = new Guid("26000000-0000-0000-0000-000000000001"), + FK_UserId = new Guid("29000000-0000-0000-0000-000000000002"), + IsActive = true, + IsDeviationFlagged = false + }, + new + { + Id = new Guid("66000000-0000-0000-0000-000000000002"), + CheckInAt = new DateTime(2026, 8, 25, 10, 0, 0, 0, DateTimeKind.Unspecified), + CheckInLatitude = 6.9000m, + CheckInLongitude = 79.8654m, + CreatedAt = new DateTime(2026, 8, 25, 10, 0, 0, 0, DateTimeKind.Unspecified), + FK_RouteId = new Guid("25000000-0000-0000-0000-000000000001"), + FK_StoreId = new Guid("26000000-0000-0000-0000-000000000002"), + FK_UserId = new Guid("29000000-0000-0000-0000-000000000002"), + IsActive = true, + IsDeviationFlagged = true + }); + }); + + modelBuilder.Entity("Distribution.Models.SaleReturn", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedByUserId") + .HasColumnType("char(36)"); + + b.Property("FK_ShopPOId") + .HasColumnType("char(36)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("Mode") + .HasColumnType("int"); + + b.Property("TotalCreditAmount") + .HasColumnType("decimal(65,30)"); + + b.HasKey("Id"); + + b.HasIndex("CreatedByUserId"); + + b.HasIndex("FK_ShopPOId"); + + b.ToTable("SaleReturns"); + + b.HasData( + new + { + Id = new Guid("51000000-0000-0000-0000-000000000001"), + CreatedAt = new DateTime(2026, 8, 26, 0, 0, 0, 0, DateTimeKind.Unspecified), + CreatedByUserId = new Guid("29000000-0000-0000-0000-000000000002"), + FK_ShopPOId = new Guid("33000000-0000-0000-0000-000000000001"), + IsActive = true, + Mode = 0, + TotalCreditAmount = 300m + }); + }); + + modelBuilder.Entity("Distribution.Models.SaleReturnItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("FK_ItemId") + .HasColumnType("char(36)"); + + b.Property("FK_SaleReturnId") + .HasColumnType("char(36)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("Quantity") + .HasColumnType("decimal(65,30)"); + + b.Property("ReasonCode") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasColumnType("decimal(65,30)"); + + b.HasKey("Id"); + + b.HasIndex("FK_ItemId"); + + b.HasIndex("FK_SaleReturnId"); + + b.ToTable("SaleReturnItems"); + + b.HasData( + new + { + Id = new Guid("52000000-0000-0000-0000-000000000001"), + CreatedAt = new DateTime(2026, 8, 26, 0, 0, 0, 0, DateTimeKind.Unspecified), + FK_ItemId = new Guid("23000000-0000-0000-0000-000000000001"), + FK_SaleReturnId = new Guid("51000000-0000-0000-0000-000000000001"), + IsActive = true, + Quantity = 5m, + ReasonCode = 1, + UnitPrice = 60m + }); + }); + + modelBuilder.Entity("Distribution.Models.SaleRoute", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.HasKey("Id"); + + b.ToTable("SaleRoutes"); + + b.HasData( + new + { + Id = new Guid("69000000-0000-0000-0000-000000000001"), + CreatedAt = new DateTime(2026, 8, 20, 0, 0, 0, 0, DateTimeKind.Unspecified), + IsActive = true + }, + new + { + Id = new Guid("69000000-0000-0000-0000-000000000002"), + CreatedAt = new DateTime(2026, 8, 20, 0, 0, 0, 0, DateTimeKind.Unspecified), + IsActive = true + }); + }); + + modelBuilder.Entity("Distribution.Models.Scheme", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("AgentLimitExempt") + .HasColumnType("tinyint(1)"); + + b.Property("BaselineExpectedSales") + .HasColumnType("decimal(65,30)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("DateRangeFrom") + .HasColumnType("datetime(6)"); + + b.Property("DateRangeTo") + .HasColumnType("datetime(6)"); + + b.Property("EligibleSegment") + .HasColumnType("longtext"); + + b.Property("FK_FreeItemId") + .HasColumnType("char(36)"); + + b.Property("FK_RegionId") + .HasColumnType("char(36)"); + + b.Property("FK_TriggerItemId") + .HasColumnType("char(36)"); + + b.Property("FreeQty") + .HasColumnType("decimal(65,30)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("RegionId") + .HasColumnType("char(36)"); + + b.Property("RuleConfig") + .HasColumnType("longtext"); + + b.Property("RuleType") + .HasColumnType("int"); + + b.Property("TriggerQty") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("FK_FreeItemId"); + + b.HasIndex("FK_TriggerItemId"); + + b.HasIndex("RegionId"); + + b.ToTable("Schemes"); + + b.HasData( + new + { + Id = new Guid("48000000-0000-0000-0000-000000000001"), + AgentLimitExempt = false, + BaselineExpectedSales = 500000m, + CreatedAt = new DateTime(2026, 8, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), + DateRangeFrom = new DateTime(2026, 8, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), + DateRangeTo = new DateTime(2026, 12, 31, 0, 0, 0, 0, DateTimeKind.Unspecified), + FK_FreeItemId = new Guid("23000000-0000-0000-0000-000000000001"), + FK_RegionId = new Guid("24000000-0000-0000-0000-000000000001"), + FK_TriggerItemId = new Guid("23000000-0000-0000-0000-000000000001"), + FreeQty = 1m, + IsActive = true, + Name = "Buy 10 Coke Get 1 Free", + RuleType = 0, + TriggerQty = 10 + }, + new + { + Id = new Guid("48000000-0000-0000-0000-000000000002"), + AgentLimitExempt = false, + CreatedAt = new DateTime(2026, 8, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), + DateRangeFrom = new DateTime(2026, 8, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), + DateRangeTo = new DateTime(2026, 12, 31, 0, 0, 0, 0, DateTimeKind.Unspecified), + FK_FreeItemId = new Guid("23000000-0000-0000-0000-000000000004"), + FK_RegionId = new Guid("24000000-0000-0000-0000-000000000002"), + FreeQty = 1m, + IsActive = true, + Name = "Snack Combo Bundle", + RuleType = 3 + }); + }); + + modelBuilder.Entity("Distribution.Models.SchemeBundleComponent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("FK_ItemId") + .HasColumnType("char(36)"); + + b.Property("FK_SchemeId") + .HasColumnType("char(36)"); + + b.Property("Qty") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("FK_ItemId"); + + b.HasIndex("FK_SchemeId", "FK_ItemId") + .IsUnique(); + + b.ToTable("SchemeBundleComponents"); + + b.HasData( + new + { + Id = new Guid("49000000-0000-0000-0000-000000000001"), + FK_ItemId = new Guid("23000000-0000-0000-0000-000000000003"), + FK_SchemeId = new Guid("48000000-0000-0000-0000-000000000002"), + Qty = 5 + }); + }); + + modelBuilder.Entity("Distribution.Models.SchemeRedemption", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("DiscountAmount") + .HasColumnType("decimal(65,30)"); + + b.Property("FK_FreeItemId") + .HasColumnType("char(36)"); + + b.Property("FK_SchemeId") + .HasColumnType("char(36)"); + + b.Property("FK_ShopPOId") + .HasColumnType("char(36)"); + + b.Property("FieldInvoiceId") + .HasColumnType("char(36)"); + + b.Property("FreeItemCostValue") + .HasColumnType("decimal(65,30)"); + + b.Property("FreeQty") + .HasColumnType("decimal(65,30)"); + + b.Property("RedeemedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("FK_FreeItemId"); + + b.HasIndex("FK_SchemeId"); + + b.HasIndex("FK_ShopPOId"); + + b.ToTable("SchemeRedemptions"); + + b.HasData( + new + { + Id = new Guid("50000000-0000-0000-0000-000000000001"), + DiscountAmount = 0m, + FK_FreeItemId = new Guid("23000000-0000-0000-0000-000000000001"), + FK_SchemeId = new Guid("48000000-0000-0000-0000-000000000001"), + FK_ShopPOId = new Guid("33000000-0000-0000-0000-000000000001"), + FreeItemCostValue = 40m, + FreeQty = 1m, + RedeemedAt = new DateTime(2026, 8, 25, 9, 30, 0, 0, DateTimeKind.Unspecified) + }); + }); + + modelBuilder.Entity("Distribution.Models.ShopPO", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CancelReason") + .HasColumnType("longtext"); + + b.Property("CancelledAt") + .HasColumnType("datetime(6)"); + + b.Property("CancelledBy") + .HasColumnType("char(36)"); + + b.Property("ConfirmedAt") + .HasColumnType("datetime(6)"); + + b.Property("ConfirmedBy") + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("FK_StoreId") + .HasColumnType("char(36)"); + + b.Property("FulfilledAt") + .HasColumnType("datetime(6)"); + + b.Property("InvoiceId") + .HasColumnType("char(36)") + .HasColumnName("InvoiceId"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("RaisedById") + .HasColumnType("char(36)"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("TotalAmount") + .HasColumnType("decimal(65,30)"); + + b.HasKey("Id"); + + b.HasIndex("FK_StoreId"); + + b.ToTable("ShopPOs"); + + b.HasData( + new + { + Id = new Guid("33000000-0000-0000-0000-000000000001"), + ConfirmedAt = new DateTime(2026, 8, 25, 8, 30, 0, 0, DateTimeKind.Unspecified), + ConfirmedBy = new Guid("29000000-0000-0000-0000-000000000001"), + CreatedAt = new DateTime(2026, 8, 25, 9, 0, 0, 0, DateTimeKind.Unspecified), + FK_StoreId = new Guid("26000000-0000-0000-0000-000000000001"), + FulfilledAt = new DateTime(2026, 8, 25, 13, 0, 0, 0, DateTimeKind.Unspecified), + InvoiceId = new Guid("34500000-0000-0000-0000-000000000001"), + IsActive = true, + RaisedById = new Guid("29000000-0000-0000-0000-000000000002"), + Status = 2, + TotalAmount = 3000m + }, + new + { + Id = new Guid("33000000-0000-0000-0000-000000000002"), + CreatedAt = new DateTime(2026, 8, 30, 10, 0, 0, 0, DateTimeKind.Unspecified), + FK_StoreId = new Guid("26000000-0000-0000-0000-000000000005"), + IsActive = true, + RaisedById = new Guid("29000000-0000-0000-0000-000000000003"), + Status = 0, + TotalAmount = 1200m + }, + new + { + Id = new Guid("33000000-0000-0000-0000-000000000003"), + ConfirmedAt = new DateTime(2026, 8, 31, 8, 30, 0, 0, DateTimeKind.Unspecified), + ConfirmedBy = new Guid("29000000-0000-0000-0000-000000000001"), + CreatedAt = new DateTime(2026, 8, 31, 9, 0, 0, 0, DateTimeKind.Unspecified), + FK_StoreId = new Guid("26000000-0000-0000-0000-000000000005"), + FulfilledAt = new DateTime(2026, 8, 31, 13, 0, 0, 0, DateTimeKind.Unspecified), + InvoiceId = new Guid("34500000-0000-0000-0000-000000000002"), + IsActive = true, + RaisedById = new Guid("29000000-0000-0000-0000-000000000003"), + Status = 2, + TotalAmount = 2400m + }); + }); + + modelBuilder.Entity("Distribution.Models.ShopPOItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("FK_ItemId") + .HasColumnType("char(36)"); + + b.Property("FK_ShopPOId") + .HasColumnType("char(36)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("Quantity") + .HasColumnType("int"); + + b.Property("QuantityFulfilled") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasColumnType("decimal(65,30)"); + + b.HasKey("Id"); + + b.HasIndex("FK_ItemId"); + + b.HasIndex("FK_ShopPOId"); + + b.ToTable("ShopPOItems"); + + b.HasData( + new + { + Id = new Guid("34000000-0000-0000-0000-000000000001"), + CreatedAt = new DateTime(2026, 8, 25, 9, 0, 0, 0, DateTimeKind.Unspecified), + FK_ItemId = new Guid("23000000-0000-0000-0000-000000000001"), + FK_ShopPOId = new Guid("33000000-0000-0000-0000-000000000001"), + IsActive = true, + Quantity = 30, + QuantityFulfilled = 30, + UnitPrice = 60m + }, + new + { + Id = new Guid("34000000-0000-0000-0000-000000000002"), + CreatedAt = new DateTime(2026, 8, 25, 9, 0, 0, 0, DateTimeKind.Unspecified), + FK_ItemId = new Guid("23000000-0000-0000-0000-000000000003"), + FK_ShopPOId = new Guid("33000000-0000-0000-0000-000000000001"), + IsActive = true, + Quantity = 24, + QuantityFulfilled = 24, + UnitPrice = 50m + }, + new + { + Id = new Guid("34000000-0000-0000-0000-000000000003"), + CreatedAt = new DateTime(2026, 8, 30, 10, 0, 0, 0, DateTimeKind.Unspecified), + FK_ItemId = new Guid("23000000-0000-0000-0000-000000000002"), + FK_ShopPOId = new Guid("33000000-0000-0000-0000-000000000002"), + IsActive = true, + Quantity = 10, + QuantityFulfilled = 0, + UnitPrice = 120m + }, + new + { + Id = new Guid("34000000-0000-0000-0000-000000000004"), + CreatedAt = new DateTime(2026, 8, 31, 9, 0, 0, 0, DateTimeKind.Unspecified), + FK_ItemId = new Guid("23000000-0000-0000-0000-000000000002"), + FK_ShopPOId = new Guid("33000000-0000-0000-0000-000000000003"), + IsActive = true, + Quantity = 20, + QuantityFulfilled = 20, + UnitPrice = 120m + }); + }); + + modelBuilder.Entity("Distribution.Models.StockBalance", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("FK_ItemBatchId") + .HasColumnType("char(36)"); + + b.Property("FK_ItemId") + .HasColumnType("char(36)"); + + b.Property("FK_ItemSerialNumberId") + .HasColumnType("char(36)"); + + b.Property("LocationId") + .HasColumnType("char(36)"); + + b.Property("LocationType") + .HasColumnType("int"); + + b.Property("Quantity") + .HasColumnType("decimal(65,30)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("FK_ItemBatchId"); + + b.HasIndex("FK_ItemSerialNumberId"); + + b.HasIndex("FK_ItemId", "LocationId", "FK_ItemBatchId", "FK_ItemSerialNumberId") + .IsUnique(); + + b.ToTable("StockBalances"); + + b.HasData( + new + { + Id = new Guid("61000000-0000-0000-0000-000000000001"), + FK_ItemId = new Guid("23000000-0000-0000-0000-000000000001"), + LocationId = new Guid("27000000-0000-0000-0000-000000000001"), + LocationType = 0, + Quantity = 150m, + UpdatedAt = new DateTime(2026, 8, 25, 9, 30, 0, 0, DateTimeKind.Unspecified) + }, + new + { + Id = new Guid("61000000-0000-0000-0000-000000000002"), + FK_ItemId = new Guid("23000000-0000-0000-0000-000000000003"), + LocationId = new Guid("27000000-0000-0000-0000-000000000001"), + LocationType = 0, + Quantity = 90m, + UpdatedAt = new DateTime(2026, 8, 25, 9, 30, 0, 0, DateTimeKind.Unspecified) + }, + new + { + Id = new Guid("61000000-0000-0000-0000-000000000003"), + FK_ItemId = new Guid("23000000-0000-0000-0000-000000000001"), + LocationId = new Guid("28000000-0000-0000-0000-000000000001"), + LocationType = 1, + Quantity = 20m, + UpdatedAt = new DateTime(2026, 8, 25, 9, 30, 0, 0, DateTimeKind.Unspecified) + }); + }); + + modelBuilder.Entity("Distribution.Models.StockMovement", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .HasColumnType("char(36)"); + + b.Property("FK_ItemBatchId") + .HasColumnType("char(36)"); + + b.Property("FK_ItemId") + .HasColumnType("char(36)"); + + b.Property("FK_ItemSerialNumberId") + .HasColumnType("char(36)"); + + b.Property("FromLocationId") + .HasColumnType("char(36)"); + + b.Property("FromLocationType") + .HasColumnType("int"); + + b.Property("Quantity") + .HasColumnType("decimal(65,30)"); + + b.Property("ReasonCode") + .HasColumnType("longtext"); + + b.Property("ReferenceId") + .HasColumnType("char(36)"); + + b.Property("ToLocationId") + .HasColumnType("char(36)"); + + b.Property("ToLocationType") + .HasColumnType("int"); + + b.Property("Type") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("FK_ItemBatchId"); + + b.HasIndex("FK_ItemId"); + + b.HasIndex("FK_ItemSerialNumberId"); + + b.ToTable("StockMovements"); + + b.HasData( + new + { + Id = new Guid("62000000-0000-0000-0000-000000000001"), + CreatedAt = new DateTime(2026, 8, 15, 0, 0, 0, 0, DateTimeKind.Unspecified), + CreatedBy = new Guid("29000000-0000-0000-0000-000000000001"), + FK_ItemId = new Guid("23000000-0000-0000-0000-000000000001"), + FromLocationId = new Guid("00000000-0000-0000-0000-000000000000"), + FromLocationType = 2, + Quantity = 200m, + ReferenceId = new Guid("43000000-0000-0000-0000-000000000001"), + ToLocationId = new Guid("27000000-0000-0000-0000-000000000001"), + ToLocationType = 0, + Type = 6 + }, + new + { + Id = new Guid("62000000-0000-0000-0000-000000000002"), + CreatedAt = new DateTime(2026, 8, 25, 7, 30, 0, 0, DateTimeKind.Unspecified), + CreatedBy = new Guid("29000000-0000-0000-0000-000000000001"), + FK_ItemId = new Guid("23000000-0000-0000-0000-000000000001"), + FromLocationId = new Guid("27000000-0000-0000-0000-000000000001"), + FromLocationType = 0, + Quantity = 50m, + ReferenceId = new Guid("35000000-0000-0000-0000-000000000001"), + ToLocationId = new Guid("28000000-0000-0000-0000-000000000001"), + ToLocationType = 1, + Type = 5 + }, + new + { + Id = new Guid("62000000-0000-0000-0000-000000000003"), + CreatedAt = new DateTime(2026, 8, 25, 9, 30, 0, 0, DateTimeKind.Unspecified), + CreatedBy = new Guid("29000000-0000-0000-0000-000000000002"), + FK_ItemId = new Guid("23000000-0000-0000-0000-000000000001"), + FromLocationId = new Guid("28000000-0000-0000-0000-000000000001"), + FromLocationType = 1, + Quantity = 30m, + ReferenceId = new Guid("32000000-0000-0000-0000-000000000001"), + ToLocationId = new Guid("00000000-0000-0000-0000-000000000000"), + ToLocationType = 2, + Type = 0 + }); + }); + + modelBuilder.Entity("Distribution.Models.Store", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("ContactNumber") + .HasColumnType("longtext"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreditAmount") + .HasColumnType("decimal(65,30)"); + + b.Property("CreditLimit") + .HasColumnType("decimal(65,30)"); + + b.Property("FK_RouteId") + .HasColumnType("char(36)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("Latitude") + .HasColumnType("decimal(65,30)"); + + b.Property("Longitude") + .HasColumnType("decimal(65,30)"); + + b.Property("OneTimeAmount") + .HasColumnType("decimal(65,30)"); + + b.Property("OutstandingBalance") + .HasColumnType("decimal(65,30)"); + + b.Property("OwnerName") + .HasColumnType("longtext"); + + b.Property("Segment") + .HasColumnType("longtext"); + + b.Property("StoreAddress") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("StoreName") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.HasIndex("FK_RouteId"); + + b.ToTable("Stores"); + + b.HasData( + new + { + Id = new Guid("26000000-0000-0000-0000-000000000001"), + CreatedAt = new DateTime(2026, 8, 20, 0, 0, 0, 0, DateTimeKind.Unspecified), + CreditAmount = 0m, + CreditLimit = 50000m, + FK_RouteId = new Guid("25000000-0000-0000-0000-000000000001"), + IsActive = true, + Latitude = 6.9344m, + Longitude = 79.8428m, + OneTimeAmount = 0m, + OutstandingBalance = 0m, + OwnerName = "Saman Kumara", + Segment = "Gold", + StoreAddress = "12 York Street, Colombo Fort", + StoreName = "Fort Mini Mart" + }, + new + { + Id = new Guid("26000000-0000-0000-0000-000000000002"), + CreatedAt = new DateTime(2026, 8, 20, 0, 0, 0, 0, DateTimeKind.Unspecified), + CreditAmount = 0m, + CreditLimit = 30000m, + FK_RouteId = new Guid("25000000-0000-0000-0000-000000000001"), + IsActive = true, + Latitude = 6.8508m, + Longitude = 79.8654m, + OneTimeAmount = 0m, + OutstandingBalance = 5000m, + OwnerName = "Nadeesha Silva", + Segment = "Silver", + StoreAddress = "45 Galle Road, Dehiwala", + StoreName = "Dehiwala Grocery" + }, + new + { + Id = new Guid("26000000-0000-0000-0000-000000000003"), + CreatedAt = new DateTime(2026, 8, 20, 0, 0, 0, 0, DateTimeKind.Unspecified), + CreditAmount = 0m, + CreditLimit = 40000m, + FK_RouteId = new Guid("25000000-0000-0000-0000-000000000002"), + IsActive = true, + Latitude = 6.9890m, + Longitude = 79.8912m, + OneTimeAmount = 0m, + OutstandingBalance = 1200m, + OwnerName = "Ruwan Gunasekara", + Segment = "Gold", + StoreAddress = "88 Negombo Road, Wattala", + StoreName = "Wattala Supermarket" + }, + new + { + Id = new Guid("26000000-0000-0000-0000-000000000004"), + CreatedAt = new DateTime(2026, 8, 20, 0, 0, 0, 0, DateTimeKind.Unspecified), + CreditAmount = 0m, + CreditLimit = 25000m, + FK_RouteId = new Guid("25000000-0000-0000-0000-000000000002"), + IsActive = true, + Latitude = 7.2094m, + Longitude = 79.8380m, + OneTimeAmount = 0m, + OutstandingBalance = 0m, + OwnerName = "Chamari Peiris", + StoreAddress = "5 Beach Road, Negombo", + StoreName = "Negombo Beach Store" + }, + new + { + Id = new Guid("26000000-0000-0000-0000-000000000005"), + CreatedAt = new DateTime(2026, 8, 20, 0, 0, 0, 0, DateTimeKind.Unspecified), + CreditAmount = 0m, + CreditLimit = 35000m, + FK_RouteId = new Guid("25000000-0000-0000-0000-000000000003"), + IsActive = true, + Latitude = 7.2906m, + Longitude = 80.6337m, + OneTimeAmount = 0m, + OutstandingBalance = 0m, + OwnerName = "Dinesh Wickramasinghe", + Segment = "Key Account", + StoreAddress = "3 Lake Road, Kandy", + StoreName = "Kandy Lake View Store" + }, + new + { + Id = new Guid("26000000-0000-0000-0000-000000000006"), + CreatedAt = new DateTime(2026, 8, 20, 0, 0, 0, 0, DateTimeKind.Unspecified), + CreditAmount = 0m, + CreditLimit = 20000m, + FK_RouteId = new Guid("25000000-0000-0000-0000-000000000004"), + IsActive = true, + Latitude = 7.4675m, + Longitude = 80.6234m, + OneTimeAmount = 0m, + OutstandingBalance = 0m, + OwnerName = "Ishara Bandara", + StoreAddress = "20 Main Street, Matale", + StoreName = "Matale Central Store" + }); + }); + + modelBuilder.Entity("Distribution.Models.SubCategory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("ExternalErpId") + .HasColumnType("int"); + + b.Property("FK_CategoryId") + .HasColumnType("char(36)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.HasIndex("FK_CategoryId"); + + b.ToTable("SubCategories"); + }); + + modelBuilder.Entity("Distribution.Models.Supplier", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("ContactInfo") + .HasColumnType("longtext"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("PerformanceScore") + .HasColumnType("decimal(65,30)"); + + b.HasKey("Id"); + + b.ToTable("Suppliers"); + + b.HasData( + new + { + Id = new Guid("38000000-0000-0000-0000-000000000001"), + ContactInfo = "+94 11 234 5678", + CreatedAt = new DateTime(2026, 8, 14, 0, 0, 0, 0, DateTimeKind.Unspecified), + IsActive = true, + Name = "Ceylon Beverages Ltd", + PerformanceScore = 4.2m + }, + new + { + Id = new Guid("38000000-0000-0000-0000-000000000002"), + ContactInfo = "+94 11 876 5432", + CreatedAt = new DateTime(2026, 8, 14, 0, 0, 0, 0, DateTimeKind.Unspecified), + IsActive = true, + Name = "Snack Foods (Pvt) Ltd" + }); + }); + + modelBuilder.Entity("Distribution.Models.SupplierClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Details") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("EvidenceUrl") + .HasColumnType("longtext"); + + b.Property("FK_GoodsReceiptId") + .HasColumnType("char(36)"); + + b.Property("FK_PurchaseOrderId") + .HasColumnType("char(36)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("RaisedByUserId") + .HasColumnType("char(36)"); + + b.Property("ResolutionAmount") + .HasColumnType("decimal(65,30)"); + + b.Property("ResolutionNotes") + .HasColumnType("longtext"); + + b.Property("ResolutionType") + .HasColumnType("int"); + + b.Property("ResolvedAt") + .HasColumnType("datetime(6)"); + + b.Property("ResolvedByUserId") + .HasColumnType("char(36)"); + + b.Property("Status") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("FK_GoodsReceiptId"); + + b.HasIndex("FK_PurchaseOrderId"); + + b.HasIndex("RaisedByUserId"); + + b.HasIndex("ResolvedByUserId"); + + b.ToTable("SupplierClaims"); + + b.HasData( + new + { + Id = new Guid("55000000-0000-0000-0000-000000000001"), + CreatedAt = new DateTime(2026, 8, 15, 0, 0, 0, 0, DateTimeKind.Unspecified), + Details = "2 units of Coke 1.5L received damaged", + FK_GoodsReceiptId = new Guid("43000000-0000-0000-0000-000000000001"), + FK_PurchaseOrderId = new Guid("41000000-0000-0000-0000-000000000001"), + IsActive = true, + RaisedByUserId = new Guid("29000000-0000-0000-0000-000000000001"), + ResolutionAmount = 160m, + ResolutionNotes = "Credited against next invoice", + ResolutionType = 1, + ResolvedAt = new DateTime(2026, 8, 16, 0, 0, 0, 0, DateTimeKind.Unspecified), + ResolvedByUserId = new Guid("29000000-0000-0000-0000-000000000006"), + Status = 1 + }); + }); + + modelBuilder.Entity("Distribution.Models.SupplierClaimItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("FK_ItemId") + .HasColumnType("char(36)"); + + b.Property("FK_SupplierClaimId") + .HasColumnType("char(36)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("IssueType") + .HasColumnType("int"); + + b.Property("Quantity") + .HasColumnType("decimal(65,30)"); + + b.HasKey("Id"); + + b.HasIndex("FK_ItemId"); + + b.HasIndex("FK_SupplierClaimId"); + + b.ToTable("SupplierClaimItems"); + + b.HasData( + new + { + Id = new Guid("56000000-0000-0000-0000-000000000001"), + CreatedAt = new DateTime(2026, 8, 15, 0, 0, 0, 0, DateTimeKind.Unspecified), + FK_ItemId = new Guid("23000000-0000-0000-0000-000000000002"), + FK_SupplierClaimId = new Guid("55000000-0000-0000-0000-000000000001"), + IsActive = true, + IssueType = 0, + Quantity = 2m + }); + }); + + modelBuilder.Entity("Distribution.Models.SysConfig", b => + { + b.Property("Key") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("UpdatedBy") + .HasColumnType("char(36)"); + + b.Property("Value") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Key"); + + b.ToTable("SysConfigs"); + + b.HasData( + new + { + Key = "InvoiceCreationMode", + UpdatedAt = new DateTime(2026, 9, 17, 0, 0, 0, 0, DateTimeKind.Unspecified), + Value = "ModeB" + }, + new + { + Key = "Feature.SerialNumberTracking", + UpdatedAt = new DateTime(2026, 10, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), + Value = "false" + }, + new + { + Key = "Feature.BatchTracking", + UpdatedAt = new DateTime(2026, 10, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), + Value = "false" + }, + new + { + Key = "Feature.ExpiryDateTracking", + UpdatedAt = new DateTime(2026, 10, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), + Value = "false" + }, + new + { + Key = "Feature.WarrantyTracking", + UpdatedAt = new DateTime(2026, 10, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), + Value = "false" + }, + new + { + Key = "Feature.CustomWarrantyOverride", + UpdatedAt = new DateTime(2026, 10, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), + Value = "false" + }, + new + { + Key = "Feature.DefaultWarrantyPeriodDays", + UpdatedAt = new DateTime(2026, 10, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), + Value = "0" + }); + }); + + modelBuilder.Entity("Distribution.Models.Uom", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("ExternalErpId") + .HasColumnType("int"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.ToTable("Uoms"); + + b.HasData( + new + { + Id = new Guid("22000000-0000-0000-0000-000000000001"), + CreatedAt = new DateTime(2026, 8, 20, 0, 0, 0, 0, DateTimeKind.Unspecified), + IsActive = true, + Name = "Piece" + }, + new + { + Id = new Guid("67000000-0000-0000-0000-000000000001"), + CreatedAt = new DateTime(2026, 8, 20, 0, 0, 0, 0, DateTimeKind.Unspecified), + IsActive = true, + Name = "Case" + }); + }); + + modelBuilder.Entity("Distribution.Models.UomConversion", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("FK_FromUomId") + .HasColumnType("char(36)"); + + b.Property("FK_ItemId") + .HasColumnType("char(36)"); + + b.Property("FK_ToUomId") + .HasColumnType("char(36)"); + + b.Property("Factor") + .HasColumnType("decimal(65,30)"); + + b.HasKey("Id"); + + b.HasIndex("FK_FromUomId"); + + b.HasIndex("FK_ItemId"); + + b.HasIndex("FK_ToUomId"); + + b.ToTable("UomConversions"); + + b.HasData( + new + { + Id = new Guid("67000000-0000-0000-0000-000000000002"), + FK_FromUomId = new Guid("67000000-0000-0000-0000-000000000001"), + FK_ItemId = new Guid("23000000-0000-0000-0000-000000000001"), + FK_ToUomId = new Guid("22000000-0000-0000-0000-000000000001"), + Factor = 24m + }); + }); + + modelBuilder.Entity("Distribution.Models.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("FK_ReportsToUserId") + .HasColumnType("char(36)"); + + b.Property("FK_RoleId") + .HasColumnType("char(36)"); + + b.Property("FullName") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("PasswordHash") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Username") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.HasIndex("FK_ReportsToUserId"); + + b.HasIndex("FK_RoleId"); + + b.ToTable("Users"); + + b.HasData( + new + { + Id = new Guid("29000000-0000-0000-0000-000000000001"), + CreatedAt = new DateTime(2026, 8, 20, 0, 0, 0, 0, DateTimeKind.Unspecified), + FK_RoleId = new Guid("10000000-0000-0000-0000-000000000002"), + FullName = "Sunil Fernando", + IsActive = true, + PasswordHash = "AQAAAAIAAYagAAAAEKGnq/U4j6uM9g5mFkxbUE8WU/AhnSNiwrKCiPAqGnzNcaMgZAF5Zal/rJUUYPZPpg==", + Username = "sunil.fernando" + }, + new + { + Id = new Guid("29000000-0000-0000-0000-000000000002"), + CreatedAt = new DateTime(2026, 8, 20, 0, 0, 0, 0, DateTimeKind.Unspecified), + FK_ReportsToUserId = new Guid("29000000-0000-0000-0000-000000000001"), + FK_RoleId = new Guid("10000000-0000-0000-0000-000000000001"), + FullName = "Nimal Perera", + IsActive = true, + PasswordHash = "AQAAAAIAAYagAAAAEKGnq/U4j6uM9g5mFkxbUE8WU/AhnSNiwrKCiPAqGnzNcaMgZAF5Zal/rJUUYPZPpg==", + Username = "nimal.perera" + }, + new + { + Id = new Guid("29000000-0000-0000-0000-000000000003"), + CreatedAt = new DateTime(2026, 8, 20, 0, 0, 0, 0, DateTimeKind.Unspecified), + FK_ReportsToUserId = new Guid("29000000-0000-0000-0000-000000000001"), + FK_RoleId = new Guid("10000000-0000-0000-0000-000000000001"), + FullName = "Kamal Silva", + IsActive = true, + PasswordHash = "AQAAAAIAAYagAAAAEKGnq/U4j6uM9g5mFkxbUE8WU/AhnSNiwrKCiPAqGnzNcaMgZAF5Zal/rJUUYPZPpg==", + Username = "kamal.silva" + }, + new + { + Id = new Guid("29000000-0000-0000-0000-000000000004"), + CreatedAt = new DateTime(2026, 8, 20, 0, 0, 0, 0, DateTimeKind.Unspecified), + FK_ReportsToUserId = new Guid("29000000-0000-0000-0000-000000000001"), + FK_RoleId = new Guid("10000000-0000-0000-0000-000000000001"), + FullName = "Ruwan Jayasuriya", + IsActive = true, + PasswordHash = "AQAAAAIAAYagAAAAEKGnq/U4j6uM9g5mFkxbUE8WU/AhnSNiwrKCiPAqGnzNcaMgZAF5Zal/rJUUYPZPpg==", + Username = "ruwan.jayasuriya" + }, + new + { + Id = new Guid("29000000-0000-0000-0000-000000000005"), + CreatedAt = new DateTime(2026, 8, 20, 0, 0, 0, 0, DateTimeKind.Unspecified), + FK_ReportsToUserId = new Guid("29000000-0000-0000-0000-000000000001"), + FK_RoleId = new Guid("10000000-0000-0000-0000-000000000001"), + FullName = "Priyantha Kumara", + IsActive = true, + PasswordHash = "AQAAAAIAAYagAAAAEKGnq/U4j6uM9g5mFkxbUE8WU/AhnSNiwrKCiPAqGnzNcaMgZAF5Zal/rJUUYPZPpg==", + Username = "priyantha.kumara" + }, + new + { + Id = new Guid("29000000-0000-0000-0000-000000000006"), + CreatedAt = new DateTime(2026, 8, 20, 0, 0, 0, 0, DateTimeKind.Unspecified), + FK_RoleId = new Guid("10000000-0000-0000-0000-000000000005"), + FullName = "Priya Rajapaksa", + IsActive = true, + PasswordHash = "AQAAAAIAAYagAAAAEKGnq/U4j6uM9g5mFkxbUE8WU/AhnSNiwrKCiPAqGnzNcaMgZAF5Zal/rJUUYPZPpg==", + Username = "priya.rajapaksa" + }, + new + { + Id = new Guid("70000000-0000-0000-0000-000000000001"), + CreatedAt = new DateTime(2026, 8, 20, 0, 0, 0, 0, DateTimeKind.Unspecified), + FK_RoleId = new Guid("10000000-0000-0000-0000-000000000006"), + FullName = "Channa De Silva", + IsActive = true, + PasswordHash = "AQAAAAIAAYagAAAAEKGnq/U4j6uM9g5mFkxbUE8WU/AhnSNiwrKCiPAqGnzNcaMgZAF5Zal/rJUUYPZPpg==", + Username = "channa.desilva" + }); + }); + + modelBuilder.Entity("Distribution.Models.UserRegionScope", b => + { + b.Property("FK_UserId") + .HasColumnType("char(36)"); + + b.Property("FK_RegionId") + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.HasKey("FK_UserId", "FK_RegionId"); + + b.HasIndex("FK_RegionId"); + + b.ToTable("UserRegionScopes"); + + b.HasData( + new + { + FK_UserId = new Guid("29000000-0000-0000-0000-000000000006"), + FK_RegionId = new Guid("24000000-0000-0000-0000-000000000001"), + CreatedAt = new DateTime(2026, 8, 20, 0, 0, 0, 0, DateTimeKind.Unspecified), + Id = new Guid("68000000-0000-0000-0000-000000000001"), + IsActive = true + }); + }); + + modelBuilder.Entity("Distribution.Models.UserRouteScope", b => + { + b.Property("FK_UserId") + .HasColumnType("char(36)"); + + b.Property("FK_RouteId") + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.HasKey("FK_UserId", "FK_RouteId"); + + b.HasIndex("FK_RouteId"); + + b.ToTable("UserRouteScopes"); + + b.HasData( + new + { + FK_UserId = new Guid("29000000-0000-0000-0000-000000000001"), + FK_RouteId = new Guid("25000000-0000-0000-0000-000000000001"), + CreatedAt = new DateTime(2026, 8, 20, 0, 0, 0, 0, DateTimeKind.Unspecified), + Id = new Guid("68000000-0000-0000-0000-000000000002"), + IsActive = true + }); + }); + + modelBuilder.Entity("Distribution.Models.UserStoreScope", b => + { + b.Property("FK_UserId") + .HasColumnType("char(36)"); + + b.Property("FK_StoreId") + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.HasKey("FK_UserId", "FK_StoreId"); + + b.HasIndex("FK_StoreId"); + + b.ToTable("UserStoreScopes"); + + b.HasData( + new + { + FK_UserId = new Guid("29000000-0000-0000-0000-000000000003"), + FK_StoreId = new Guid("26000000-0000-0000-0000-000000000005"), + CreatedAt = new DateTime(2026, 8, 20, 0, 0, 0, 0, DateTimeKind.Unspecified), + Id = new Guid("68000000-0000-0000-0000-000000000003"), + IsActive = true + }); + }); + + modelBuilder.Entity("Distribution.Models.Vehicle", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("DefaultWarehouseId") + .HasColumnType("char(36)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("NumberPlate") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("RegNumber") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Type") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("DefaultWarehouseId"); + + b.ToTable("Vehicles"); + + b.HasData( + new + { + Id = new Guid("28000000-0000-0000-0000-000000000001"), + CreatedAt = new DateTime(2026, 8, 20, 0, 0, 0, 0, DateTimeKind.Unspecified), + DefaultWarehouseId = new Guid("27000000-0000-0000-0000-000000000001"), + IsActive = true, + NumberPlate = "CAB-1234", + RegNumber = "WP-CAB-1234", + Type = 2 + }, + new + { + Id = new Guid("28000000-0000-0000-0000-000000000002"), + CreatedAt = new DateTime(2026, 8, 20, 0, 0, 0, 0, DateTimeKind.Unspecified), + DefaultWarehouseId = new Guid("27000000-0000-0000-0000-000000000002"), + IsActive = true, + NumberPlate = "KY-5678", + RegNumber = "CP-KY-5678", + Type = 1 + }); + }); + + modelBuilder.Entity("Distribution.Models.Warehouse", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("Location") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Type") + .HasColumnType("int"); + + b.Property("VehicleId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("VehicleId"); + + b.ToTable("Warehouses"); + + b.HasData( + new + { + Id = new Guid("27000000-0000-0000-0000-000000000001"), + CreatedAt = new DateTime(2026, 8, 20, 0, 0, 0, 0, DateTimeKind.Unspecified), + IsActive = true, + Location = "Peliyagoda, Colombo", + Name = "Colombo Distribution Center", + Type = 0 + }, + new + { + Id = new Guid("27000000-0000-0000-0000-000000000002"), + CreatedAt = new DateTime(2026, 8, 20, 0, 0, 0, 0, DateTimeKind.Unspecified), + IsActive = true, + Location = "Katugastota, Kandy", + Name = "Kandy Fulfillment Center", + Type = 1 + }); + }); + + modelBuilder.Entity("Distribution.Models.Claim", b => + { + b.HasOne("Distribution.Models.User", "AgencyUser") + .WithMany() + .HasForeignKey("FK_AgencyUserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Distribution.Models.User", "SettledByUser") + .WithMany() + .HasForeignKey("SettledByUserId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("AgencyUser"); + + b.Navigation("SettledByUser"); + }); + + modelBuilder.Entity("Distribution.Models.CreditNote", b => + { + b.HasOne("Distribution.Models.SaleReturn", "SaleReturn") + .WithMany() + .HasForeignKey("FK_SaleReturnId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("SaleReturn"); + }); + + modelBuilder.Entity("Distribution.Models.DeliveryRun", b => + { + b.HasOne("Distribution.Models.DriverProfile", "DriverProfile") + .WithMany() + .HasForeignKey("FK_DriverProfileId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Distribution.Models.Vehicle", "Vehicle") + .WithMany() + .HasForeignKey("FK_VehicleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("DriverProfile"); + + b.Navigation("Vehicle"); + }); + + modelBuilder.Entity("Distribution.Models.DeliveryTask", b => + { + b.HasOne("Distribution.Models.DeliveryRun", "Ride") + .WithMany() + .HasForeignKey("FK_RideId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Distribution.Models.ShopPO", "ShopPO") + .WithMany() + .HasForeignKey("FK_ShopPOId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Ride"); + + b.Navigation("ShopPO"); + }); + + modelBuilder.Entity("Distribution.Models.DriverProfile", b => + { + b.HasOne("Distribution.Models.User", "User") + .WithMany() + .HasForeignKey("FK_UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Distribution.Models.GoodsReceipt", b => + { + b.HasOne("Distribution.Models.PurchaseOrder", "PurchaseOrder") + .WithMany() + .HasForeignKey("FK_PurchaseOrderId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("Distribution.Models.Supplier", "Supplier") + .WithMany() + .HasForeignKey("FK_SupplierId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("Distribution.Models.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("FK_WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("PurchaseOrder"); + + b.Navigation("Supplier"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("Distribution.Models.GoodsReceiptItem", b => + { + b.HasOne("Distribution.Models.GoodsReceipt", "GoodsReceipt") + .WithMany("Items") + .HasForeignKey("FK_GoodsReceiptId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Distribution.Models.Item", "Item") + .WithMany() + .HasForeignKey("FK_ItemId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("Distribution.Models.PurchaseOrderItem", "PurchaseOrderItem") + .WithMany() + .HasForeignKey("FK_PurchaseOrderItemId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("GoodsReceipt"); + + b.Navigation("Item"); + + b.Navigation("PurchaseOrderItem"); + }); + + modelBuilder.Entity("Distribution.Models.GoodsReceiptItemBatch", b => + { + b.HasOne("Distribution.Models.GoodsReceiptItem", "GoodsReceiptItem") + .WithMany("Batches") + .HasForeignKey("FK_GoodsReceiptItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Distribution.Models.ItemBatch", "ItemBatch") + .WithMany() + .HasForeignKey("FK_ItemBatchId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("GoodsReceiptItem"); + + b.Navigation("ItemBatch"); + }); + + modelBuilder.Entity("Distribution.Models.GoodsReceiptItemSerial", b => + { + b.HasOne("Distribution.Models.GoodsReceiptItem", "GoodsReceiptItem") + .WithMany("Serials") + .HasForeignKey("FK_GoodsReceiptItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Distribution.Models.ItemSerialNumber", "ItemSerialNumber") + .WithMany() + .HasForeignKey("FK_ItemSerialNumberId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("GoodsReceiptItem"); + + b.Navigation("ItemSerialNumber"); + }); + + modelBuilder.Entity("Distribution.Models.Invoice", b => + { + b.HasOne("Distribution.Models.ShopPO", "ShopPO") + .WithMany() + .HasForeignKey("FK_ShopPOId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("ShopPO"); + }); + + modelBuilder.Entity("Distribution.Models.Item", b => + { + b.HasOne("Distribution.Models.Uom", "BaseUom") + .WithMany() + .HasForeignKey("FK_BaseUomId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Distribution.Models.Brand", "Brand") + .WithMany() + .HasForeignKey("FK_BrandId"); + + b.HasOne("Distribution.Models.Category", "Category") + .WithMany() + .HasForeignKey("FK_CategoryId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Distribution.Models.SubCategory", "SubCategory") + .WithMany() + .HasForeignKey("FK_SubCategoryId"); + + b.Navigation("BaseUom"); + + b.Navigation("Brand"); + + b.Navigation("Category"); + + b.Navigation("SubCategory"); + }); + + modelBuilder.Entity("Distribution.Models.ItemBatch", b => + { + b.HasOne("Distribution.Models.Item", "Item") + .WithMany() + .HasForeignKey("FK_ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Item"); + }); + + modelBuilder.Entity("Distribution.Models.ItemSerialNumber", b => + { + b.HasOne("Distribution.Models.Item", "Item") + .WithMany() + .HasForeignKey("FK_ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Item"); + }); + + modelBuilder.Entity("Distribution.Models.LoadingSheet", b => + { + b.HasOne("Distribution.Models.DeliveryRun", "Ride") + .WithMany() + .HasForeignKey("FK_RideId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Ride"); + }); + + modelBuilder.Entity("Distribution.Models.LoadingSheetItem", b => + { + b.HasOne("Distribution.Models.Item", "Item") + .WithMany() + .HasForeignKey("FK_ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Distribution.Models.LoadingSheet", "LoadingSheet") + .WithMany("Items") + .HasForeignKey("FK_LoadingSheetId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Distribution.Models.ShopPOItem", "ShopPOItem") + .WithMany() + .HasForeignKey("FK_ShopPOItemId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Distribution.Models.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("FK_WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Item"); + + b.Navigation("LoadingSheet"); + + b.Navigation("ShopPOItem"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("Distribution.Models.LoadingSheetOrder", b => + { + b.HasOne("Distribution.Models.LoadingSheet", "LoadingSheet") + .WithMany("Orders") + .HasForeignKey("FK_LoadingSheetId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Distribution.Models.ShopPO", "ShopPO") + .WithMany() + .HasForeignKey("FK_ShopPOId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("LoadingSheet"); + + b.Navigation("ShopPO"); + }); + + modelBuilder.Entity("Distribution.Models.Payment", b => + { + b.HasOne("Distribution.Models.Invoice", "Invoice") + .WithMany("Payments") + .HasForeignKey("FK_InvoiceId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Invoice"); + }); + + modelBuilder.Entity("Distribution.Models.PriceList", b => + { + b.HasOne("Distribution.Models.Region", "Region") + .WithMany() + .HasForeignKey("FK_RegionId"); + + b.Navigation("Region"); + }); + + modelBuilder.Entity("Distribution.Models.PriceListItem", b => + { + b.HasOne("Distribution.Models.Item", "Item") + .WithMany() + .HasForeignKey("FK_ItemId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Distribution.Models.PriceList", "PriceList") + .WithMany("Items") + .HasForeignKey("FK_PriceListId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Item"); + + b.Navigation("PriceList"); + }); + + modelBuilder.Entity("Distribution.Models.PurchaseOrder", b => + { + b.HasOne("Distribution.Models.Supplier", "Supplier") + .WithMany() + .HasForeignKey("FK_SupplierId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Supplier"); + }); + + modelBuilder.Entity("Distribution.Models.PurchaseOrderItem", b => + { + b.HasOne("Distribution.Models.Item", "Item") + .WithMany() + .HasForeignKey("FK_ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Distribution.Models.PurchaseOrder", "PurchaseOrder") + .WithMany("Items") + .HasForeignKey("FK_PurchaseOrderId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Item"); + + b.Navigation("PurchaseOrder"); + }); + + modelBuilder.Entity("Distribution.Models.PurchaseReturn", b => + { + b.HasOne("Distribution.Models.PurchaseOrder", "PurchaseOrder") + .WithMany() + .HasForeignKey("FK_PurchaseOrderId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("PurchaseOrder"); + }); + + modelBuilder.Entity("Distribution.Models.PurchaseReturnItem", b => + { + b.HasOne("Distribution.Models.Item", "Item") + .WithMany() + .HasForeignKey("FK_ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Distribution.Models.PurchaseReturn", "PurchaseReturn") + .WithMany("Items") + .HasForeignKey("FK_PurchaseReturnId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Item"); + + b.Navigation("PurchaseReturn"); + }); + + modelBuilder.Entity("Distribution.Models.RegionAgencyMap", b => + { + b.HasOne("Distribution.Models.User", "AgencyUser") + .WithMany() + .HasForeignKey("FK_AgencyUserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Distribution.Models.Region", "Region") + .WithMany() + .HasForeignKey("FK_RegionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AgencyUser"); + + b.Navigation("Region"); + }); + + modelBuilder.Entity("Distribution.Models.RegionalBudget", b => + { + b.HasOne("Distribution.Models.Region", "Region") + .WithMany() + .HasForeignKey("FK_RegionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Region"); + }); + + modelBuilder.Entity("Distribution.Models.RegionalTarget", b => + { + b.HasOne("Distribution.Models.Region", "Region") + .WithMany() + .HasForeignKey("FK_RegionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Region"); + }); + + modelBuilder.Entity("Distribution.Models.RegionalTaxRule", b => + { + b.HasOne("Distribution.Models.Region", "Region") + .WithMany() + .HasForeignKey("FK_RegionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Region"); + }); + + modelBuilder.Entity("Distribution.Models.RolePermission", b => + { + b.HasOne("Distribution.Models.Permission", "Permission") + .WithMany() + .HasForeignKey("FK_PermissionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Distribution.Models.Role", "Role") + .WithMany() + .HasForeignKey("FK_RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Permission"); + + b.Navigation("Role"); + }); + + modelBuilder.Entity("Distribution.Models.Route", b => + { + b.HasOne("Distribution.Models.User", "OwnerUser") + .WithMany() + .HasForeignKey("FK_OwnerUserId"); + + b.HasOne("Distribution.Models.Region", "Region") + .WithMany() + .HasForeignKey("FK_RegionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("OwnerUser"); + + b.Navigation("Region"); + }); + + modelBuilder.Entity("Distribution.Models.RouteVisitLog", b => + { + b.HasOne("Distribution.Models.Route", "Route") + .WithMany() + .HasForeignKey("FK_RouteId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Distribution.Models.Store", "Store") + .WithMany() + .HasForeignKey("FK_StoreId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Distribution.Models.User", "User") + .WithMany() + .HasForeignKey("FK_UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Route"); + + b.Navigation("Store"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Distribution.Models.SaleReturn", b => + { + b.HasOne("Distribution.Models.User", "CreatedByUser") + .WithMany() + .HasForeignKey("CreatedByUserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Distribution.Models.ShopPO", "ShopPO") + .WithMany() + .HasForeignKey("FK_ShopPOId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("CreatedByUser"); + + b.Navigation("ShopPO"); + }); + + modelBuilder.Entity("Distribution.Models.SaleReturnItem", b => + { + b.HasOne("Distribution.Models.Item", "Item") + .WithMany() + .HasForeignKey("FK_ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Distribution.Models.SaleReturn", "SaleReturn") + .WithMany("Items") + .HasForeignKey("FK_SaleReturnId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Item"); + + b.Navigation("SaleReturn"); + }); + + modelBuilder.Entity("Distribution.Models.Scheme", b => + { + b.HasOne("Distribution.Models.Item", "FreeItem") + .WithMany() + .HasForeignKey("FK_FreeItemId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("Distribution.Models.Item", "TriggerItem") + .WithMany() + .HasForeignKey("FK_TriggerItemId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("Distribution.Models.Region", "Region") + .WithMany() + .HasForeignKey("RegionId"); + + b.Navigation("FreeItem"); + + b.Navigation("Region"); + + b.Navigation("TriggerItem"); + }); + + modelBuilder.Entity("Distribution.Models.SchemeBundleComponent", b => + { + b.HasOne("Distribution.Models.Item", "Item") + .WithMany() + .HasForeignKey("FK_ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Distribution.Models.Scheme", "Scheme") + .WithMany("BundleComponents") + .HasForeignKey("FK_SchemeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Item"); + + b.Navigation("Scheme"); + }); + + modelBuilder.Entity("Distribution.Models.SchemeRedemption", b => + { + b.HasOne("Distribution.Models.Item", "FreeItem") + .WithMany() + .HasForeignKey("FK_FreeItemId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("Distribution.Models.Scheme", "Scheme") + .WithMany("Redemptions") + .HasForeignKey("FK_SchemeId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Distribution.Models.ShopPO", "ShopPO") + .WithMany() + .HasForeignKey("FK_ShopPOId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("FreeItem"); + + b.Navigation("Scheme"); + + b.Navigation("ShopPO"); + }); + + modelBuilder.Entity("Distribution.Models.ShopPO", b => + { + b.HasOne("Distribution.Models.Store", "Store") + .WithMany() + .HasForeignKey("FK_StoreId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Store"); + }); + + modelBuilder.Entity("Distribution.Models.ShopPOItem", b => + { + b.HasOne("Distribution.Models.Item", "Item") + .WithMany() + .HasForeignKey("FK_ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Distribution.Models.ShopPO", "ShopPO") + .WithMany("Items") + .HasForeignKey("FK_ShopPOId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Item"); + + b.Navigation("ShopPO"); + }); + + modelBuilder.Entity("Distribution.Models.StockBalance", b => + { + b.HasOne("Distribution.Models.ItemBatch", "ItemBatch") + .WithMany() + .HasForeignKey("FK_ItemBatchId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("Distribution.Models.Item", "Item") + .WithMany() + .HasForeignKey("FK_ItemId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Distribution.Models.ItemSerialNumber", "ItemSerialNumber") + .WithMany() + .HasForeignKey("FK_ItemSerialNumberId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Item"); + + b.Navigation("ItemBatch"); + + b.Navigation("ItemSerialNumber"); + }); + + modelBuilder.Entity("Distribution.Models.StockMovement", b => + { + b.HasOne("Distribution.Models.ItemBatch", "ItemBatch") + .WithMany() + .HasForeignKey("FK_ItemBatchId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("Distribution.Models.Item", "Item") + .WithMany() + .HasForeignKey("FK_ItemId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Distribution.Models.ItemSerialNumber", "ItemSerialNumber") + .WithMany() + .HasForeignKey("FK_ItemSerialNumberId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Item"); + + b.Navigation("ItemBatch"); + + b.Navigation("ItemSerialNumber"); + }); + + modelBuilder.Entity("Distribution.Models.Store", b => + { + b.HasOne("Distribution.Models.Route", "Route") + .WithMany() + .HasForeignKey("FK_RouteId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Route"); + }); + + modelBuilder.Entity("Distribution.Models.SubCategory", b => + { + b.HasOne("Distribution.Models.Category", "Category") + .WithMany() + .HasForeignKey("FK_CategoryId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Category"); + }); + + modelBuilder.Entity("Distribution.Models.SupplierClaim", b => + { + b.HasOne("Distribution.Models.GoodsReceipt", "GoodsReceipt") + .WithMany() + .HasForeignKey("FK_GoodsReceiptId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("Distribution.Models.PurchaseOrder", "PurchaseOrder") + .WithMany() + .HasForeignKey("FK_PurchaseOrderId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Distribution.Models.User", "RaisedByUser") + .WithMany() + .HasForeignKey("RaisedByUserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Distribution.Models.User", "ResolvedByUser") + .WithMany() + .HasForeignKey("ResolvedByUserId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("GoodsReceipt"); + + b.Navigation("PurchaseOrder"); + + b.Navigation("RaisedByUser"); + + b.Navigation("ResolvedByUser"); + }); + + modelBuilder.Entity("Distribution.Models.SupplierClaimItem", b => + { + b.HasOne("Distribution.Models.Item", "Item") + .WithMany() + .HasForeignKey("FK_ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Distribution.Models.SupplierClaim", "SupplierClaim") + .WithMany("Items") + .HasForeignKey("FK_SupplierClaimId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Item"); + + b.Navigation("SupplierClaim"); + }); + + modelBuilder.Entity("Distribution.Models.UomConversion", b => + { + b.HasOne("Distribution.Models.Uom", "FromUom") + .WithMany() + .HasForeignKey("FK_FromUomId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Distribution.Models.Item", "Item") + .WithMany("Conversions") + .HasForeignKey("FK_ItemId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Distribution.Models.Uom", "ToUom") + .WithMany() + .HasForeignKey("FK_ToUomId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("FromUom"); + + b.Navigation("Item"); + + b.Navigation("ToUom"); + }); + + modelBuilder.Entity("Distribution.Models.User", b => + { + b.HasOne("Distribution.Models.User", "ReportsTo") + .WithMany() + .HasForeignKey("FK_ReportsToUserId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("Distribution.Models.Role", "Role") + .WithMany() + .HasForeignKey("FK_RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ReportsTo"); + + b.Navigation("Role"); + }); + + modelBuilder.Entity("Distribution.Models.UserRegionScope", b => + { + b.HasOne("Distribution.Models.Region", "Region") + .WithMany() + .HasForeignKey("FK_RegionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Distribution.Models.User", "User") + .WithMany() + .HasForeignKey("FK_UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Region"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Distribution.Models.UserRouteScope", b => + { + b.HasOne("Distribution.Models.Route", "Route") + .WithMany() + .HasForeignKey("FK_RouteId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Distribution.Models.User", "User") + .WithMany() + .HasForeignKey("FK_UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Route"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Distribution.Models.UserStoreScope", b => + { + b.HasOne("Distribution.Models.Store", "Store") + .WithMany() + .HasForeignKey("FK_StoreId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Distribution.Models.User", "User") + .WithMany() + .HasForeignKey("FK_UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Store"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Distribution.Models.Vehicle", b => + { + b.HasOne("Distribution.Models.Warehouse", null) + .WithMany() + .HasForeignKey("DefaultWarehouseId") + .OnDelete(DeleteBehavior.Restrict); + }); + + modelBuilder.Entity("Distribution.Models.Warehouse", b => + { + b.HasOne("Distribution.Models.Vehicle", "Vehicle") + .WithMany() + .HasForeignKey("VehicleId"); + + b.Navigation("Vehicle"); + }); + + modelBuilder.Entity("Distribution.Models.GoodsReceipt", b => + { + b.Navigation("Items"); + }); + + modelBuilder.Entity("Distribution.Models.GoodsReceiptItem", b => + { + b.Navigation("Batches"); + + b.Navigation("Serials"); + }); + + modelBuilder.Entity("Distribution.Models.Invoice", b => + { + b.Navigation("Payments"); + }); + + modelBuilder.Entity("Distribution.Models.Item", b => + { + b.Navigation("Conversions"); + }); + + modelBuilder.Entity("Distribution.Models.LoadingSheet", b => + { + b.Navigation("Items"); + + b.Navigation("Orders"); + }); + + modelBuilder.Entity("Distribution.Models.PriceList", b => + { + b.Navigation("Items"); + }); + + modelBuilder.Entity("Distribution.Models.PurchaseOrder", b => + { + b.Navigation("Items"); + }); + + modelBuilder.Entity("Distribution.Models.PurchaseReturn", b => + { + b.Navigation("Items"); + }); + + modelBuilder.Entity("Distribution.Models.SaleReturn", b => + { + b.Navigation("Items"); + }); + + modelBuilder.Entity("Distribution.Models.Scheme", b => + { + b.Navigation("BundleComponents"); + + b.Navigation("Redemptions"); + }); + + modelBuilder.Entity("Distribution.Models.ShopPO", b => + { + b.Navigation("Items"); + }); + + modelBuilder.Entity("Distribution.Models.SupplierClaim", b => + { + b.Navigation("Items"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Distribution/Migrations/20261007063956_AddGrnPricing.cs b/Distribution/Migrations/20261007063956_AddGrnPricing.cs new file mode 100644 index 0000000..faee22a --- /dev/null +++ b/Distribution/Migrations/20261007063956_AddGrnPricing.cs @@ -0,0 +1,105 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Distribution.Migrations +{ + /// + public partial class AddGrnPricing : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "DiscountPercent", + table: "GoodsReceipts", + type: "decimal(5,2)", + precision: 5, + scale: 2, + nullable: false, + defaultValue: 0m); + + migrationBuilder.AddColumn( + name: "VatPercent", + table: "GoodsReceipts", + type: "decimal(5,2)", + precision: 5, + scale: 2, + nullable: false, + defaultValue: 0m); + + migrationBuilder.AddColumn( + name: "DiscountPercent", + table: "GoodsReceiptItems", + type: "decimal(5,2)", + precision: 5, + scale: 2, + nullable: false, + defaultValue: 0m); + + migrationBuilder.AddColumn( + name: "UnitCost", + table: "GoodsReceiptItems", + type: "decimal(18,2)", + precision: 18, + scale: 2, + nullable: false, + defaultValue: 0m); + + migrationBuilder.AddColumn( + name: "VatPercent", + table: "GoodsReceiptItems", + type: "decimal(5,2)", + precision: 5, + scale: 2, + nullable: false, + defaultValue: 0m); + + migrationBuilder.UpdateData( + table: "GoodsReceiptItems", + keyColumn: "Id", + keyValue: new Guid("44000000-0000-0000-0000-000000000001"), + columns: new[] { "DiscountPercent", "UnitCost", "VatPercent" }, + values: new object[] { 0m, 0m, 0m }); + + migrationBuilder.UpdateData( + table: "GoodsReceiptItems", + keyColumn: "Id", + keyValue: new Guid("44000000-0000-0000-0000-000000000002"), + columns: new[] { "DiscountPercent", "UnitCost", "VatPercent" }, + values: new object[] { 0m, 0m, 0m }); + + migrationBuilder.UpdateData( + table: "GoodsReceipts", + keyColumn: "Id", + keyValue: new Guid("43000000-0000-0000-0000-000000000001"), + columns: new[] { "DiscountPercent", "VatPercent" }, + values: new object[] { 0m, 0m }); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "DiscountPercent", + table: "GoodsReceipts"); + + migrationBuilder.DropColumn( + name: "VatPercent", + table: "GoodsReceipts"); + + migrationBuilder.DropColumn( + name: "DiscountPercent", + table: "GoodsReceiptItems"); + + migrationBuilder.DropColumn( + name: "UnitCost", + table: "GoodsReceiptItems"); + + migrationBuilder.DropColumn( + name: "VatPercent", + table: "GoodsReceiptItems"); + } + } +} diff --git a/Distribution/Migrations/20261007065602_AddGrnFreeQuantity.Designer.cs b/Distribution/Migrations/20261007065602_AddGrnFreeQuantity.Designer.cs new file mode 100644 index 0000000..ae2c131 --- /dev/null +++ b/Distribution/Migrations/20261007065602_AddGrnFreeQuantity.Designer.cs @@ -0,0 +1,4412 @@ +// +using System; +using Distribution.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace Distribution.Migrations +{ + [DbContext(typeof(AppDBContext))] + [Migration("20261007065602_AddGrnFreeQuantity")] + partial class AddGrnFreeQuantity + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "9.0.0") + .HasAnnotation("Relational:MaxIdentifierLength", 64); + + MySqlModelBuilderExtensions.AutoIncrementColumns(modelBuilder); + + modelBuilder.Entity("Distribution.Models.Brand", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("ExternalErpId") + .HasColumnType("int"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.ToTable("Brands"); + + b.HasData( + new + { + Id = new Guid("21000000-0000-0000-0000-000000000001"), + CreatedAt = new DateTime(2026, 8, 20, 0, 0, 0, 0, DateTimeKind.Unspecified), + IsActive = true, + Name = "Coca-Cola" + }, + new + { + Id = new Guid("21000000-0000-0000-0000-000000000002"), + CreatedAt = new DateTime(2026, 8, 20, 0, 0, 0, 0, DateTimeKind.Unspecified), + IsActive = true, + Name = "Lays" + }); + }); + + modelBuilder.Entity("Distribution.Models.Category", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("AllowCustomerReturn") + .HasColumnType("tinyint(1)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("ExternalErpId") + .HasColumnType("int"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.ToTable("Categories"); + + b.HasData( + new + { + Id = new Guid("20000000-0000-0000-0000-000000000001"), + AllowCustomerReturn = false, + CreatedAt = new DateTime(2026, 8, 20, 0, 0, 0, 0, DateTimeKind.Unspecified), + IsActive = true, + Name = "Beverages" + }, + new + { + Id = new Guid("20000000-0000-0000-0000-000000000002"), + AllowCustomerReturn = false, + CreatedAt = new DateTime(2026, 8, 20, 0, 0, 0, 0, DateTimeKind.Unspecified), + IsActive = true, + Name = "Snacks" + }); + }); + + modelBuilder.Entity("Distribution.Models.Claim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Details") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("FK_AgencyUserId") + .HasColumnType("char(36)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("SettledAt") + .HasColumnType("datetime(6)"); + + b.Property("SettledByUserId") + .HasColumnType("char(36)"); + + b.Property("SettlementAmount") + .HasColumnType("decimal(65,30)"); + + b.Property("Status") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("FK_AgencyUserId"); + + b.HasIndex("SettledByUserId"); + + b.ToTable("Claims"); + + b.HasData( + new + { + Id = new Guid("53000000-0000-0000-0000-000000000001"), + CreatedAt = new DateTime(2026, 8, 27, 0, 0, 0, 0, DateTimeKind.Unspecified), + Details = "Distributor margin claim for Q3 promotional shortfall", + FK_AgencyUserId = new Guid("70000000-0000-0000-0000-000000000001"), + IsActive = true, + SettledAt = new DateTime(2026, 8, 28, 0, 0, 0, 0, DateTimeKind.Unspecified), + SettledByUserId = new Guid("29000000-0000-0000-0000-000000000006"), + SettlementAmount = 15000m, + Status = 1 + }); + }); + + modelBuilder.Entity("Distribution.Models.CreditNote", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("Amount") + .HasColumnType("decimal(65,30)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("FK_SaleReturnId") + .HasColumnType("char(36)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("IssuedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("FK_SaleReturnId"); + + b.ToTable("CreditNotes"); + + b.HasData( + new + { + Id = new Guid("54000000-0000-0000-0000-000000000001"), + Amount = 300m, + CreatedAt = new DateTime(2026, 8, 26, 0, 0, 0, 0, DateTimeKind.Unspecified), + FK_SaleReturnId = new Guid("51000000-0000-0000-0000-000000000001"), + IsActive = true, + IssuedAt = new DateTime(2026, 8, 26, 0, 0, 0, 0, DateTimeKind.Unspecified) + }); + }); + + modelBuilder.Entity("Distribution.Models.DeliveryRun", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CancelReason") + .HasColumnType("longtext"); + + b.Property("CancelTypeCode") + .HasColumnType("int"); + + b.Property("ClosedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("FK_DriverProfileId") + .HasColumnType("char(36)"); + + b.Property("FK_VehicleId") + .HasColumnType("char(36)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("Meterend") + .HasColumnType("int"); + + b.Property("Meterstart") + .HasColumnType("int"); + + b.Property("StartedAt") + .HasColumnType("datetime(6)"); + + b.Property("Status") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("FK_DriverProfileId"); + + b.HasIndex("FK_VehicleId"); + + b.ToTable("DeliveryRuns"); + + b.HasData( + new + { + Id = new Guid("31000000-0000-0000-0000-000000000001"), + ClosedAt = new DateTime(2026, 8, 25, 14, 0, 0, 0, DateTimeKind.Unspecified), + CreatedAt = new DateTime(2026, 8, 25, 7, 30, 0, 0, DateTimeKind.Unspecified), + FK_DriverProfileId = new Guid("30000000-0000-0000-0000-000000000001"), + FK_VehicleId = new Guid("28000000-0000-0000-0000-000000000001"), + IsActive = true, + Meterend = 10550, + Meterstart = 10500, + StartedAt = new DateTime(2026, 8, 25, 8, 0, 0, 0, DateTimeKind.Unspecified), + Status = 2 + }, + new + { + Id = new Guid("31000000-0000-0000-0000-000000000002"), + CreatedAt = new DateTime(2026, 8, 30, 8, 45, 0, 0, DateTimeKind.Unspecified), + FK_DriverProfileId = new Guid("30000000-0000-0000-0000-000000000002"), + FK_VehicleId = new Guid("28000000-0000-0000-0000-000000000002"), + IsActive = true, + Meterend = 0, + Meterstart = 20300, + StartedAt = new DateTime(2026, 8, 30, 9, 0, 0, 0, DateTimeKind.Unspecified), + Status = 0 + }); + }); + + modelBuilder.Entity("Distribution.Models.DeliveryTask", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("ConfirmedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CustomerConfirmed") + .HasColumnType("tinyint(1)"); + + b.Property("FK_RideId") + .HasColumnType("char(36)"); + + b.Property("FK_ShopPOId") + .HasColumnType("char(36)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("Note") + .HasColumnType("longtext"); + + b.Property("Sequence") + .HasColumnType("int"); + + b.Property("Status") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("FK_RideId"); + + b.HasIndex("FK_ShopPOId"); + + b.ToTable("DeliveryTasks"); + + b.HasData( + new + { + Id = new Guid("37000000-0000-0000-0000-000000000001"), + CreatedAt = new DateTime(2026, 8, 30, 10, 5, 0, 0, DateTimeKind.Unspecified), + CustomerConfirmed = false, + FK_RideId = new Guid("31000000-0000-0000-0000-000000000002"), + FK_ShopPOId = new Guid("33000000-0000-0000-0000-000000000002"), + IsActive = true, + Sequence = 1, + Status = 0 + }); + }); + + modelBuilder.Entity("Distribution.Models.DriverComplianceConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("EnforceLicenseExpiry") + .HasColumnType("tinyint(1)"); + + b.Property("EnforceMedicalClearance") + .HasColumnType("tinyint(1)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("UpdatedBy") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.ToTable("DriverComplianceConfigs"); + + b.HasData( + new + { + Id = new Guid("64000000-0000-0000-0000-000000000001"), + EnforceLicenseExpiry = true, + EnforceMedicalClearance = true, + UpdatedAt = new DateTime(2026, 8, 20, 0, 0, 0, 0, DateTimeKind.Unspecified), + UpdatedBy = new Guid("29000000-0000-0000-0000-000000000001") + }); + }); + + modelBuilder.Entity("Distribution.Models.DriverProfile", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("FK_UserId") + .HasColumnType("char(36)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("LicenseExpiry") + .HasColumnType("datetime(6)"); + + b.Property("LicenseNumber") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("MedicalClearanceExpiry") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("FK_UserId") + .IsUnique(); + + b.ToTable("DriverProfiles"); + + b.HasData( + new + { + Id = new Guid("30000000-0000-0000-0000-000000000001"), + CreatedAt = new DateTime(2026, 8, 20, 0, 0, 0, 0, DateTimeKind.Unspecified), + FK_UserId = new Guid("29000000-0000-0000-0000-000000000004"), + IsActive = true, + LicenseExpiry = new DateTime(2027, 6, 30, 0, 0, 0, 0, DateTimeKind.Unspecified), + LicenseNumber = "B1234567", + MedicalClearanceExpiry = new DateTime(2027, 1, 31, 0, 0, 0, 0, DateTimeKind.Unspecified) + }, + new + { + Id = new Guid("30000000-0000-0000-0000-000000000002"), + CreatedAt = new DateTime(2026, 8, 20, 0, 0, 0, 0, DateTimeKind.Unspecified), + FK_UserId = new Guid("29000000-0000-0000-0000-000000000005"), + IsActive = true, + LicenseExpiry = new DateTime(2027, 9, 30, 0, 0, 0, 0, DateTimeKind.Unspecified), + LicenseNumber = "B7654321", + MedicalClearanceExpiry = new DateTime(2027, 3, 31, 0, 0, 0, 0, DateTimeKind.Unspecified) + }); + }); + + modelBuilder.Entity("Distribution.Models.GoodsReceipt", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("DiscountPercent") + .HasPrecision(5, 2) + .HasColumnType("decimal(5,2)"); + + b.Property("FK_PurchaseOrderId") + .HasColumnType("char(36)"); + + b.Property("FK_ReceivedByUserId") + .HasColumnType("char(36)"); + + b.Property("FK_SupplierId") + .HasColumnType("char(36)"); + + b.Property("FK_WarehouseId") + .HasColumnType("char(36)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("ReceivedAt") + .HasColumnType("datetime(6)"); + + b.Property("VatPercent") + .HasPrecision(5, 2) + .HasColumnType("decimal(5,2)"); + + b.HasKey("Id"); + + b.HasIndex("FK_PurchaseOrderId"); + + b.HasIndex("FK_SupplierId"); + + b.HasIndex("FK_WarehouseId"); + + b.ToTable("GoodsReceipts"); + + b.HasData( + new + { + Id = new Guid("43000000-0000-0000-0000-000000000001"), + CreatedAt = new DateTime(2026, 8, 15, 0, 0, 0, 0, DateTimeKind.Unspecified), + DiscountPercent = 0m, + FK_PurchaseOrderId = new Guid("41000000-0000-0000-0000-000000000001"), + FK_ReceivedByUserId = new Guid("29000000-0000-0000-0000-000000000001"), + FK_WarehouseId = new Guid("27000000-0000-0000-0000-000000000001"), + IsActive = true, + ReceivedAt = new DateTime(2026, 8, 15, 0, 0, 0, 0, DateTimeKind.Unspecified), + VatPercent = 0m + }); + }); + + modelBuilder.Entity("Distribution.Models.GoodsReceiptItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("AcceptedQuantity") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("DiscountPercent") + .HasPrecision(5, 2) + .HasColumnType("decimal(5,2)"); + + b.Property("FK_GoodsReceiptId") + .HasColumnType("char(36)"); + + b.Property("FK_ItemId") + .HasColumnType("char(36)"); + + b.Property("FK_PurchaseOrderItemId") + .HasColumnType("char(36)"); + + b.Property("FreeQuantity") + .HasColumnType("int"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("RejectedQuantity") + .HasColumnType("int"); + + b.Property("UnitCost") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("VatPercent") + .HasPrecision(5, 2) + .HasColumnType("decimal(5,2)"); + + b.HasKey("Id"); + + b.HasIndex("FK_GoodsReceiptId"); + + b.HasIndex("FK_ItemId"); + + b.HasIndex("FK_PurchaseOrderItemId"); + + b.ToTable("GoodsReceiptItems"); + + b.HasData( + new + { + Id = new Guid("44000000-0000-0000-0000-000000000001"), + AcceptedQuantity = 200, + CreatedAt = new DateTime(2026, 8, 15, 0, 0, 0, 0, DateTimeKind.Unspecified), + DiscountPercent = 0m, + FK_GoodsReceiptId = new Guid("43000000-0000-0000-0000-000000000001"), + FK_PurchaseOrderItemId = new Guid("42000000-0000-0000-0000-000000000001"), + FreeQuantity = 0, + IsActive = true, + RejectedQuantity = 0, + UnitCost = 0m, + VatPercent = 0m + }, + new + { + Id = new Guid("44000000-0000-0000-0000-000000000002"), + AcceptedQuantity = 98, + CreatedAt = new DateTime(2026, 8, 15, 0, 0, 0, 0, DateTimeKind.Unspecified), + DiscountPercent = 0m, + FK_GoodsReceiptId = new Guid("43000000-0000-0000-0000-000000000001"), + FK_PurchaseOrderItemId = new Guid("42000000-0000-0000-0000-000000000002"), + FreeQuantity = 0, + IsActive = true, + RejectedQuantity = 2, + UnitCost = 0m, + VatPercent = 0m + }); + }); + + modelBuilder.Entity("Distribution.Models.GoodsReceiptItemBatch", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("FK_GoodsReceiptItemId") + .HasColumnType("char(36)"); + + b.Property("FK_ItemBatchId") + .HasColumnType("char(36)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("Quantity") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("FK_GoodsReceiptItemId"); + + b.HasIndex("FK_ItemBatchId"); + + b.ToTable("GoodsReceiptItemBatches"); + }); + + modelBuilder.Entity("Distribution.Models.GoodsReceiptItemSerial", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("FK_GoodsReceiptItemId") + .HasColumnType("char(36)"); + + b.Property("FK_ItemSerialNumberId") + .HasColumnType("char(36)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.HasKey("Id"); + + b.HasIndex("FK_GoodsReceiptItemId"); + + b.HasIndex("FK_ItemSerialNumberId"); + + b.ToTable("GoodsReceiptItemSerials"); + }); + + modelBuilder.Entity("Distribution.Models.Invoice", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .HasColumnType("char(36)"); + + b.Property("FK_ShopPOId") + .HasColumnType("char(36)"); + + b.Property("FinalizedAt") + .HasColumnType("datetime(6)"); + + b.Property("FinalizedBy") + .HasColumnType("char(36)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("PaymentStatus") + .HasColumnType("int"); + + b.Property("PrintedAt") + .HasColumnType("datetime(6)"); + + b.Property("PrintedBy") + .HasColumnType("char(36)"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("TotalAmount") + .HasColumnType("decimal(65,30)"); + + b.HasKey("Id"); + + b.HasIndex("FK_ShopPOId"); + + b.ToTable("Invoices"); + + b.HasData( + new + { + Id = new Guid("34500000-0000-0000-0000-000000000001"), + CreatedAt = new DateTime(2026, 8, 25, 8, 30, 0, 0, DateTimeKind.Unspecified), + CreatedBy = new Guid("29000000-0000-0000-0000-000000000002"), + FK_ShopPOId = new Guid("33000000-0000-0000-0000-000000000001"), + FinalizedAt = new DateTime(2026, 8, 25, 9, 0, 0, 0, DateTimeKind.Unspecified), + FinalizedBy = new Guid("29000000-0000-0000-0000-000000000001"), + IsActive = true, + PaymentStatus = 2, + PrintedAt = new DateTime(2026, 8, 25, 8, 45, 0, 0, DateTimeKind.Unspecified), + PrintedBy = new Guid("29000000-0000-0000-0000-000000000002"), + Status = 2, + TotalAmount = 3000m + }, + new + { + Id = new Guid("34500000-0000-0000-0000-000000000002"), + CreatedAt = new DateTime(2026, 8, 31, 9, 0, 0, 0, DateTimeKind.Unspecified), + CreatedBy = new Guid("29000000-0000-0000-0000-000000000003"), + FK_ShopPOId = new Guid("33000000-0000-0000-0000-000000000003"), + IsActive = true, + PaymentStatus = 1, + PrintedAt = new DateTime(2026, 8, 31, 9, 15, 0, 0, DateTimeKind.Unspecified), + PrintedBy = new Guid("29000000-0000-0000-0000-000000000003"), + Status = 1, + TotalAmount = 2400m + }); + }); + + modelBuilder.Entity("Distribution.Models.Item", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("AllowCustomWarrantyPeriod") + .HasColumnType("tinyint(1)"); + + b.Property("CostPrice") + .HasColumnType("decimal(65,30)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Description") + .HasColumnType("longtext"); + + b.Property("ExternalErpItemId") + .HasColumnType("int"); + + b.Property("FK_BaseUomId") + .HasColumnType("char(36)"); + + b.Property("FK_BrandId") + .HasColumnType("char(36)"); + + b.Property("FK_CategoryId") + .HasColumnType("char(36)"); + + b.Property("FK_SubCategoryId") + .HasColumnType("char(36)"); + + b.Property("HasWarranty") + .HasColumnType("tinyint(1)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("LastSyncedAt") + .HasColumnType("datetime(6)"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("ReorderLevel") + .HasColumnType("decimal(65,30)"); + + b.Property("SalePrice") + .HasColumnType("decimal(65,30)"); + + b.Property("Sku") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("SyncSource") + .HasColumnType("int"); + + b.Property("TracksBatches") + .HasColumnType("tinyint(1)"); + + b.Property("TracksExpiryDate") + .HasColumnType("tinyint(1)"); + + b.Property("TracksSerialNumbers") + .HasColumnType("tinyint(1)"); + + b.Property("WarrantyPeriodDays") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("FK_BaseUomId"); + + b.HasIndex("FK_BrandId"); + + b.HasIndex("FK_CategoryId"); + + b.HasIndex("FK_SubCategoryId"); + + b.ToTable("Items"); + + b.HasData( + new + { + Id = new Guid("23000000-0000-0000-0000-000000000001"), + AllowCustomWarrantyPeriod = false, + CreatedAt = new DateTime(2026, 8, 20, 0, 0, 0, 0, DateTimeKind.Unspecified), + Description = "Coca-Cola 500ml bottle", + FK_BaseUomId = new Guid("22000000-0000-0000-0000-000000000001"), + FK_BrandId = new Guid("21000000-0000-0000-0000-000000000001"), + FK_CategoryId = new Guid("20000000-0000-0000-0000-000000000001"), + HasWarranty = false, + IsActive = true, + Name = "Coca-Cola 500ml", + SalePrice = 60m, + Sku = "BEV-COKE-500", + SyncSource = 0, + TracksBatches = false, + TracksExpiryDate = false, + TracksSerialNumbers = false + }, + new + { + Id = new Guid("23000000-0000-0000-0000-000000000002"), + AllowCustomWarrantyPeriod = false, + CreatedAt = new DateTime(2026, 8, 20, 0, 0, 0, 0, DateTimeKind.Unspecified), + Description = "Coca-Cola 1.5L bottle", + FK_BaseUomId = new Guid("22000000-0000-0000-0000-000000000001"), + FK_BrandId = new Guid("21000000-0000-0000-0000-000000000001"), + FK_CategoryId = new Guid("20000000-0000-0000-0000-000000000001"), + HasWarranty = false, + IsActive = true, + Name = "Coca-Cola 1.5L", + SalePrice = 120m, + Sku = "BEV-COKE-1500", + SyncSource = 0, + TracksBatches = false, + TracksExpiryDate = false, + TracksSerialNumbers = false + }, + new + { + Id = new Guid("23000000-0000-0000-0000-000000000003"), + AllowCustomWarrantyPeriod = false, + CreatedAt = new DateTime(2026, 8, 20, 0, 0, 0, 0, DateTimeKind.Unspecified), + Description = "Lays Classic Salted 52g pack", + FK_BaseUomId = new Guid("22000000-0000-0000-0000-000000000001"), + FK_BrandId = new Guid("21000000-0000-0000-0000-000000000002"), + FK_CategoryId = new Guid("20000000-0000-0000-0000-000000000002"), + HasWarranty = false, + IsActive = true, + Name = "Lays Classic 52g", + SalePrice = 50m, + Sku = "SNK-LAYS-52", + SyncSource = 0, + TracksBatches = false, + TracksExpiryDate = false, + TracksSerialNumbers = false + }, + new + { + Id = new Guid("23000000-0000-0000-0000-000000000004"), + AllowCustomWarrantyPeriod = false, + CreatedAt = new DateTime(2026, 8, 20, 0, 0, 0, 0, DateTimeKind.Unspecified), + Description = "Lays Family Pack 150g", + FK_BaseUomId = new Guid("22000000-0000-0000-0000-000000000001"), + FK_BrandId = new Guid("21000000-0000-0000-0000-000000000002"), + FK_CategoryId = new Guid("20000000-0000-0000-0000-000000000002"), + HasWarranty = false, + IsActive = true, + Name = "Lays Family Pack 150g", + SalePrice = 150m, + Sku = "SNK-LAYS-FAM", + SyncSource = 0, + TracksBatches = false, + TracksExpiryDate = false, + TracksSerialNumbers = false + }); + }); + + modelBuilder.Entity("Distribution.Models.ItemBatch", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("BatchNumber") + .HasColumnType("varchar(255)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("ExpiryDate") + .HasColumnType("datetime(6)"); + + b.Property("FK_ItemId") + .HasColumnType("char(36)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("ManufactureDate") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("FK_ItemId", "BatchNumber"); + + b.ToTable("ItemBatches"); + }); + + modelBuilder.Entity("Distribution.Models.ItemSerialNumber", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CurrentLocationId") + .HasColumnType("char(36)"); + + b.Property("CurrentLocationType") + .HasColumnType("int"); + + b.Property("FK_ItemId") + .HasColumnType("char(36)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("ReceivedAt") + .HasColumnType("datetime(6)"); + + b.Property("SerialNumber") + .IsRequired() + .HasColumnType("varchar(255)"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("WarrantyExpiresAt") + .HasColumnType("datetime(6)"); + + b.Property("WarrantyPeriodDaysOverride") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("FK_ItemId", "SerialNumber") + .IsUnique(); + + b.ToTable("ItemSerialNumbers"); + }); + + modelBuilder.Entity("Distribution.Models.LoadingSheet", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("ConfirmedAt") + .HasColumnType("datetime(6)"); + + b.Property("ConfirmedBy") + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("FK_RideId") + .HasColumnType("char(36)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("PopulatedAt") + .HasColumnType("datetime(6)"); + + b.Property("PopulatedBy") + .HasColumnType("char(36)"); + + b.Property("Status") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("FK_RideId"); + + b.ToTable("LoadingSheets"); + + b.HasData( + new + { + Id = new Guid("35000000-0000-0000-0000-000000000001"), + ConfirmedAt = new DateTime(2026, 8, 25, 7, 45, 0, 0, DateTimeKind.Unspecified), + ConfirmedBy = new Guid("29000000-0000-0000-0000-000000000001"), + CreatedAt = new DateTime(2026, 8, 25, 7, 30, 0, 0, DateTimeKind.Unspecified), + FK_RideId = new Guid("31000000-0000-0000-0000-000000000001"), + IsActive = true, + PopulatedAt = new DateTime(2026, 8, 25, 7, 30, 0, 0, DateTimeKind.Unspecified), + PopulatedBy = new Guid("29000000-0000-0000-0000-000000000001"), + Status = 1 + }); + }); + + modelBuilder.Entity("Distribution.Models.LoadingSheetItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("FK_ItemId") + .HasColumnType("char(36)"); + + b.Property("FK_LoadingSheetId") + .HasColumnType("char(36)"); + + b.Property("FK_ShopPOItemId") + .HasColumnType("char(36)"); + + b.Property("FK_WarehouseId") + .HasColumnType("char(36)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("QuantityDispatched") + .HasColumnType("decimal(65,30)"); + + b.HasKey("Id"); + + b.HasIndex("FK_ItemId"); + + b.HasIndex("FK_LoadingSheetId"); + + b.HasIndex("FK_ShopPOItemId"); + + b.HasIndex("FK_WarehouseId"); + + b.ToTable("LoadingSheetItems"); + + b.HasData( + new + { + Id = new Guid("36000000-0000-0000-0000-000000000001"), + CreatedAt = new DateTime(2026, 8, 25, 7, 30, 0, 0, DateTimeKind.Unspecified), + FK_ItemId = new Guid("23000000-0000-0000-0000-000000000001"), + FK_LoadingSheetId = new Guid("35000000-0000-0000-0000-000000000001"), + FK_WarehouseId = new Guid("27000000-0000-0000-0000-000000000001"), + IsActive = true, + QuantityDispatched = 50m + }, + new + { + Id = new Guid("36000000-0000-0000-0000-000000000002"), + CreatedAt = new DateTime(2026, 8, 25, 7, 30, 0, 0, DateTimeKind.Unspecified), + FK_ItemId = new Guid("23000000-0000-0000-0000-000000000003"), + FK_LoadingSheetId = new Guid("35000000-0000-0000-0000-000000000001"), + FK_WarehouseId = new Guid("27000000-0000-0000-0000-000000000001"), + IsActive = true, + QuantityDispatched = 30m + }); + }); + + modelBuilder.Entity("Distribution.Models.LoadingSheetOrder", b => + { + b.Property("FK_LoadingSheetId") + .HasColumnType("char(36)"); + + b.Property("FK_ShopPOId") + .HasColumnType("char(36)"); + + b.HasKey("FK_LoadingSheetId", "FK_ShopPOId"); + + b.HasIndex("FK_ShopPOId"); + + b.ToTable("LoadingSheetOrders"); + }); + + modelBuilder.Entity("Distribution.Models.Payment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("Amount") + .HasColumnType("decimal(65,30)"); + + b.Property("ChequeDate") + .HasColumnType("datetime(6)"); + + b.Property("ChequeNumber") + .HasColumnType("longtext"); + + b.Property("CollectedAt") + .HasColumnType("datetime(6)"); + + b.Property("CollectedBy") + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("EntryMode") + .HasColumnType("int"); + + b.Property("FK_InvoiceId") + .HasColumnType("char(36)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("Method") + .HasColumnType("int"); + + b.Property("RecordedBy") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("FK_InvoiceId"); + + b.ToTable("Payments"); + + b.HasData( + new + { + Id = new Guid("47000000-0000-0000-0000-000000000001"), + Amount = 3000m, + CollectedAt = new DateTime(2026, 8, 25, 9, 30, 0, 0, DateTimeKind.Unspecified), + CollectedBy = new Guid("29000000-0000-0000-0000-000000000002"), + CreatedAt = new DateTime(2026, 8, 25, 9, 30, 0, 0, DateTimeKind.Unspecified), + EntryMode = 0, + FK_InvoiceId = new Guid("34500000-0000-0000-0000-000000000001"), + IsActive = true, + Method = 0 + }, + new + { + Id = new Guid("47000000-0000-0000-0000-000000000002"), + Amount = 1000m, + CollectedAt = new DateTime(2026, 8, 31, 9, 20, 0, 0, DateTimeKind.Unspecified), + CollectedBy = new Guid("29000000-0000-0000-0000-000000000003"), + CreatedAt = new DateTime(2026, 8, 31, 9, 20, 0, 0, DateTimeKind.Unspecified), + EntryMode = 0, + FK_InvoiceId = new Guid("34500000-0000-0000-0000-000000000002"), + IsActive = true, + Method = 0 + }, + new + { + Id = new Guid("47000000-0000-0000-0000-000000000003"), + Amount = 800m, + ChequeDate = new DateTime(2026, 8, 31, 0, 0, 0, 0, DateTimeKind.Unspecified), + ChequeNumber = "000456", + CollectedAt = new DateTime(2026, 8, 31, 11, 0, 0, 0, DateTimeKind.Unspecified), + CollectedBy = new Guid("29000000-0000-0000-0000-000000000003"), + CreatedAt = new DateTime(2026, 8, 31, 18, 0, 0, 0, DateTimeKind.Unspecified), + EntryMode = 1, + FK_InvoiceId = new Guid("34500000-0000-0000-0000-000000000002"), + IsActive = true, + Method = 1, + RecordedBy = new Guid("29000000-0000-0000-0000-000000000001") + }, + new + { + Id = new Guid("47000000-0000-0000-0000-000000000004"), + Amount = 300m, + CollectedAt = new DateTime(2026, 8, 31, 11, 5, 0, 0, DateTimeKind.Unspecified), + CollectedBy = new Guid("29000000-0000-0000-0000-000000000003"), + CreatedAt = new DateTime(2026, 8, 31, 11, 5, 0, 0, DateTimeKind.Unspecified), + EntryMode = 0, + FK_InvoiceId = new Guid("34500000-0000-0000-0000-000000000002"), + IsActive = true, + Method = 2 + }); + }); + + modelBuilder.Entity("Distribution.Models.Permission", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("Code") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Description") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.HasKey("Id"); + + b.ToTable("Permissions"); + }); + + modelBuilder.Entity("Distribution.Models.PriceList", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("EffectiveFrom") + .HasColumnType("datetime(6)"); + + b.Property("EffectiveTo") + .HasColumnType("datetime(6)"); + + b.Property("FK_RegionId") + .HasColumnType("char(36)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.HasIndex("FK_RegionId"); + + b.ToTable("PriceLists"); + + b.HasData( + new + { + Id = new Guid("39000000-0000-0000-0000-000000000001"), + CreatedAt = new DateTime(2026, 8, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), + EffectiveFrom = new DateTime(2026, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), + EffectiveTo = new DateTime(2026, 12, 31, 0, 0, 0, 0, DateTimeKind.Unspecified), + FK_RegionId = new Guid("24000000-0000-0000-0000-000000000001"), + IsActive = true, + Name = "Western Province Price List" + }); + }); + + modelBuilder.Entity("Distribution.Models.PriceListItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("AgencyMarginPercent") + .HasColumnType("decimal(65,30)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("FK_ItemId") + .HasColumnType("char(36)"); + + b.Property("FK_PriceListId") + .HasColumnType("char(36)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("MinPrice") + .HasColumnType("decimal(65,30)"); + + b.Property("Price") + .HasColumnType("decimal(65,30)"); + + b.Property("RetailPrice") + .HasColumnType("decimal(65,30)"); + + b.HasKey("Id"); + + b.HasIndex("FK_ItemId"); + + b.HasIndex("FK_PriceListId", "FK_ItemId") + .IsUnique(); + + b.ToTable("PriceListItems"); + + b.HasData( + new + { + Id = new Guid("40000000-0000-0000-0000-000000000001"), + CreatedAt = new DateTime(2026, 8, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), + FK_ItemId = new Guid("23000000-0000-0000-0000-000000000001"), + FK_PriceListId = new Guid("39000000-0000-0000-0000-000000000001"), + IsActive = true, + MinPrice = 55m, + Price = 60m + }, + new + { + Id = new Guid("40000000-0000-0000-0000-000000000002"), + CreatedAt = new DateTime(2026, 8, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), + FK_ItemId = new Guid("23000000-0000-0000-0000-000000000003"), + FK_PriceListId = new Guid("39000000-0000-0000-0000-000000000001"), + IsActive = true, + MinPrice = 45m, + Price = 50m + }); + }); + + modelBuilder.Entity("Distribution.Models.PurchaseOrder", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CancelReason") + .HasColumnType("longtext"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("FK_CreatedByUserId") + .HasColumnType("char(36)"); + + b.Property("FK_SupplierId") + .HasColumnType("char(36)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("Status") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("FK_SupplierId"); + + b.ToTable("PurchaseOrders"); + + b.HasData( + new + { + Id = new Guid("41000000-0000-0000-0000-000000000001"), + CreatedAt = new DateTime(2026, 8, 14, 0, 0, 0, 0, DateTimeKind.Unspecified), + FK_CreatedByUserId = new Guid("29000000-0000-0000-0000-000000000001"), + FK_SupplierId = new Guid("38000000-0000-0000-0000-000000000001"), + IsActive = true, + Status = 2 + }, + new + { + Id = new Guid("41000000-0000-0000-0000-000000000002"), + CreatedAt = new DateTime(2026, 8, 14, 0, 0, 0, 0, DateTimeKind.Unspecified), + FK_CreatedByUserId = new Guid("29000000-0000-0000-0000-000000000001"), + FK_SupplierId = new Guid("38000000-0000-0000-0000-000000000002"), + IsActive = true, + Status = 2 + }); + }); + + modelBuilder.Entity("Distribution.Models.PurchaseOrderItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CancelledQuantity") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("FK_ItemId") + .HasColumnType("char(36)"); + + b.Property("FK_PurchaseOrderId") + .HasColumnType("char(36)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("IsCancelled") + .HasColumnType("tinyint(1)"); + + b.Property("Quantity") + .HasColumnType("int"); + + b.Property("ReceivedQuantity") + .HasColumnType("int"); + + b.Property("UnitCost") + .HasColumnType("decimal(65,30)"); + + b.HasKey("Id"); + + b.HasIndex("FK_ItemId"); + + b.HasIndex("FK_PurchaseOrderId"); + + b.ToTable("PurchaseOrderItems"); + + b.HasData( + new + { + Id = new Guid("42000000-0000-0000-0000-000000000001"), + CancelledQuantity = 0, + CreatedAt = new DateTime(2026, 8, 14, 0, 0, 0, 0, DateTimeKind.Unspecified), + FK_ItemId = new Guid("23000000-0000-0000-0000-000000000001"), + FK_PurchaseOrderId = new Guid("41000000-0000-0000-0000-000000000001"), + IsActive = true, + IsCancelled = false, + Quantity = 200, + ReceivedQuantity = 200, + UnitCost = 40m + }, + new + { + Id = new Guid("42000000-0000-0000-0000-000000000002"), + CancelledQuantity = 0, + CreatedAt = new DateTime(2026, 8, 14, 0, 0, 0, 0, DateTimeKind.Unspecified), + FK_ItemId = new Guid("23000000-0000-0000-0000-000000000002"), + FK_PurchaseOrderId = new Guid("41000000-0000-0000-0000-000000000001"), + IsActive = true, + IsCancelled = false, + Quantity = 100, + ReceivedQuantity = 98, + UnitCost = 80m + }, + new + { + Id = new Guid("42000000-0000-0000-0000-000000000003"), + CancelledQuantity = 0, + CreatedAt = new DateTime(2026, 8, 14, 0, 0, 0, 0, DateTimeKind.Unspecified), + FK_ItemId = new Guid("23000000-0000-0000-0000-000000000003"), + FK_PurchaseOrderId = new Guid("41000000-0000-0000-0000-000000000002"), + IsActive = true, + IsCancelled = false, + Quantity = 150, + ReceivedQuantity = 150, + UnitCost = 35m + }); + }); + + modelBuilder.Entity("Distribution.Models.PurchaseReturn", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("FK_PurchaseOrderId") + .HasColumnType("char(36)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("Reason") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.HasIndex("FK_PurchaseOrderId"); + + b.ToTable("PurchaseReturns"); + + b.HasData( + new + { + Id = new Guid("45000000-0000-0000-0000-000000000001"), + CreatedAt = new DateTime(2026, 8, 16, 0, 0, 0, 0, DateTimeKind.Unspecified), + FK_PurchaseOrderId = new Guid("41000000-0000-0000-0000-000000000002"), + IsActive = true, + Reason = "Damaged in transit" + }); + }); + + modelBuilder.Entity("Distribution.Models.PurchaseReturnItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("FK_ItemId") + .HasColumnType("char(36)"); + + b.Property("FK_PurchaseReturnId") + .HasColumnType("char(36)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("Quantity") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("FK_ItemId"); + + b.HasIndex("FK_PurchaseReturnId"); + + b.ToTable("PurchaseReturnItems"); + + b.HasData( + new + { + Id = new Guid("46000000-0000-0000-0000-000000000001"), + CreatedAt = new DateTime(2026, 8, 16, 0, 0, 0, 0, DateTimeKind.Unspecified), + FK_ItemId = new Guid("23000000-0000-0000-0000-000000000003"), + FK_PurchaseReturnId = new Guid("45000000-0000-0000-0000-000000000001"), + IsActive = true, + Quantity = 10 + }); + }); + + modelBuilder.Entity("Distribution.Models.Region", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.HasIndex("Code") + .IsUnique(); + + b.ToTable("Regions"); + + b.HasData( + new + { + Id = new Guid("24000000-0000-0000-0000-000000000001"), + Code = "REG-0001", + CreatedAt = new DateTime(2026, 8, 20, 0, 0, 0, 0, DateTimeKind.Unspecified), + IsActive = true, + Name = "Western Province" + }, + new + { + Id = new Guid("24000000-0000-0000-0000-000000000002"), + Code = "REG-0002", + CreatedAt = new DateTime(2026, 8, 20, 0, 0, 0, 0, DateTimeKind.Unspecified), + IsActive = true, + Name = "Central Province" + }); + }); + + modelBuilder.Entity("Distribution.Models.RegionAgencyMap", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("FK_AgencyUserId") + .HasColumnType("char(36)"); + + b.Property("FK_RegionId") + .HasColumnType("char(36)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.HasKey("Id"); + + b.HasIndex("FK_AgencyUserId"); + + b.HasIndex("FK_RegionId", "FK_AgencyUserId") + .IsUnique(); + + b.ToTable("RegionAgencyMaps"); + + b.HasData( + new + { + Id = new Guid("57000000-0000-0000-0000-000000000001"), + CreatedAt = new DateTime(2026, 8, 20, 0, 0, 0, 0, DateTimeKind.Unspecified), + FK_AgencyUserId = new Guid("70000000-0000-0000-0000-000000000001"), + FK_RegionId = new Guid("24000000-0000-0000-0000-000000000001"), + IsActive = true + }); + }); + + modelBuilder.Entity("Distribution.Models.RegionalBudget", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("BudgetAmount") + .HasColumnType("decimal(65,30)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("FK_RegionId") + .HasColumnType("char(36)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("Period") + .IsRequired() + .HasColumnType("varchar(255)"); + + b.HasKey("Id"); + + b.HasIndex("FK_RegionId", "Period") + .IsUnique(); + + b.ToTable("RegionalBudgets"); + + b.HasData( + new + { + Id = new Guid("58000000-0000-0000-0000-000000000001"), + BudgetAmount = 200000m, + CreatedAt = new DateTime(2026, 8, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), + FK_RegionId = new Guid("24000000-0000-0000-0000-000000000001"), + IsActive = true, + Period = "2026-08" + }); + }); + + modelBuilder.Entity("Distribution.Models.RegionalTarget", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("FK_RegionId") + .HasColumnType("char(36)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("Period") + .IsRequired() + .HasColumnType("varchar(255)"); + + b.Property("TargetValue") + .HasColumnType("decimal(65,30)"); + + b.HasKey("Id"); + + b.HasIndex("FK_RegionId", "Period") + .IsUnique(); + + b.ToTable("RegionalTargets"); + + b.HasData( + new + { + Id = new Guid("59000000-0000-0000-0000-000000000001"), + CreatedAt = new DateTime(2026, 8, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), + FK_RegionId = new Guid("24000000-0000-0000-0000-000000000001"), + IsActive = true, + Period = "2026-08", + TargetValue = 500000m + }); + }); + + modelBuilder.Entity("Distribution.Models.RegionalTaxRule", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("FK_RegionId") + .HasColumnType("char(36)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("RuleConfig") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.HasIndex("FK_RegionId") + .IsUnique(); + + b.ToTable("RegionalTaxRules"); + + b.HasData( + new + { + Id = new Guid("60000000-0000-0000-0000-000000000001"), + CreatedAt = new DateTime(2026, 8, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), + FK_RegionId = new Guid("24000000-0000-0000-0000-000000000001"), + IsActive = true, + RuleConfig = "{\"rate\": 0.15}" + }); + }); + + modelBuilder.Entity("Distribution.Models.Role", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("Level") + .HasColumnType("int"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.ToTable("Roles"); + + b.HasData( + new + { + Id = new Guid("10000000-0000-0000-0000-000000000001"), + CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), + IsActive = true, + Level = 10, + Name = "SalesRep" + }, + new + { + Id = new Guid("10000000-0000-0000-0000-000000000002"), + CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), + IsActive = true, + Level = 20, + Name = "Supervisor" + }, + new + { + Id = new Guid("10000000-0000-0000-0000-000000000003"), + CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), + IsActive = true, + Level = 30, + Name = "AreaManager" + }, + new + { + Id = new Guid("10000000-0000-0000-0000-000000000004"), + CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), + IsActive = true, + Level = 40, + Name = "AreaSupervisor" + }, + new + { + Id = new Guid("10000000-0000-0000-0000-000000000005"), + CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), + IsActive = true, + Level = 50, + Name = "RegionalManager" + }, + new + { + Id = new Guid("10000000-0000-0000-0000-000000000006"), + CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), + IsActive = true, + Level = 60, + Name = "RegionalAgency" + }); + }); + + modelBuilder.Entity("Distribution.Models.RolePermission", b => + { + b.Property("FK_RoleId") + .HasColumnType("char(36)"); + + b.Property("FK_PermissionId") + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.HasKey("FK_RoleId", "FK_PermissionId"); + + b.HasIndex("FK_PermissionId"); + + b.ToTable("RolePermissions"); + }); + + modelBuilder.Entity("Distribution.Models.Route", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("EndPoint") + .HasColumnType("longtext"); + + b.Property("FK_OwnerUserId") + .HasColumnType("char(36)"); + + b.Property("FK_RegionId") + .HasColumnType("char(36)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("Length") + .HasColumnType("decimal(65,30)"); + + b.Property("RouteName") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("StartPoint") + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.HasIndex("FK_OwnerUserId"); + + b.HasIndex("FK_RegionId"); + + b.ToTable("Routes"); + + b.HasData( + new + { + Id = new Guid("25000000-0000-0000-0000-000000000001"), + CreatedAt = new DateTime(2026, 8, 20, 0, 0, 0, 0, DateTimeKind.Unspecified), + EndPoint = "Dehiwala", + FK_OwnerUserId = new Guid("29000000-0000-0000-0000-000000000002"), + FK_RegionId = new Guid("24000000-0000-0000-0000-000000000001"), + IsActive = true, + Length = 18.5m, + RouteName = "Colombo City Route", + StartPoint = "Colombo Fort" + }, + new + { + Id = new Guid("25000000-0000-0000-0000-000000000002"), + CreatedAt = new DateTime(2026, 8, 20, 0, 0, 0, 0, DateTimeKind.Unspecified), + EndPoint = "Negombo", + FK_OwnerUserId = new Guid("29000000-0000-0000-0000-000000000002"), + FK_RegionId = new Guid("24000000-0000-0000-0000-000000000001"), + IsActive = true, + Length = 32.0m, + RouteName = "Negombo Coastal Route", + StartPoint = "Wattala" + }, + new + { + Id = new Guid("25000000-0000-0000-0000-000000000003"), + CreatedAt = new DateTime(2026, 8, 20, 0, 0, 0, 0, DateTimeKind.Unspecified), + EndPoint = "Peradeniya", + FK_OwnerUserId = new Guid("29000000-0000-0000-0000-000000000003"), + FK_RegionId = new Guid("24000000-0000-0000-0000-000000000002"), + IsActive = true, + Length = 12.0m, + RouteName = "Kandy Town Route", + StartPoint = "Kandy Lake" + }, + new + { + Id = new Guid("25000000-0000-0000-0000-000000000004"), + CreatedAt = new DateTime(2026, 8, 20, 0, 0, 0, 0, DateTimeKind.Unspecified), + EndPoint = "Matale", + FK_OwnerUserId = new Guid("29000000-0000-0000-0000-000000000003"), + FK_RegionId = new Guid("24000000-0000-0000-0000-000000000002"), + IsActive = true, + Length = 26.0m, + RouteName = "Matale Route", + StartPoint = "Kandy" + }); + }); + + modelBuilder.Entity("Distribution.Models.RouteVisitLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CheckInAt") + .HasColumnType("datetime(6)"); + + b.Property("CheckInLatitude") + .HasColumnType("decimal(65,30)"); + + b.Property("CheckInLongitude") + .HasColumnType("decimal(65,30)"); + + b.Property("CheckOutAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("FK_RouteId") + .HasColumnType("char(36)"); + + b.Property("FK_StoreId") + .HasColumnType("char(36)"); + + b.Property("FK_UserId") + .HasColumnType("char(36)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("IsDeviationFlagged") + .HasColumnType("tinyint(1)"); + + b.HasKey("Id"); + + b.HasIndex("FK_RouteId"); + + b.HasIndex("FK_StoreId"); + + b.HasIndex("FK_UserId"); + + b.ToTable("RouteVisitLogs"); + + b.HasData( + new + { + Id = new Guid("66000000-0000-0000-0000-000000000001"), + CheckInAt = new DateTime(2026, 8, 25, 9, 0, 0, 0, DateTimeKind.Unspecified), + CheckInLatitude = 6.9344m, + CheckInLongitude = 79.8428m, + CheckOutAt = new DateTime(2026, 8, 25, 9, 25, 0, 0, DateTimeKind.Unspecified), + CreatedAt = new DateTime(2026, 8, 25, 9, 0, 0, 0, DateTimeKind.Unspecified), + FK_RouteId = new Guid("25000000-0000-0000-0000-000000000001"), + FK_StoreId = new Guid("26000000-0000-0000-0000-000000000001"), + FK_UserId = new Guid("29000000-0000-0000-0000-000000000002"), + IsActive = true, + IsDeviationFlagged = false + }, + new + { + Id = new Guid("66000000-0000-0000-0000-000000000002"), + CheckInAt = new DateTime(2026, 8, 25, 10, 0, 0, 0, DateTimeKind.Unspecified), + CheckInLatitude = 6.9000m, + CheckInLongitude = 79.8654m, + CreatedAt = new DateTime(2026, 8, 25, 10, 0, 0, 0, DateTimeKind.Unspecified), + FK_RouteId = new Guid("25000000-0000-0000-0000-000000000001"), + FK_StoreId = new Guid("26000000-0000-0000-0000-000000000002"), + FK_UserId = new Guid("29000000-0000-0000-0000-000000000002"), + IsActive = true, + IsDeviationFlagged = true + }); + }); + + modelBuilder.Entity("Distribution.Models.SaleReturn", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedByUserId") + .HasColumnType("char(36)"); + + b.Property("FK_ShopPOId") + .HasColumnType("char(36)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("Mode") + .HasColumnType("int"); + + b.Property("TotalCreditAmount") + .HasColumnType("decimal(65,30)"); + + b.HasKey("Id"); + + b.HasIndex("CreatedByUserId"); + + b.HasIndex("FK_ShopPOId"); + + b.ToTable("SaleReturns"); + + b.HasData( + new + { + Id = new Guid("51000000-0000-0000-0000-000000000001"), + CreatedAt = new DateTime(2026, 8, 26, 0, 0, 0, 0, DateTimeKind.Unspecified), + CreatedByUserId = new Guid("29000000-0000-0000-0000-000000000002"), + FK_ShopPOId = new Guid("33000000-0000-0000-0000-000000000001"), + IsActive = true, + Mode = 0, + TotalCreditAmount = 300m + }); + }); + + modelBuilder.Entity("Distribution.Models.SaleReturnItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("FK_ItemId") + .HasColumnType("char(36)"); + + b.Property("FK_SaleReturnId") + .HasColumnType("char(36)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("Quantity") + .HasColumnType("decimal(65,30)"); + + b.Property("ReasonCode") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasColumnType("decimal(65,30)"); + + b.HasKey("Id"); + + b.HasIndex("FK_ItemId"); + + b.HasIndex("FK_SaleReturnId"); + + b.ToTable("SaleReturnItems"); + + b.HasData( + new + { + Id = new Guid("52000000-0000-0000-0000-000000000001"), + CreatedAt = new DateTime(2026, 8, 26, 0, 0, 0, 0, DateTimeKind.Unspecified), + FK_ItemId = new Guid("23000000-0000-0000-0000-000000000001"), + FK_SaleReturnId = new Guid("51000000-0000-0000-0000-000000000001"), + IsActive = true, + Quantity = 5m, + ReasonCode = 1, + UnitPrice = 60m + }); + }); + + modelBuilder.Entity("Distribution.Models.SaleRoute", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.HasKey("Id"); + + b.ToTable("SaleRoutes"); + + b.HasData( + new + { + Id = new Guid("69000000-0000-0000-0000-000000000001"), + CreatedAt = new DateTime(2026, 8, 20, 0, 0, 0, 0, DateTimeKind.Unspecified), + IsActive = true + }, + new + { + Id = new Guid("69000000-0000-0000-0000-000000000002"), + CreatedAt = new DateTime(2026, 8, 20, 0, 0, 0, 0, DateTimeKind.Unspecified), + IsActive = true + }); + }); + + modelBuilder.Entity("Distribution.Models.Scheme", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("AgentLimitExempt") + .HasColumnType("tinyint(1)"); + + b.Property("BaselineExpectedSales") + .HasColumnType("decimal(65,30)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("DateRangeFrom") + .HasColumnType("datetime(6)"); + + b.Property("DateRangeTo") + .HasColumnType("datetime(6)"); + + b.Property("EligibleSegment") + .HasColumnType("longtext"); + + b.Property("FK_FreeItemId") + .HasColumnType("char(36)"); + + b.Property("FK_RegionId") + .HasColumnType("char(36)"); + + b.Property("FK_TriggerItemId") + .HasColumnType("char(36)"); + + b.Property("FreeQty") + .HasColumnType("decimal(65,30)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("RegionId") + .HasColumnType("char(36)"); + + b.Property("RuleConfig") + .HasColumnType("longtext"); + + b.Property("RuleType") + .HasColumnType("int"); + + b.Property("TriggerQty") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("FK_FreeItemId"); + + b.HasIndex("FK_TriggerItemId"); + + b.HasIndex("RegionId"); + + b.ToTable("Schemes"); + + b.HasData( + new + { + Id = new Guid("48000000-0000-0000-0000-000000000001"), + AgentLimitExempt = false, + BaselineExpectedSales = 500000m, + CreatedAt = new DateTime(2026, 8, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), + DateRangeFrom = new DateTime(2026, 8, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), + DateRangeTo = new DateTime(2026, 12, 31, 0, 0, 0, 0, DateTimeKind.Unspecified), + FK_FreeItemId = new Guid("23000000-0000-0000-0000-000000000001"), + FK_RegionId = new Guid("24000000-0000-0000-0000-000000000001"), + FK_TriggerItemId = new Guid("23000000-0000-0000-0000-000000000001"), + FreeQty = 1m, + IsActive = true, + Name = "Buy 10 Coke Get 1 Free", + RuleType = 0, + TriggerQty = 10 + }, + new + { + Id = new Guid("48000000-0000-0000-0000-000000000002"), + AgentLimitExempt = false, + CreatedAt = new DateTime(2026, 8, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), + DateRangeFrom = new DateTime(2026, 8, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), + DateRangeTo = new DateTime(2026, 12, 31, 0, 0, 0, 0, DateTimeKind.Unspecified), + FK_FreeItemId = new Guid("23000000-0000-0000-0000-000000000004"), + FK_RegionId = new Guid("24000000-0000-0000-0000-000000000002"), + FreeQty = 1m, + IsActive = true, + Name = "Snack Combo Bundle", + RuleType = 3 + }); + }); + + modelBuilder.Entity("Distribution.Models.SchemeBundleComponent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("FK_ItemId") + .HasColumnType("char(36)"); + + b.Property("FK_SchemeId") + .HasColumnType("char(36)"); + + b.Property("Qty") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("FK_ItemId"); + + b.HasIndex("FK_SchemeId", "FK_ItemId") + .IsUnique(); + + b.ToTable("SchemeBundleComponents"); + + b.HasData( + new + { + Id = new Guid("49000000-0000-0000-0000-000000000001"), + FK_ItemId = new Guid("23000000-0000-0000-0000-000000000003"), + FK_SchemeId = new Guid("48000000-0000-0000-0000-000000000002"), + Qty = 5 + }); + }); + + modelBuilder.Entity("Distribution.Models.SchemeRedemption", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("DiscountAmount") + .HasColumnType("decimal(65,30)"); + + b.Property("FK_FreeItemId") + .HasColumnType("char(36)"); + + b.Property("FK_SchemeId") + .HasColumnType("char(36)"); + + b.Property("FK_ShopPOId") + .HasColumnType("char(36)"); + + b.Property("FieldInvoiceId") + .HasColumnType("char(36)"); + + b.Property("FreeItemCostValue") + .HasColumnType("decimal(65,30)"); + + b.Property("FreeQty") + .HasColumnType("decimal(65,30)"); + + b.Property("RedeemedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("FK_FreeItemId"); + + b.HasIndex("FK_SchemeId"); + + b.HasIndex("FK_ShopPOId"); + + b.ToTable("SchemeRedemptions"); + + b.HasData( + new + { + Id = new Guid("50000000-0000-0000-0000-000000000001"), + DiscountAmount = 0m, + FK_FreeItemId = new Guid("23000000-0000-0000-0000-000000000001"), + FK_SchemeId = new Guid("48000000-0000-0000-0000-000000000001"), + FK_ShopPOId = new Guid("33000000-0000-0000-0000-000000000001"), + FreeItemCostValue = 40m, + FreeQty = 1m, + RedeemedAt = new DateTime(2026, 8, 25, 9, 30, 0, 0, DateTimeKind.Unspecified) + }); + }); + + modelBuilder.Entity("Distribution.Models.ShopPO", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CancelReason") + .HasColumnType("longtext"); + + b.Property("CancelledAt") + .HasColumnType("datetime(6)"); + + b.Property("CancelledBy") + .HasColumnType("char(36)"); + + b.Property("ConfirmedAt") + .HasColumnType("datetime(6)"); + + b.Property("ConfirmedBy") + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("FK_StoreId") + .HasColumnType("char(36)"); + + b.Property("FulfilledAt") + .HasColumnType("datetime(6)"); + + b.Property("InvoiceId") + .HasColumnType("char(36)") + .HasColumnName("InvoiceId"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("RaisedById") + .HasColumnType("char(36)"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("TotalAmount") + .HasColumnType("decimal(65,30)"); + + b.HasKey("Id"); + + b.HasIndex("FK_StoreId"); + + b.ToTable("ShopPOs"); + + b.HasData( + new + { + Id = new Guid("33000000-0000-0000-0000-000000000001"), + ConfirmedAt = new DateTime(2026, 8, 25, 8, 30, 0, 0, DateTimeKind.Unspecified), + ConfirmedBy = new Guid("29000000-0000-0000-0000-000000000001"), + CreatedAt = new DateTime(2026, 8, 25, 9, 0, 0, 0, DateTimeKind.Unspecified), + FK_StoreId = new Guid("26000000-0000-0000-0000-000000000001"), + FulfilledAt = new DateTime(2026, 8, 25, 13, 0, 0, 0, DateTimeKind.Unspecified), + InvoiceId = new Guid("34500000-0000-0000-0000-000000000001"), + IsActive = true, + RaisedById = new Guid("29000000-0000-0000-0000-000000000002"), + Status = 2, + TotalAmount = 3000m + }, + new + { + Id = new Guid("33000000-0000-0000-0000-000000000002"), + CreatedAt = new DateTime(2026, 8, 30, 10, 0, 0, 0, DateTimeKind.Unspecified), + FK_StoreId = new Guid("26000000-0000-0000-0000-000000000005"), + IsActive = true, + RaisedById = new Guid("29000000-0000-0000-0000-000000000003"), + Status = 0, + TotalAmount = 1200m + }, + new + { + Id = new Guid("33000000-0000-0000-0000-000000000003"), + ConfirmedAt = new DateTime(2026, 8, 31, 8, 30, 0, 0, DateTimeKind.Unspecified), + ConfirmedBy = new Guid("29000000-0000-0000-0000-000000000001"), + CreatedAt = new DateTime(2026, 8, 31, 9, 0, 0, 0, DateTimeKind.Unspecified), + FK_StoreId = new Guid("26000000-0000-0000-0000-000000000005"), + FulfilledAt = new DateTime(2026, 8, 31, 13, 0, 0, 0, DateTimeKind.Unspecified), + InvoiceId = new Guid("34500000-0000-0000-0000-000000000002"), + IsActive = true, + RaisedById = new Guid("29000000-0000-0000-0000-000000000003"), + Status = 2, + TotalAmount = 2400m + }); + }); + + modelBuilder.Entity("Distribution.Models.ShopPOItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("FK_ItemId") + .HasColumnType("char(36)"); + + b.Property("FK_ShopPOId") + .HasColumnType("char(36)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("Quantity") + .HasColumnType("int"); + + b.Property("QuantityFulfilled") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasColumnType("decimal(65,30)"); + + b.HasKey("Id"); + + b.HasIndex("FK_ItemId"); + + b.HasIndex("FK_ShopPOId"); + + b.ToTable("ShopPOItems"); + + b.HasData( + new + { + Id = new Guid("34000000-0000-0000-0000-000000000001"), + CreatedAt = new DateTime(2026, 8, 25, 9, 0, 0, 0, DateTimeKind.Unspecified), + FK_ItemId = new Guid("23000000-0000-0000-0000-000000000001"), + FK_ShopPOId = new Guid("33000000-0000-0000-0000-000000000001"), + IsActive = true, + Quantity = 30, + QuantityFulfilled = 30, + UnitPrice = 60m + }, + new + { + Id = new Guid("34000000-0000-0000-0000-000000000002"), + CreatedAt = new DateTime(2026, 8, 25, 9, 0, 0, 0, DateTimeKind.Unspecified), + FK_ItemId = new Guid("23000000-0000-0000-0000-000000000003"), + FK_ShopPOId = new Guid("33000000-0000-0000-0000-000000000001"), + IsActive = true, + Quantity = 24, + QuantityFulfilled = 24, + UnitPrice = 50m + }, + new + { + Id = new Guid("34000000-0000-0000-0000-000000000003"), + CreatedAt = new DateTime(2026, 8, 30, 10, 0, 0, 0, DateTimeKind.Unspecified), + FK_ItemId = new Guid("23000000-0000-0000-0000-000000000002"), + FK_ShopPOId = new Guid("33000000-0000-0000-0000-000000000002"), + IsActive = true, + Quantity = 10, + QuantityFulfilled = 0, + UnitPrice = 120m + }, + new + { + Id = new Guid("34000000-0000-0000-0000-000000000004"), + CreatedAt = new DateTime(2026, 8, 31, 9, 0, 0, 0, DateTimeKind.Unspecified), + FK_ItemId = new Guid("23000000-0000-0000-0000-000000000002"), + FK_ShopPOId = new Guid("33000000-0000-0000-0000-000000000003"), + IsActive = true, + Quantity = 20, + QuantityFulfilled = 20, + UnitPrice = 120m + }); + }); + + modelBuilder.Entity("Distribution.Models.StockBalance", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("FK_ItemBatchId") + .HasColumnType("char(36)"); + + b.Property("FK_ItemId") + .HasColumnType("char(36)"); + + b.Property("FK_ItemSerialNumberId") + .HasColumnType("char(36)"); + + b.Property("LocationId") + .HasColumnType("char(36)"); + + b.Property("LocationType") + .HasColumnType("int"); + + b.Property("Quantity") + .HasColumnType("decimal(65,30)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("FK_ItemBatchId"); + + b.HasIndex("FK_ItemSerialNumberId"); + + b.HasIndex("FK_ItemId", "LocationId", "FK_ItemBatchId", "FK_ItemSerialNumberId") + .IsUnique(); + + b.ToTable("StockBalances"); + + b.HasData( + new + { + Id = new Guid("61000000-0000-0000-0000-000000000001"), + FK_ItemId = new Guid("23000000-0000-0000-0000-000000000001"), + LocationId = new Guid("27000000-0000-0000-0000-000000000001"), + LocationType = 0, + Quantity = 150m, + UpdatedAt = new DateTime(2026, 8, 25, 9, 30, 0, 0, DateTimeKind.Unspecified) + }, + new + { + Id = new Guid("61000000-0000-0000-0000-000000000002"), + FK_ItemId = new Guid("23000000-0000-0000-0000-000000000003"), + LocationId = new Guid("27000000-0000-0000-0000-000000000001"), + LocationType = 0, + Quantity = 90m, + UpdatedAt = new DateTime(2026, 8, 25, 9, 30, 0, 0, DateTimeKind.Unspecified) + }, + new + { + Id = new Guid("61000000-0000-0000-0000-000000000003"), + FK_ItemId = new Guid("23000000-0000-0000-0000-000000000001"), + LocationId = new Guid("28000000-0000-0000-0000-000000000001"), + LocationType = 1, + Quantity = 20m, + UpdatedAt = new DateTime(2026, 8, 25, 9, 30, 0, 0, DateTimeKind.Unspecified) + }); + }); + + modelBuilder.Entity("Distribution.Models.StockMovement", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .HasColumnType("char(36)"); + + b.Property("FK_ItemBatchId") + .HasColumnType("char(36)"); + + b.Property("FK_ItemId") + .HasColumnType("char(36)"); + + b.Property("FK_ItemSerialNumberId") + .HasColumnType("char(36)"); + + b.Property("FromLocationId") + .HasColumnType("char(36)"); + + b.Property("FromLocationType") + .HasColumnType("int"); + + b.Property("Quantity") + .HasColumnType("decimal(65,30)"); + + b.Property("ReasonCode") + .HasColumnType("longtext"); + + b.Property("ReferenceId") + .HasColumnType("char(36)"); + + b.Property("ToLocationId") + .HasColumnType("char(36)"); + + b.Property("ToLocationType") + .HasColumnType("int"); + + b.Property("Type") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("FK_ItemBatchId"); + + b.HasIndex("FK_ItemId"); + + b.HasIndex("FK_ItemSerialNumberId"); + + b.ToTable("StockMovements"); + + b.HasData( + new + { + Id = new Guid("62000000-0000-0000-0000-000000000001"), + CreatedAt = new DateTime(2026, 8, 15, 0, 0, 0, 0, DateTimeKind.Unspecified), + CreatedBy = new Guid("29000000-0000-0000-0000-000000000001"), + FK_ItemId = new Guid("23000000-0000-0000-0000-000000000001"), + FromLocationId = new Guid("00000000-0000-0000-0000-000000000000"), + FromLocationType = 2, + Quantity = 200m, + ReferenceId = new Guid("43000000-0000-0000-0000-000000000001"), + ToLocationId = new Guid("27000000-0000-0000-0000-000000000001"), + ToLocationType = 0, + Type = 6 + }, + new + { + Id = new Guid("62000000-0000-0000-0000-000000000002"), + CreatedAt = new DateTime(2026, 8, 25, 7, 30, 0, 0, DateTimeKind.Unspecified), + CreatedBy = new Guid("29000000-0000-0000-0000-000000000001"), + FK_ItemId = new Guid("23000000-0000-0000-0000-000000000001"), + FromLocationId = new Guid("27000000-0000-0000-0000-000000000001"), + FromLocationType = 0, + Quantity = 50m, + ReferenceId = new Guid("35000000-0000-0000-0000-000000000001"), + ToLocationId = new Guid("28000000-0000-0000-0000-000000000001"), + ToLocationType = 1, + Type = 5 + }, + new + { + Id = new Guid("62000000-0000-0000-0000-000000000003"), + CreatedAt = new DateTime(2026, 8, 25, 9, 30, 0, 0, DateTimeKind.Unspecified), + CreatedBy = new Guid("29000000-0000-0000-0000-000000000002"), + FK_ItemId = new Guid("23000000-0000-0000-0000-000000000001"), + FromLocationId = new Guid("28000000-0000-0000-0000-000000000001"), + FromLocationType = 1, + Quantity = 30m, + ReferenceId = new Guid("32000000-0000-0000-0000-000000000001"), + ToLocationId = new Guid("00000000-0000-0000-0000-000000000000"), + ToLocationType = 2, + Type = 0 + }); + }); + + modelBuilder.Entity("Distribution.Models.Store", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("ContactNumber") + .HasColumnType("longtext"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreditAmount") + .HasColumnType("decimal(65,30)"); + + b.Property("CreditLimit") + .HasColumnType("decimal(65,30)"); + + b.Property("FK_RouteId") + .HasColumnType("char(36)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("Latitude") + .HasColumnType("decimal(65,30)"); + + b.Property("Longitude") + .HasColumnType("decimal(65,30)"); + + b.Property("OneTimeAmount") + .HasColumnType("decimal(65,30)"); + + b.Property("OutstandingBalance") + .HasColumnType("decimal(65,30)"); + + b.Property("OwnerName") + .HasColumnType("longtext"); + + b.Property("Segment") + .HasColumnType("longtext"); + + b.Property("StoreAddress") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("StoreName") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.HasIndex("FK_RouteId"); + + b.ToTable("Stores"); + + b.HasData( + new + { + Id = new Guid("26000000-0000-0000-0000-000000000001"), + CreatedAt = new DateTime(2026, 8, 20, 0, 0, 0, 0, DateTimeKind.Unspecified), + CreditAmount = 0m, + CreditLimit = 50000m, + FK_RouteId = new Guid("25000000-0000-0000-0000-000000000001"), + IsActive = true, + Latitude = 6.9344m, + Longitude = 79.8428m, + OneTimeAmount = 0m, + OutstandingBalance = 0m, + OwnerName = "Saman Kumara", + Segment = "Gold", + StoreAddress = "12 York Street, Colombo Fort", + StoreName = "Fort Mini Mart" + }, + new + { + Id = new Guid("26000000-0000-0000-0000-000000000002"), + CreatedAt = new DateTime(2026, 8, 20, 0, 0, 0, 0, DateTimeKind.Unspecified), + CreditAmount = 0m, + CreditLimit = 30000m, + FK_RouteId = new Guid("25000000-0000-0000-0000-000000000001"), + IsActive = true, + Latitude = 6.8508m, + Longitude = 79.8654m, + OneTimeAmount = 0m, + OutstandingBalance = 5000m, + OwnerName = "Nadeesha Silva", + Segment = "Silver", + StoreAddress = "45 Galle Road, Dehiwala", + StoreName = "Dehiwala Grocery" + }, + new + { + Id = new Guid("26000000-0000-0000-0000-000000000003"), + CreatedAt = new DateTime(2026, 8, 20, 0, 0, 0, 0, DateTimeKind.Unspecified), + CreditAmount = 0m, + CreditLimit = 40000m, + FK_RouteId = new Guid("25000000-0000-0000-0000-000000000002"), + IsActive = true, + Latitude = 6.9890m, + Longitude = 79.8912m, + OneTimeAmount = 0m, + OutstandingBalance = 1200m, + OwnerName = "Ruwan Gunasekara", + Segment = "Gold", + StoreAddress = "88 Negombo Road, Wattala", + StoreName = "Wattala Supermarket" + }, + new + { + Id = new Guid("26000000-0000-0000-0000-000000000004"), + CreatedAt = new DateTime(2026, 8, 20, 0, 0, 0, 0, DateTimeKind.Unspecified), + CreditAmount = 0m, + CreditLimit = 25000m, + FK_RouteId = new Guid("25000000-0000-0000-0000-000000000002"), + IsActive = true, + Latitude = 7.2094m, + Longitude = 79.8380m, + OneTimeAmount = 0m, + OutstandingBalance = 0m, + OwnerName = "Chamari Peiris", + StoreAddress = "5 Beach Road, Negombo", + StoreName = "Negombo Beach Store" + }, + new + { + Id = new Guid("26000000-0000-0000-0000-000000000005"), + CreatedAt = new DateTime(2026, 8, 20, 0, 0, 0, 0, DateTimeKind.Unspecified), + CreditAmount = 0m, + CreditLimit = 35000m, + FK_RouteId = new Guid("25000000-0000-0000-0000-000000000003"), + IsActive = true, + Latitude = 7.2906m, + Longitude = 80.6337m, + OneTimeAmount = 0m, + OutstandingBalance = 0m, + OwnerName = "Dinesh Wickramasinghe", + Segment = "Key Account", + StoreAddress = "3 Lake Road, Kandy", + StoreName = "Kandy Lake View Store" + }, + new + { + Id = new Guid("26000000-0000-0000-0000-000000000006"), + CreatedAt = new DateTime(2026, 8, 20, 0, 0, 0, 0, DateTimeKind.Unspecified), + CreditAmount = 0m, + CreditLimit = 20000m, + FK_RouteId = new Guid("25000000-0000-0000-0000-000000000004"), + IsActive = true, + Latitude = 7.4675m, + Longitude = 80.6234m, + OneTimeAmount = 0m, + OutstandingBalance = 0m, + OwnerName = "Ishara Bandara", + StoreAddress = "20 Main Street, Matale", + StoreName = "Matale Central Store" + }); + }); + + modelBuilder.Entity("Distribution.Models.SubCategory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("ExternalErpId") + .HasColumnType("int"); + + b.Property("FK_CategoryId") + .HasColumnType("char(36)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.HasIndex("FK_CategoryId"); + + b.ToTable("SubCategories"); + }); + + modelBuilder.Entity("Distribution.Models.Supplier", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("ContactInfo") + .HasColumnType("longtext"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("PerformanceScore") + .HasColumnType("decimal(65,30)"); + + b.HasKey("Id"); + + b.ToTable("Suppliers"); + + b.HasData( + new + { + Id = new Guid("38000000-0000-0000-0000-000000000001"), + ContactInfo = "+94 11 234 5678", + CreatedAt = new DateTime(2026, 8, 14, 0, 0, 0, 0, DateTimeKind.Unspecified), + IsActive = true, + Name = "Ceylon Beverages Ltd", + PerformanceScore = 4.2m + }, + new + { + Id = new Guid("38000000-0000-0000-0000-000000000002"), + ContactInfo = "+94 11 876 5432", + CreatedAt = new DateTime(2026, 8, 14, 0, 0, 0, 0, DateTimeKind.Unspecified), + IsActive = true, + Name = "Snack Foods (Pvt) Ltd" + }); + }); + + modelBuilder.Entity("Distribution.Models.SupplierClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Details") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("EvidenceUrl") + .HasColumnType("longtext"); + + b.Property("FK_GoodsReceiptId") + .HasColumnType("char(36)"); + + b.Property("FK_PurchaseOrderId") + .HasColumnType("char(36)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("RaisedByUserId") + .HasColumnType("char(36)"); + + b.Property("ResolutionAmount") + .HasColumnType("decimal(65,30)"); + + b.Property("ResolutionNotes") + .HasColumnType("longtext"); + + b.Property("ResolutionType") + .HasColumnType("int"); + + b.Property("ResolvedAt") + .HasColumnType("datetime(6)"); + + b.Property("ResolvedByUserId") + .HasColumnType("char(36)"); + + b.Property("Status") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("FK_GoodsReceiptId"); + + b.HasIndex("FK_PurchaseOrderId"); + + b.HasIndex("RaisedByUserId"); + + b.HasIndex("ResolvedByUserId"); + + b.ToTable("SupplierClaims"); + + b.HasData( + new + { + Id = new Guid("55000000-0000-0000-0000-000000000001"), + CreatedAt = new DateTime(2026, 8, 15, 0, 0, 0, 0, DateTimeKind.Unspecified), + Details = "2 units of Coke 1.5L received damaged", + FK_GoodsReceiptId = new Guid("43000000-0000-0000-0000-000000000001"), + FK_PurchaseOrderId = new Guid("41000000-0000-0000-0000-000000000001"), + IsActive = true, + RaisedByUserId = new Guid("29000000-0000-0000-0000-000000000001"), + ResolutionAmount = 160m, + ResolutionNotes = "Credited against next invoice", + ResolutionType = 1, + ResolvedAt = new DateTime(2026, 8, 16, 0, 0, 0, 0, DateTimeKind.Unspecified), + ResolvedByUserId = new Guid("29000000-0000-0000-0000-000000000006"), + Status = 1 + }); + }); + + modelBuilder.Entity("Distribution.Models.SupplierClaimItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("FK_ItemId") + .HasColumnType("char(36)"); + + b.Property("FK_SupplierClaimId") + .HasColumnType("char(36)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("IssueType") + .HasColumnType("int"); + + b.Property("Quantity") + .HasColumnType("decimal(65,30)"); + + b.HasKey("Id"); + + b.HasIndex("FK_ItemId"); + + b.HasIndex("FK_SupplierClaimId"); + + b.ToTable("SupplierClaimItems"); + + b.HasData( + new + { + Id = new Guid("56000000-0000-0000-0000-000000000001"), + CreatedAt = new DateTime(2026, 8, 15, 0, 0, 0, 0, DateTimeKind.Unspecified), + FK_ItemId = new Guid("23000000-0000-0000-0000-000000000002"), + FK_SupplierClaimId = new Guid("55000000-0000-0000-0000-000000000001"), + IsActive = true, + IssueType = 0, + Quantity = 2m + }); + }); + + modelBuilder.Entity("Distribution.Models.SysConfig", b => + { + b.Property("Key") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("UpdatedBy") + .HasColumnType("char(36)"); + + b.Property("Value") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Key"); + + b.ToTable("SysConfigs"); + + b.HasData( + new + { + Key = "InvoiceCreationMode", + UpdatedAt = new DateTime(2026, 9, 17, 0, 0, 0, 0, DateTimeKind.Unspecified), + Value = "ModeB" + }, + new + { + Key = "Feature.SerialNumberTracking", + UpdatedAt = new DateTime(2026, 10, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), + Value = "false" + }, + new + { + Key = "Feature.BatchTracking", + UpdatedAt = new DateTime(2026, 10, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), + Value = "false" + }, + new + { + Key = "Feature.ExpiryDateTracking", + UpdatedAt = new DateTime(2026, 10, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), + Value = "false" + }, + new + { + Key = "Feature.WarrantyTracking", + UpdatedAt = new DateTime(2026, 10, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), + Value = "false" + }, + new + { + Key = "Feature.CustomWarrantyOverride", + UpdatedAt = new DateTime(2026, 10, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), + Value = "false" + }, + new + { + Key = "Feature.DefaultWarrantyPeriodDays", + UpdatedAt = new DateTime(2026, 10, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), + Value = "0" + }); + }); + + modelBuilder.Entity("Distribution.Models.Uom", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("ExternalErpId") + .HasColumnType("int"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.ToTable("Uoms"); + + b.HasData( + new + { + Id = new Guid("22000000-0000-0000-0000-000000000001"), + CreatedAt = new DateTime(2026, 8, 20, 0, 0, 0, 0, DateTimeKind.Unspecified), + IsActive = true, + Name = "Piece" + }, + new + { + Id = new Guid("67000000-0000-0000-0000-000000000001"), + CreatedAt = new DateTime(2026, 8, 20, 0, 0, 0, 0, DateTimeKind.Unspecified), + IsActive = true, + Name = "Case" + }); + }); + + modelBuilder.Entity("Distribution.Models.UomConversion", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("FK_FromUomId") + .HasColumnType("char(36)"); + + b.Property("FK_ItemId") + .HasColumnType("char(36)"); + + b.Property("FK_ToUomId") + .HasColumnType("char(36)"); + + b.Property("Factor") + .HasColumnType("decimal(65,30)"); + + b.HasKey("Id"); + + b.HasIndex("FK_FromUomId"); + + b.HasIndex("FK_ItemId"); + + b.HasIndex("FK_ToUomId"); + + b.ToTable("UomConversions"); + + b.HasData( + new + { + Id = new Guid("67000000-0000-0000-0000-000000000002"), + FK_FromUomId = new Guid("67000000-0000-0000-0000-000000000001"), + FK_ItemId = new Guid("23000000-0000-0000-0000-000000000001"), + FK_ToUomId = new Guid("22000000-0000-0000-0000-000000000001"), + Factor = 24m + }); + }); + + modelBuilder.Entity("Distribution.Models.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("FK_ReportsToUserId") + .HasColumnType("char(36)"); + + b.Property("FK_RoleId") + .HasColumnType("char(36)"); + + b.Property("FullName") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("PasswordHash") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Username") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.HasIndex("FK_ReportsToUserId"); + + b.HasIndex("FK_RoleId"); + + b.ToTable("Users"); + + b.HasData( + new + { + Id = new Guid("29000000-0000-0000-0000-000000000001"), + CreatedAt = new DateTime(2026, 8, 20, 0, 0, 0, 0, DateTimeKind.Unspecified), + FK_RoleId = new Guid("10000000-0000-0000-0000-000000000002"), + FullName = "Sunil Fernando", + IsActive = true, + PasswordHash = "AQAAAAIAAYagAAAAEKGnq/U4j6uM9g5mFkxbUE8WU/AhnSNiwrKCiPAqGnzNcaMgZAF5Zal/rJUUYPZPpg==", + Username = "sunil.fernando" + }, + new + { + Id = new Guid("29000000-0000-0000-0000-000000000002"), + CreatedAt = new DateTime(2026, 8, 20, 0, 0, 0, 0, DateTimeKind.Unspecified), + FK_ReportsToUserId = new Guid("29000000-0000-0000-0000-000000000001"), + FK_RoleId = new Guid("10000000-0000-0000-0000-000000000001"), + FullName = "Nimal Perera", + IsActive = true, + PasswordHash = "AQAAAAIAAYagAAAAEKGnq/U4j6uM9g5mFkxbUE8WU/AhnSNiwrKCiPAqGnzNcaMgZAF5Zal/rJUUYPZPpg==", + Username = "nimal.perera" + }, + new + { + Id = new Guid("29000000-0000-0000-0000-000000000003"), + CreatedAt = new DateTime(2026, 8, 20, 0, 0, 0, 0, DateTimeKind.Unspecified), + FK_ReportsToUserId = new Guid("29000000-0000-0000-0000-000000000001"), + FK_RoleId = new Guid("10000000-0000-0000-0000-000000000001"), + FullName = "Kamal Silva", + IsActive = true, + PasswordHash = "AQAAAAIAAYagAAAAEKGnq/U4j6uM9g5mFkxbUE8WU/AhnSNiwrKCiPAqGnzNcaMgZAF5Zal/rJUUYPZPpg==", + Username = "kamal.silva" + }, + new + { + Id = new Guid("29000000-0000-0000-0000-000000000004"), + CreatedAt = new DateTime(2026, 8, 20, 0, 0, 0, 0, DateTimeKind.Unspecified), + FK_ReportsToUserId = new Guid("29000000-0000-0000-0000-000000000001"), + FK_RoleId = new Guid("10000000-0000-0000-0000-000000000001"), + FullName = "Ruwan Jayasuriya", + IsActive = true, + PasswordHash = "AQAAAAIAAYagAAAAEKGnq/U4j6uM9g5mFkxbUE8WU/AhnSNiwrKCiPAqGnzNcaMgZAF5Zal/rJUUYPZPpg==", + Username = "ruwan.jayasuriya" + }, + new + { + Id = new Guid("29000000-0000-0000-0000-000000000005"), + CreatedAt = new DateTime(2026, 8, 20, 0, 0, 0, 0, DateTimeKind.Unspecified), + FK_ReportsToUserId = new Guid("29000000-0000-0000-0000-000000000001"), + FK_RoleId = new Guid("10000000-0000-0000-0000-000000000001"), + FullName = "Priyantha Kumara", + IsActive = true, + PasswordHash = "AQAAAAIAAYagAAAAEKGnq/U4j6uM9g5mFkxbUE8WU/AhnSNiwrKCiPAqGnzNcaMgZAF5Zal/rJUUYPZPpg==", + Username = "priyantha.kumara" + }, + new + { + Id = new Guid("29000000-0000-0000-0000-000000000006"), + CreatedAt = new DateTime(2026, 8, 20, 0, 0, 0, 0, DateTimeKind.Unspecified), + FK_RoleId = new Guid("10000000-0000-0000-0000-000000000005"), + FullName = "Priya Rajapaksa", + IsActive = true, + PasswordHash = "AQAAAAIAAYagAAAAEKGnq/U4j6uM9g5mFkxbUE8WU/AhnSNiwrKCiPAqGnzNcaMgZAF5Zal/rJUUYPZPpg==", + Username = "priya.rajapaksa" + }, + new + { + Id = new Guid("70000000-0000-0000-0000-000000000001"), + CreatedAt = new DateTime(2026, 8, 20, 0, 0, 0, 0, DateTimeKind.Unspecified), + FK_RoleId = new Guid("10000000-0000-0000-0000-000000000006"), + FullName = "Channa De Silva", + IsActive = true, + PasswordHash = "AQAAAAIAAYagAAAAEKGnq/U4j6uM9g5mFkxbUE8WU/AhnSNiwrKCiPAqGnzNcaMgZAF5Zal/rJUUYPZPpg==", + Username = "channa.desilva" + }); + }); + + modelBuilder.Entity("Distribution.Models.UserRegionScope", b => + { + b.Property("FK_UserId") + .HasColumnType("char(36)"); + + b.Property("FK_RegionId") + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.HasKey("FK_UserId", "FK_RegionId"); + + b.HasIndex("FK_RegionId"); + + b.ToTable("UserRegionScopes"); + + b.HasData( + new + { + FK_UserId = new Guid("29000000-0000-0000-0000-000000000006"), + FK_RegionId = new Guid("24000000-0000-0000-0000-000000000001"), + CreatedAt = new DateTime(2026, 8, 20, 0, 0, 0, 0, DateTimeKind.Unspecified), + Id = new Guid("68000000-0000-0000-0000-000000000001"), + IsActive = true + }); + }); + + modelBuilder.Entity("Distribution.Models.UserRouteScope", b => + { + b.Property("FK_UserId") + .HasColumnType("char(36)"); + + b.Property("FK_RouteId") + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.HasKey("FK_UserId", "FK_RouteId"); + + b.HasIndex("FK_RouteId"); + + b.ToTable("UserRouteScopes"); + + b.HasData( + new + { + FK_UserId = new Guid("29000000-0000-0000-0000-000000000001"), + FK_RouteId = new Guid("25000000-0000-0000-0000-000000000001"), + CreatedAt = new DateTime(2026, 8, 20, 0, 0, 0, 0, DateTimeKind.Unspecified), + Id = new Guid("68000000-0000-0000-0000-000000000002"), + IsActive = true + }); + }); + + modelBuilder.Entity("Distribution.Models.UserStoreScope", b => + { + b.Property("FK_UserId") + .HasColumnType("char(36)"); + + b.Property("FK_StoreId") + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.HasKey("FK_UserId", "FK_StoreId"); + + b.HasIndex("FK_StoreId"); + + b.ToTable("UserStoreScopes"); + + b.HasData( + new + { + FK_UserId = new Guid("29000000-0000-0000-0000-000000000003"), + FK_StoreId = new Guid("26000000-0000-0000-0000-000000000005"), + CreatedAt = new DateTime(2026, 8, 20, 0, 0, 0, 0, DateTimeKind.Unspecified), + Id = new Guid("68000000-0000-0000-0000-000000000003"), + IsActive = true + }); + }); + + modelBuilder.Entity("Distribution.Models.Vehicle", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("DefaultWarehouseId") + .HasColumnType("char(36)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("NumberPlate") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("RegNumber") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Type") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("DefaultWarehouseId"); + + b.ToTable("Vehicles"); + + b.HasData( + new + { + Id = new Guid("28000000-0000-0000-0000-000000000001"), + CreatedAt = new DateTime(2026, 8, 20, 0, 0, 0, 0, DateTimeKind.Unspecified), + DefaultWarehouseId = new Guid("27000000-0000-0000-0000-000000000001"), + IsActive = true, + NumberPlate = "CAB-1234", + RegNumber = "WP-CAB-1234", + Type = 2 + }, + new + { + Id = new Guid("28000000-0000-0000-0000-000000000002"), + CreatedAt = new DateTime(2026, 8, 20, 0, 0, 0, 0, DateTimeKind.Unspecified), + DefaultWarehouseId = new Guid("27000000-0000-0000-0000-000000000002"), + IsActive = true, + NumberPlate = "KY-5678", + RegNumber = "CP-KY-5678", + Type = 1 + }); + }); + + modelBuilder.Entity("Distribution.Models.Warehouse", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("Location") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Type") + .HasColumnType("int"); + + b.Property("VehicleId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("VehicleId"); + + b.ToTable("Warehouses"); + + b.HasData( + new + { + Id = new Guid("27000000-0000-0000-0000-000000000001"), + CreatedAt = new DateTime(2026, 8, 20, 0, 0, 0, 0, DateTimeKind.Unspecified), + IsActive = true, + Location = "Peliyagoda, Colombo", + Name = "Colombo Distribution Center", + Type = 0 + }, + new + { + Id = new Guid("27000000-0000-0000-0000-000000000002"), + CreatedAt = new DateTime(2026, 8, 20, 0, 0, 0, 0, DateTimeKind.Unspecified), + IsActive = true, + Location = "Katugastota, Kandy", + Name = "Kandy Fulfillment Center", + Type = 1 + }); + }); + + modelBuilder.Entity("Distribution.Models.Claim", b => + { + b.HasOne("Distribution.Models.User", "AgencyUser") + .WithMany() + .HasForeignKey("FK_AgencyUserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Distribution.Models.User", "SettledByUser") + .WithMany() + .HasForeignKey("SettledByUserId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("AgencyUser"); + + b.Navigation("SettledByUser"); + }); + + modelBuilder.Entity("Distribution.Models.CreditNote", b => + { + b.HasOne("Distribution.Models.SaleReturn", "SaleReturn") + .WithMany() + .HasForeignKey("FK_SaleReturnId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("SaleReturn"); + }); + + modelBuilder.Entity("Distribution.Models.DeliveryRun", b => + { + b.HasOne("Distribution.Models.DriverProfile", "DriverProfile") + .WithMany() + .HasForeignKey("FK_DriverProfileId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Distribution.Models.Vehicle", "Vehicle") + .WithMany() + .HasForeignKey("FK_VehicleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("DriverProfile"); + + b.Navigation("Vehicle"); + }); + + modelBuilder.Entity("Distribution.Models.DeliveryTask", b => + { + b.HasOne("Distribution.Models.DeliveryRun", "Ride") + .WithMany() + .HasForeignKey("FK_RideId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Distribution.Models.ShopPO", "ShopPO") + .WithMany() + .HasForeignKey("FK_ShopPOId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Ride"); + + b.Navigation("ShopPO"); + }); + + modelBuilder.Entity("Distribution.Models.DriverProfile", b => + { + b.HasOne("Distribution.Models.User", "User") + .WithMany() + .HasForeignKey("FK_UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Distribution.Models.GoodsReceipt", b => + { + b.HasOne("Distribution.Models.PurchaseOrder", "PurchaseOrder") + .WithMany() + .HasForeignKey("FK_PurchaseOrderId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("Distribution.Models.Supplier", "Supplier") + .WithMany() + .HasForeignKey("FK_SupplierId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("Distribution.Models.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("FK_WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("PurchaseOrder"); + + b.Navigation("Supplier"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("Distribution.Models.GoodsReceiptItem", b => + { + b.HasOne("Distribution.Models.GoodsReceipt", "GoodsReceipt") + .WithMany("Items") + .HasForeignKey("FK_GoodsReceiptId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Distribution.Models.Item", "Item") + .WithMany() + .HasForeignKey("FK_ItemId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("Distribution.Models.PurchaseOrderItem", "PurchaseOrderItem") + .WithMany() + .HasForeignKey("FK_PurchaseOrderItemId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("GoodsReceipt"); + + b.Navigation("Item"); + + b.Navigation("PurchaseOrderItem"); + }); + + modelBuilder.Entity("Distribution.Models.GoodsReceiptItemBatch", b => + { + b.HasOne("Distribution.Models.GoodsReceiptItem", "GoodsReceiptItem") + .WithMany("Batches") + .HasForeignKey("FK_GoodsReceiptItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Distribution.Models.ItemBatch", "ItemBatch") + .WithMany() + .HasForeignKey("FK_ItemBatchId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("GoodsReceiptItem"); + + b.Navigation("ItemBatch"); + }); + + modelBuilder.Entity("Distribution.Models.GoodsReceiptItemSerial", b => + { + b.HasOne("Distribution.Models.GoodsReceiptItem", "GoodsReceiptItem") + .WithMany("Serials") + .HasForeignKey("FK_GoodsReceiptItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Distribution.Models.ItemSerialNumber", "ItemSerialNumber") + .WithMany() + .HasForeignKey("FK_ItemSerialNumberId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("GoodsReceiptItem"); + + b.Navigation("ItemSerialNumber"); + }); + + modelBuilder.Entity("Distribution.Models.Invoice", b => + { + b.HasOne("Distribution.Models.ShopPO", "ShopPO") + .WithMany() + .HasForeignKey("FK_ShopPOId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("ShopPO"); + }); + + modelBuilder.Entity("Distribution.Models.Item", b => + { + b.HasOne("Distribution.Models.Uom", "BaseUom") + .WithMany() + .HasForeignKey("FK_BaseUomId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Distribution.Models.Brand", "Brand") + .WithMany() + .HasForeignKey("FK_BrandId"); + + b.HasOne("Distribution.Models.Category", "Category") + .WithMany() + .HasForeignKey("FK_CategoryId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Distribution.Models.SubCategory", "SubCategory") + .WithMany() + .HasForeignKey("FK_SubCategoryId"); + + b.Navigation("BaseUom"); + + b.Navigation("Brand"); + + b.Navigation("Category"); + + b.Navigation("SubCategory"); + }); + + modelBuilder.Entity("Distribution.Models.ItemBatch", b => + { + b.HasOne("Distribution.Models.Item", "Item") + .WithMany() + .HasForeignKey("FK_ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Item"); + }); + + modelBuilder.Entity("Distribution.Models.ItemSerialNumber", b => + { + b.HasOne("Distribution.Models.Item", "Item") + .WithMany() + .HasForeignKey("FK_ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Item"); + }); + + modelBuilder.Entity("Distribution.Models.LoadingSheet", b => + { + b.HasOne("Distribution.Models.DeliveryRun", "Ride") + .WithMany() + .HasForeignKey("FK_RideId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Ride"); + }); + + modelBuilder.Entity("Distribution.Models.LoadingSheetItem", b => + { + b.HasOne("Distribution.Models.Item", "Item") + .WithMany() + .HasForeignKey("FK_ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Distribution.Models.LoadingSheet", "LoadingSheet") + .WithMany("Items") + .HasForeignKey("FK_LoadingSheetId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Distribution.Models.ShopPOItem", "ShopPOItem") + .WithMany() + .HasForeignKey("FK_ShopPOItemId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Distribution.Models.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("FK_WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Item"); + + b.Navigation("LoadingSheet"); + + b.Navigation("ShopPOItem"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("Distribution.Models.LoadingSheetOrder", b => + { + b.HasOne("Distribution.Models.LoadingSheet", "LoadingSheet") + .WithMany("Orders") + .HasForeignKey("FK_LoadingSheetId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Distribution.Models.ShopPO", "ShopPO") + .WithMany() + .HasForeignKey("FK_ShopPOId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("LoadingSheet"); + + b.Navigation("ShopPO"); + }); + + modelBuilder.Entity("Distribution.Models.Payment", b => + { + b.HasOne("Distribution.Models.Invoice", "Invoice") + .WithMany("Payments") + .HasForeignKey("FK_InvoiceId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Invoice"); + }); + + modelBuilder.Entity("Distribution.Models.PriceList", b => + { + b.HasOne("Distribution.Models.Region", "Region") + .WithMany() + .HasForeignKey("FK_RegionId"); + + b.Navigation("Region"); + }); + + modelBuilder.Entity("Distribution.Models.PriceListItem", b => + { + b.HasOne("Distribution.Models.Item", "Item") + .WithMany() + .HasForeignKey("FK_ItemId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Distribution.Models.PriceList", "PriceList") + .WithMany("Items") + .HasForeignKey("FK_PriceListId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Item"); + + b.Navigation("PriceList"); + }); + + modelBuilder.Entity("Distribution.Models.PurchaseOrder", b => + { + b.HasOne("Distribution.Models.Supplier", "Supplier") + .WithMany() + .HasForeignKey("FK_SupplierId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Supplier"); + }); + + modelBuilder.Entity("Distribution.Models.PurchaseOrderItem", b => + { + b.HasOne("Distribution.Models.Item", "Item") + .WithMany() + .HasForeignKey("FK_ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Distribution.Models.PurchaseOrder", "PurchaseOrder") + .WithMany("Items") + .HasForeignKey("FK_PurchaseOrderId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Item"); + + b.Navigation("PurchaseOrder"); + }); + + modelBuilder.Entity("Distribution.Models.PurchaseReturn", b => + { + b.HasOne("Distribution.Models.PurchaseOrder", "PurchaseOrder") + .WithMany() + .HasForeignKey("FK_PurchaseOrderId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("PurchaseOrder"); + }); + + modelBuilder.Entity("Distribution.Models.PurchaseReturnItem", b => + { + b.HasOne("Distribution.Models.Item", "Item") + .WithMany() + .HasForeignKey("FK_ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Distribution.Models.PurchaseReturn", "PurchaseReturn") + .WithMany("Items") + .HasForeignKey("FK_PurchaseReturnId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Item"); + + b.Navigation("PurchaseReturn"); + }); + + modelBuilder.Entity("Distribution.Models.RegionAgencyMap", b => + { + b.HasOne("Distribution.Models.User", "AgencyUser") + .WithMany() + .HasForeignKey("FK_AgencyUserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Distribution.Models.Region", "Region") + .WithMany() + .HasForeignKey("FK_RegionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AgencyUser"); + + b.Navigation("Region"); + }); + + modelBuilder.Entity("Distribution.Models.RegionalBudget", b => + { + b.HasOne("Distribution.Models.Region", "Region") + .WithMany() + .HasForeignKey("FK_RegionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Region"); + }); + + modelBuilder.Entity("Distribution.Models.RegionalTarget", b => + { + b.HasOne("Distribution.Models.Region", "Region") + .WithMany() + .HasForeignKey("FK_RegionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Region"); + }); + + modelBuilder.Entity("Distribution.Models.RegionalTaxRule", b => + { + b.HasOne("Distribution.Models.Region", "Region") + .WithMany() + .HasForeignKey("FK_RegionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Region"); + }); + + modelBuilder.Entity("Distribution.Models.RolePermission", b => + { + b.HasOne("Distribution.Models.Permission", "Permission") + .WithMany() + .HasForeignKey("FK_PermissionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Distribution.Models.Role", "Role") + .WithMany() + .HasForeignKey("FK_RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Permission"); + + b.Navigation("Role"); + }); + + modelBuilder.Entity("Distribution.Models.Route", b => + { + b.HasOne("Distribution.Models.User", "OwnerUser") + .WithMany() + .HasForeignKey("FK_OwnerUserId"); + + b.HasOne("Distribution.Models.Region", "Region") + .WithMany() + .HasForeignKey("FK_RegionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("OwnerUser"); + + b.Navigation("Region"); + }); + + modelBuilder.Entity("Distribution.Models.RouteVisitLog", b => + { + b.HasOne("Distribution.Models.Route", "Route") + .WithMany() + .HasForeignKey("FK_RouteId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Distribution.Models.Store", "Store") + .WithMany() + .HasForeignKey("FK_StoreId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Distribution.Models.User", "User") + .WithMany() + .HasForeignKey("FK_UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Route"); + + b.Navigation("Store"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Distribution.Models.SaleReturn", b => + { + b.HasOne("Distribution.Models.User", "CreatedByUser") + .WithMany() + .HasForeignKey("CreatedByUserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Distribution.Models.ShopPO", "ShopPO") + .WithMany() + .HasForeignKey("FK_ShopPOId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("CreatedByUser"); + + b.Navigation("ShopPO"); + }); + + modelBuilder.Entity("Distribution.Models.SaleReturnItem", b => + { + b.HasOne("Distribution.Models.Item", "Item") + .WithMany() + .HasForeignKey("FK_ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Distribution.Models.SaleReturn", "SaleReturn") + .WithMany("Items") + .HasForeignKey("FK_SaleReturnId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Item"); + + b.Navigation("SaleReturn"); + }); + + modelBuilder.Entity("Distribution.Models.Scheme", b => + { + b.HasOne("Distribution.Models.Item", "FreeItem") + .WithMany() + .HasForeignKey("FK_FreeItemId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("Distribution.Models.Item", "TriggerItem") + .WithMany() + .HasForeignKey("FK_TriggerItemId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("Distribution.Models.Region", "Region") + .WithMany() + .HasForeignKey("RegionId"); + + b.Navigation("FreeItem"); + + b.Navigation("Region"); + + b.Navigation("TriggerItem"); + }); + + modelBuilder.Entity("Distribution.Models.SchemeBundleComponent", b => + { + b.HasOne("Distribution.Models.Item", "Item") + .WithMany() + .HasForeignKey("FK_ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Distribution.Models.Scheme", "Scheme") + .WithMany("BundleComponents") + .HasForeignKey("FK_SchemeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Item"); + + b.Navigation("Scheme"); + }); + + modelBuilder.Entity("Distribution.Models.SchemeRedemption", b => + { + b.HasOne("Distribution.Models.Item", "FreeItem") + .WithMany() + .HasForeignKey("FK_FreeItemId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("Distribution.Models.Scheme", "Scheme") + .WithMany("Redemptions") + .HasForeignKey("FK_SchemeId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Distribution.Models.ShopPO", "ShopPO") + .WithMany() + .HasForeignKey("FK_ShopPOId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("FreeItem"); + + b.Navigation("Scheme"); + + b.Navigation("ShopPO"); + }); + + modelBuilder.Entity("Distribution.Models.ShopPO", b => + { + b.HasOne("Distribution.Models.Store", "Store") + .WithMany() + .HasForeignKey("FK_StoreId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Store"); + }); + + modelBuilder.Entity("Distribution.Models.ShopPOItem", b => + { + b.HasOne("Distribution.Models.Item", "Item") + .WithMany() + .HasForeignKey("FK_ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Distribution.Models.ShopPO", "ShopPO") + .WithMany("Items") + .HasForeignKey("FK_ShopPOId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Item"); + + b.Navigation("ShopPO"); + }); + + modelBuilder.Entity("Distribution.Models.StockBalance", b => + { + b.HasOne("Distribution.Models.ItemBatch", "ItemBatch") + .WithMany() + .HasForeignKey("FK_ItemBatchId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("Distribution.Models.Item", "Item") + .WithMany() + .HasForeignKey("FK_ItemId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Distribution.Models.ItemSerialNumber", "ItemSerialNumber") + .WithMany() + .HasForeignKey("FK_ItemSerialNumberId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Item"); + + b.Navigation("ItemBatch"); + + b.Navigation("ItemSerialNumber"); + }); + + modelBuilder.Entity("Distribution.Models.StockMovement", b => + { + b.HasOne("Distribution.Models.ItemBatch", "ItemBatch") + .WithMany() + .HasForeignKey("FK_ItemBatchId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("Distribution.Models.Item", "Item") + .WithMany() + .HasForeignKey("FK_ItemId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Distribution.Models.ItemSerialNumber", "ItemSerialNumber") + .WithMany() + .HasForeignKey("FK_ItemSerialNumberId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Item"); + + b.Navigation("ItemBatch"); + + b.Navigation("ItemSerialNumber"); + }); + + modelBuilder.Entity("Distribution.Models.Store", b => + { + b.HasOne("Distribution.Models.Route", "Route") + .WithMany() + .HasForeignKey("FK_RouteId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Route"); + }); + + modelBuilder.Entity("Distribution.Models.SubCategory", b => + { + b.HasOne("Distribution.Models.Category", "Category") + .WithMany() + .HasForeignKey("FK_CategoryId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Category"); + }); + + modelBuilder.Entity("Distribution.Models.SupplierClaim", b => + { + b.HasOne("Distribution.Models.GoodsReceipt", "GoodsReceipt") + .WithMany() + .HasForeignKey("FK_GoodsReceiptId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("Distribution.Models.PurchaseOrder", "PurchaseOrder") + .WithMany() + .HasForeignKey("FK_PurchaseOrderId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Distribution.Models.User", "RaisedByUser") + .WithMany() + .HasForeignKey("RaisedByUserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Distribution.Models.User", "ResolvedByUser") + .WithMany() + .HasForeignKey("ResolvedByUserId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("GoodsReceipt"); + + b.Navigation("PurchaseOrder"); + + b.Navigation("RaisedByUser"); + + b.Navigation("ResolvedByUser"); + }); + + modelBuilder.Entity("Distribution.Models.SupplierClaimItem", b => + { + b.HasOne("Distribution.Models.Item", "Item") + .WithMany() + .HasForeignKey("FK_ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Distribution.Models.SupplierClaim", "SupplierClaim") + .WithMany("Items") + .HasForeignKey("FK_SupplierClaimId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Item"); + + b.Navigation("SupplierClaim"); + }); + + modelBuilder.Entity("Distribution.Models.UomConversion", b => + { + b.HasOne("Distribution.Models.Uom", "FromUom") + .WithMany() + .HasForeignKey("FK_FromUomId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Distribution.Models.Item", "Item") + .WithMany("Conversions") + .HasForeignKey("FK_ItemId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Distribution.Models.Uom", "ToUom") + .WithMany() + .HasForeignKey("FK_ToUomId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("FromUom"); + + b.Navigation("Item"); + + b.Navigation("ToUom"); + }); + + modelBuilder.Entity("Distribution.Models.User", b => + { + b.HasOne("Distribution.Models.User", "ReportsTo") + .WithMany() + .HasForeignKey("FK_ReportsToUserId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("Distribution.Models.Role", "Role") + .WithMany() + .HasForeignKey("FK_RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ReportsTo"); + + b.Navigation("Role"); + }); + + modelBuilder.Entity("Distribution.Models.UserRegionScope", b => + { + b.HasOne("Distribution.Models.Region", "Region") + .WithMany() + .HasForeignKey("FK_RegionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Distribution.Models.User", "User") + .WithMany() + .HasForeignKey("FK_UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Region"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Distribution.Models.UserRouteScope", b => + { + b.HasOne("Distribution.Models.Route", "Route") + .WithMany() + .HasForeignKey("FK_RouteId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Distribution.Models.User", "User") + .WithMany() + .HasForeignKey("FK_UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Route"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Distribution.Models.UserStoreScope", b => + { + b.HasOne("Distribution.Models.Store", "Store") + .WithMany() + .HasForeignKey("FK_StoreId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Distribution.Models.User", "User") + .WithMany() + .HasForeignKey("FK_UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Store"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Distribution.Models.Vehicle", b => + { + b.HasOne("Distribution.Models.Warehouse", null) + .WithMany() + .HasForeignKey("DefaultWarehouseId") + .OnDelete(DeleteBehavior.Restrict); + }); + + modelBuilder.Entity("Distribution.Models.Warehouse", b => + { + b.HasOne("Distribution.Models.Vehicle", "Vehicle") + .WithMany() + .HasForeignKey("VehicleId"); + + b.Navigation("Vehicle"); + }); + + modelBuilder.Entity("Distribution.Models.GoodsReceipt", b => + { + b.Navigation("Items"); + }); + + modelBuilder.Entity("Distribution.Models.GoodsReceiptItem", b => + { + b.Navigation("Batches"); + + b.Navigation("Serials"); + }); + + modelBuilder.Entity("Distribution.Models.Invoice", b => + { + b.Navigation("Payments"); + }); + + modelBuilder.Entity("Distribution.Models.Item", b => + { + b.Navigation("Conversions"); + }); + + modelBuilder.Entity("Distribution.Models.LoadingSheet", b => + { + b.Navigation("Items"); + + b.Navigation("Orders"); + }); + + modelBuilder.Entity("Distribution.Models.PriceList", b => + { + b.Navigation("Items"); + }); + + modelBuilder.Entity("Distribution.Models.PurchaseOrder", b => + { + b.Navigation("Items"); + }); + + modelBuilder.Entity("Distribution.Models.PurchaseReturn", b => + { + b.Navigation("Items"); + }); + + modelBuilder.Entity("Distribution.Models.SaleReturn", b => + { + b.Navigation("Items"); + }); + + modelBuilder.Entity("Distribution.Models.Scheme", b => + { + b.Navigation("BundleComponents"); + + b.Navigation("Redemptions"); + }); + + modelBuilder.Entity("Distribution.Models.ShopPO", b => + { + b.Navigation("Items"); + }); + + modelBuilder.Entity("Distribution.Models.SupplierClaim", b => + { + b.Navigation("Items"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Distribution/Migrations/20261007065602_AddGrnFreeQuantity.cs b/Distribution/Migrations/20261007065602_AddGrnFreeQuantity.cs new file mode 100644 index 0000000..72f6684 --- /dev/null +++ b/Distribution/Migrations/20261007065602_AddGrnFreeQuantity.cs @@ -0,0 +1,44 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Distribution.Migrations +{ + /// + public partial class AddGrnFreeQuantity : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "FreeQuantity", + table: "GoodsReceiptItems", + type: "int", + nullable: false, + defaultValue: 0); + + migrationBuilder.UpdateData( + table: "GoodsReceiptItems", + keyColumn: "Id", + keyValue: new Guid("44000000-0000-0000-0000-000000000001"), + column: "FreeQuantity", + value: 0); + + migrationBuilder.UpdateData( + table: "GoodsReceiptItems", + keyColumn: "Id", + keyValue: new Guid("44000000-0000-0000-0000-000000000002"), + column: "FreeQuantity", + value: 0); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "FreeQuantity", + table: "GoodsReceiptItems"); + } + } +} diff --git a/Distribution/Migrations/AppDBContextModelSnapshot.cs b/Distribution/Migrations/AppDBContextModelSnapshot.cs index 33c22c4..bd3c2ef 100644 --- a/Distribution/Migrations/AppDBContextModelSnapshot.cs +++ b/Distribution/Migrations/AppDBContextModelSnapshot.cs @@ -427,6 +427,10 @@ namespace Distribution.Migrations b.Property("CreatedAt") .HasColumnType("datetime(6)"); + b.Property("DiscountPercent") + .HasPrecision(5, 2) + .HasColumnType("decimal(5,2)"); + b.Property("FK_PurchaseOrderId") .HasColumnType("char(36)"); @@ -445,6 +449,10 @@ namespace Distribution.Migrations b.Property("ReceivedAt") .HasColumnType("datetime(6)"); + b.Property("VatPercent") + .HasPrecision(5, 2) + .HasColumnType("decimal(5,2)"); + b.HasKey("Id"); b.HasIndex("FK_PurchaseOrderId"); @@ -460,11 +468,13 @@ namespace Distribution.Migrations { Id = new Guid("43000000-0000-0000-0000-000000000001"), CreatedAt = new DateTime(2026, 8, 15, 0, 0, 0, 0, DateTimeKind.Unspecified), + DiscountPercent = 0m, FK_PurchaseOrderId = new Guid("41000000-0000-0000-0000-000000000001"), FK_ReceivedByUserId = new Guid("29000000-0000-0000-0000-000000000001"), FK_WarehouseId = new Guid("27000000-0000-0000-0000-000000000001"), IsActive = true, - ReceivedAt = new DateTime(2026, 8, 15, 0, 0, 0, 0, DateTimeKind.Unspecified) + ReceivedAt = new DateTime(2026, 8, 15, 0, 0, 0, 0, DateTimeKind.Unspecified), + VatPercent = 0m }); }); @@ -480,6 +490,10 @@ namespace Distribution.Migrations b.Property("CreatedAt") .HasColumnType("datetime(6)"); + b.Property("DiscountPercent") + .HasPrecision(5, 2) + .HasColumnType("decimal(5,2)"); + b.Property("FK_GoodsReceiptId") .HasColumnType("char(36)"); @@ -489,12 +503,23 @@ namespace Distribution.Migrations b.Property("FK_PurchaseOrderItemId") .HasColumnType("char(36)"); + b.Property("FreeQuantity") + .HasColumnType("int"); + b.Property("IsActive") .HasColumnType("tinyint(1)"); b.Property("RejectedQuantity") .HasColumnType("int"); + b.Property("UnitCost") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("VatPercent") + .HasPrecision(5, 2) + .HasColumnType("decimal(5,2)"); + b.HasKey("Id"); b.HasIndex("FK_GoodsReceiptId"); @@ -511,20 +536,28 @@ namespace Distribution.Migrations Id = new Guid("44000000-0000-0000-0000-000000000001"), AcceptedQuantity = 200, CreatedAt = new DateTime(2026, 8, 15, 0, 0, 0, 0, DateTimeKind.Unspecified), + DiscountPercent = 0m, FK_GoodsReceiptId = new Guid("43000000-0000-0000-0000-000000000001"), FK_PurchaseOrderItemId = new Guid("42000000-0000-0000-0000-000000000001"), + FreeQuantity = 0, IsActive = true, - RejectedQuantity = 0 + RejectedQuantity = 0, + UnitCost = 0m, + VatPercent = 0m }, new { Id = new Guid("44000000-0000-0000-0000-000000000002"), AcceptedQuantity = 98, CreatedAt = new DateTime(2026, 8, 15, 0, 0, 0, 0, DateTimeKind.Unspecified), + DiscountPercent = 0m, FK_GoodsReceiptId = new Guid("43000000-0000-0000-0000-000000000001"), FK_PurchaseOrderItemId = new Guid("42000000-0000-0000-0000-000000000002"), + FreeQuantity = 0, IsActive = true, - RejectedQuantity = 2 + RejectedQuantity = 2, + UnitCost = 0m, + VatPercent = 0m }); }); @@ -1500,17 +1533,17 @@ namespace Distribution.Migrations new { Id = new Guid("24000000-0000-0000-0000-000000000001"), + Code = "REG-0001", CreatedAt = new DateTime(2026, 8, 20, 0, 0, 0, 0, DateTimeKind.Unspecified), IsActive = true, - Code = "REG-0001", Name = "Western Province" }, new { Id = new Guid("24000000-0000-0000-0000-000000000002"), + Code = "REG-0002", CreatedAt = new DateTime(2026, 8, 20, 0, 0, 0, 0, DateTimeKind.Unspecified), IsActive = true, - Code = "REG-0002", Name = "Central Province" }); }); diff --git a/Distribution/Models/GoodsReceipt.cs b/Distribution/Models/GoodsReceipt.cs index cee1549..025103b 100644 --- a/Distribution/Models/GoodsReceipt.cs +++ b/Distribution/Models/GoodsReceipt.cs @@ -1,4 +1,5 @@ using System.ComponentModel.DataAnnotations.Schema; +using Microsoft.EntityFrameworkCore; namespace Distribution.Models { @@ -27,6 +28,14 @@ namespace Distribution.Models public DateTime ReceivedAt { get; set; } = DateTime.Now; + /// GRN-wide default discount %, applied to any line that doesn't set its own. + [Precision(5, 2)] + public decimal DiscountPercent { get; set; } + + /// GRN-wide default VAT %, applied to any line that doesn't set its own. + [Precision(5, 2)] + public decimal VatPercent { get; set; } + public ICollection Items { get; set; } = []; } } diff --git a/Distribution/Models/GoodsReceiptItem.cs b/Distribution/Models/GoodsReceiptItem.cs index b905f69..734e8f7 100644 --- a/Distribution/Models/GoodsReceiptItem.cs +++ b/Distribution/Models/GoodsReceiptItem.cs @@ -1,4 +1,5 @@ using System.ComponentModel.DataAnnotations.Schema; +using Microsoft.EntityFrameworkCore; namespace Distribution.Models { @@ -24,6 +25,22 @@ namespace Distribution.Models /// Damaged/short at receipt, not added to stock. public int RejectedQuantity { get; set; } = 0; + /// + /// Units the supplier gave at no charge. Received into stock like accepted units, but never + /// costed and never counted against the purchase order's ordered quantity. + /// + public int FreeQuantity { get; set; } = 0; + + /// Cost per base-UOM unit. Only accepted units are costed. + [Precision(18, 2)] + public decimal UnitCost { get; set; } + + [Precision(5, 2)] + public decimal DiscountPercent { get; set; } + + [Precision(5, 2)] + public decimal VatPercent { get; set; } + /// Populated when the received item tracks batches and/or expiry dates. public ICollection Batches { get; set; } = []; From 40e890d4ff7d64ead5e8227b39d381f0869908f1 Mon Sep 17 00:00:00 2001 From: DeepnaPooja Date: Thu, 8 Oct 2026 10:40:43 +0530 Subject: [PATCH 2/9] Fix GRN UI --- .../grn/_components/grn-client.tsx | 14 +- .../grn/_components/grn-costing.tsx | 186 +++++ .../grn/_components/grn-tracking.tsx | 2 +- .../grn/_components/grn-view-sheet.tsx | 21 +- .../procurement/grn/direct/new/page.tsx | 316 --------- .../(dashboard)/procurement/grn/new/page.tsx | 659 ++++++++++++++++++ .../procurement/grn/po/new/page.tsx | 383 ---------- distribution_fe/lib/api/procurement.ts | 12 + distribution_fe/types/api.ts | 19 + 9 files changed, 901 insertions(+), 711 deletions(-) create mode 100644 distribution_fe/app/(dashboard)/procurement/grn/_components/grn-costing.tsx delete mode 100644 distribution_fe/app/(dashboard)/procurement/grn/direct/new/page.tsx create mode 100644 distribution_fe/app/(dashboard)/procurement/grn/new/page.tsx delete mode 100644 distribution_fe/app/(dashboard)/procurement/grn/po/new/page.tsx diff --git a/distribution_fe/app/(dashboard)/procurement/grn/_components/grn-client.tsx b/distribution_fe/app/(dashboard)/procurement/grn/_components/grn-client.tsx index ca0259d..8789dde 100644 --- a/distribution_fe/app/(dashboard)/procurement/grn/_components/grn-client.tsx +++ b/distribution_fe/app/(dashboard)/procurement/grn/_components/grn-client.tsx @@ -124,16 +124,10 @@ export function GrnClient() { Stock received into a warehouse, against a purchase order or directly

-
- - -
+ Math.round(Number((n * 100).toPrecision(12))) / 100 + +export interface CostedLine { + acceptedQuantity: number + unitCost: number + discountPercent: number + vatPercent: number +} + +/** gross = qty x cost; discount on gross; VAT on (gross - discount); total = gross - discount + VAT. */ +export function lineAmounts(l: CostedLine) { + const gross = round2((l.acceptedQuantity || 0) * (l.unitCost || 0)) + const discount = round2((gross * (l.discountPercent || 0)) / 100) + const vat = round2(((gross - discount) * (l.vatPercent || 0)) / 100) + return { gross, discount, vat, total: gross - discount + vat } +} + +export function grnTotals(lines: CostedLine[]) { + const amounts = lines.map(lineAmounts) + const subTotal = amounts.reduce((s, a) => s + a.gross, 0) + const discount = amounts.reduce((s, a) => s + a.discount, 0) + const vat = amounts.reduce((s, a) => s + a.vat, 0) + return { subTotal, discount, taxable: subTotal - discount, vat, total: subTotal - discount + vat } +} + +export const money = (n: number) => + n.toLocaleString('en-IN', { minimumFractionDigits: 2, maximumFractionDigits: 2 }) + +/** Text for the Batch No column: lot numbers for batch items, a count for serial/expiry items. */ +export function batchSummary( + info: ItemSummaryResponse | undefined, + batches: BatchRow[], + serialText: string, +): string { + if (!info) return '—' + if (info.tracksSerialNumbers) { + const n = serialText.split(/[\n,]+/).map(s => s.trim()).filter(Boolean).length + return n === 0 ? 'Serials not entered' : `${n} serial${n === 1 ? '' : 's'}` + } + if (info.tracksBatches) { + const named = batches.filter(b => b.batchNumber.trim()) + return named.length === 0 ? 'Not entered' : named.map(b => `${b.batchNumber.trim()} ×${b.quantity}`).join(', ') + } + if (info.tracksExpiryDate) { + return batches.length === 0 ? 'Not entered' : `${batches.length} expiry lot${batches.length === 1 ? '' : 's'}` + } + return '—' +} + +interface PercentSelectProps { + label: string + value: number + options: number[] + onChange: (value: number) => void +} + +/** + * GRN-wide percentage: pick a preset from the list or type any value from 0 to 100. The text is kept + * locally while typing so "2." or "" don't get rewritten under the cursor; the number is only pushed + * up when it is valid. + */ +export function PercentSelect({ label, value, options, onChange }: PercentSelectProps) { + const listId = useId() + const [text, setText] = useState(String(value)) + + // Follow outside changes (e.g. a reset) without fighting what the user is typing. + useEffect(() => { + setText(prev => (Number(prev) === value && prev !== '' ? prev : String(value))) + }, [value]) + + function handleChange(raw: string) { + setText(raw) + const n = Number(raw) + if (raw.trim() !== '' && Number.isFinite(n) && n >= 0 && n <= 100) onChange(n) + } + + // An empty or out-of-range entry falls back to the last valid value on blur. + function handleBlur() { + const n = Number(text) + if (text.trim() === '' || !Number.isFinite(n) || n < 0 || n > 100) setText(String(value)) + } + + return ( + + {label} +
+ handleChange(e.target.value)} + onBlur={handleBlur} + className="pr-8" + /> + + % + + + {options.map(o => ( + +
+
+ ) +} + +interface NumberCellProps { + control: Control + name: FieldPath + step?: number + max?: number +} + +/** Numeric table-cell input bound to a form field. */ +export function NumberCell({ control, name, step = 1, max }: NumberCellProps) { + return ( + ( + field.onChange(Number(e.target.value))} + onBlur={field.onBlur} + aria-invalid={invalid} + /> + )} + /> + ) +} + +/** Totals block under the items table. */ +export function GrnTotals({ lines }: { lines: CostedLine[] }) { + const t = grnTotals(lines) + const rows: [string, string][] = [ + ['Subtotal', money(t.subTotal)], + ['Discount', `- ${money(t.discount)}`], + ['Taxable amount', money(t.taxable)], + ['VAT', money(t.vat)], + ] + return ( +
+ + + {rows.map(([label, value]) => ( +
+ {label} + {value} +
+ ))} +
+ Grand total + {money(t.total)} +
+
+
+
+ ) +} diff --git a/distribution_fe/app/(dashboard)/procurement/grn/_components/grn-tracking.tsx b/distribution_fe/app/(dashboard)/procurement/grn/_components/grn-tracking.tsx index 2d31ab6..0922e12 100644 --- a/distribution_fe/app/(dashboard)/procurement/grn/_components/grn-tracking.tsx +++ b/distribution_fe/app/(dashboard)/procurement/grn/_components/grn-tracking.tsx @@ -145,7 +145,7 @@ export function LineTracking({ info, name, accepted, serialText, batches, onSeri {isSerial ? 'Serial numbers' : info.tracksBatches ? 'Batches' : 'Expiry lots'} - {name} ({info.sku}) — accepting {accepted} + {name} ({info.sku}) — receiving {accepted} diff --git a/distribution_fe/app/(dashboard)/procurement/grn/_components/grn-view-sheet.tsx b/distribution_fe/app/(dashboard)/procurement/grn/_components/grn-view-sheet.tsx index 894eeaf..b32de5f 100644 --- a/distribution_fe/app/(dashboard)/procurement/grn/_components/grn-view-sheet.tsx +++ b/distribution_fe/app/(dashboard)/procurement/grn/_components/grn-view-sheet.tsx @@ -8,6 +8,7 @@ import { } from '@/components/ui/dialog' import { Separator } from '@/components/ui/separator' import type { GrnResponse } from '@/types/api' +import { money } from './grn-costing' const fmtDateTime = (v: string) => new Date(v).toLocaleString('en-GB', { @@ -37,7 +38,7 @@ export function GrnViewSheet({ open, onClose, grn }: GrnViewSheetProps) { return ( !v && onClose()}> - + Goods Receipt Details @@ -63,6 +64,11 @@ export function GrnViewSheet({ open, onClose, grn }: GrnViewSheetProps) { Item Accepted Rejected + Free + Unit Cost + Disc % + VAT % + Line Total @@ -94,11 +100,24 @@ export function GrnViewSheet({ open, onClose, grn }: GrnViewSheetProps) { {item.acceptedQuantity} {item.rejectedQuantity} + {item.freeQuantity} + {money(item.unitCost)} + {item.discountPercent}% + {item.vatPercent}% + {money(item.lineTotal)} ))} + +
+ + + + + {money(grn.totalAmount)}} /> +
diff --git a/distribution_fe/app/(dashboard)/procurement/grn/direct/new/page.tsx b/distribution_fe/app/(dashboard)/procurement/grn/direct/new/page.tsx deleted file mode 100644 index 5cfa515..0000000 --- a/distribution_fe/app/(dashboard)/procurement/grn/direct/new/page.tsx +++ /dev/null @@ -1,316 +0,0 @@ -'use client' - -import { useEffect, useState } from 'react' -import { useRouter } from 'next/navigation' -import { HugeiconsIcon } from '@hugeicons/react' -import { ArrowLeft01Icon } from '@hugeicons/core-free-icons' -import { useFieldArray } from 'react-hook-form' -import { useForm, zodResolver, z } from '@/lib/form' -import { FormField } from '@/components/shared/form-field' -import { Input } from '@/components/ui/input' -import { - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue, -} from '@/components/ui/select' -import { Button } from '@/components/ui/button' -import { FieldGroup } from '@/components/ui/field' -import { Separator } from '@/components/ui/separator' -import { toast } from '@/components/ui/toast' -import type { GrnItemBody } from '@/lib/api/procurement' -import { - grn as grnApi, - warehouses as warehousesApi, - users as usersApi, - suppliers as suppliersApi, - items as itemsApi, - getApiError, - getPaginatedItems, -} from '@/lib/api/index' -import type { WarehouseResponse, UserResponse, SupplierResponse, ItemSummaryResponse } from '@/types/api' -import { LineTracking, trackingPayload, trackingProgress } from '../../_components/grn-tracking' - -const NO_SUPPLIER = 'none' - -const lineSchema = z.object({ - itemId: z.string().min(1, 'Select an item'), - acceptedQuantity: z.number().min(0, 'Must be 0 or more'), - rejectedQuantity: z.number().min(0, 'Must be 0 or more'), - serialText: z.string(), - batches: z.array(z.object({ batchNumber: z.string(), expiryDate: z.string(), quantity: z.number() })), -}) - -const schema = z.object({ - warehouseId: z.string().min(1, 'Warehouse is required'), - supplierId: z.string(), - receivedByUserId: z.string().min(1, 'Received by is required'), - items: z.array(lineSchema).min(1, 'Add at least one item'), -}) - -type FormValues = z.infer - -const emptyLine = { itemId: '', acceptedQuantity: 1, rejectedQuantity: 0, serialText: '', batches: [] } - -/** Receive goods into a warehouse with no purchase order. */ -export default function NewDirectGrnPage() { - const router = useRouter() - const goBack = () => router.push('/procurement/grn') - const [warehousesList, setWarehousesList] = useState([]) - const [usersList, setUsersList] = useState([]) - const [suppliersList, setSuppliersList] = useState([]) - const [itemsList, setItemsList] = useState([]) - - const form = useForm({ - resolver: zodResolver(schema), - defaultValues: { warehouseId: '', supplierId: NO_SUPPLIER, receivedByUserId: '', items: [{ ...emptyLine }] }, - }) - - const { fields, append, remove } = useFieldArray({ control: form.control, name: 'items' }) - - useEffect(() => { - Promise.all([ - warehousesApi.list({ pageSize: 200 }), - usersApi.list({ pageSize: 200 }), - suppliersApi.list(), - itemsApi.list({ pageSize: 200 }), - ]) - .then(([ws, us, ss, its]) => { - setWarehousesList(getPaginatedItems(ws)) - setUsersList(getPaginatedItems(us)) - setSuppliersList(ss) - setItemsList(getPaginatedItems(its)) - }) - .catch(err => - toast.add({ title: 'Failed to load reference data', description: getApiError(err), type: 'error' }) - ) - }, []) - - const itemById = (id: string) => itemsList.find(i => i.id === id) - - const watchedItems = form.watch('items') - const trackingIncomplete = watchedItems.some(v => { - const p = trackingProgress(itemById(v.itemId), v.acceptedQuantity, v.serialText, v.batches) - return p !== null && !p.complete - }) - - async function onSubmit(values: FormValues) { - const items: GrnItemBody[] = [] - for (const v of values.items) { - const info = itemById(v.itemId) - if (v.acceptedQuantity + v.rejectedQuantity <= 0) { - toast.add({ title: `${info?.sku ?? 'Line'}: enter a quantity.`, type: 'error' }) - return - } - const payload = trackingPayload(info, v) - if ('error' in payload) { - toast.add({ title: `${info?.sku ?? 'Line'}: ${payload.error}`, type: 'error' }) - return - } - items.push({ - itemId: v.itemId, - acceptedQuantity: v.acceptedQuantity, - rejectedQuantity: v.rejectedQuantity, - ...payload, - }) - } - - try { - await grnApi.createDirect({ - warehouseId: values.warehouseId, - supplierId: values.supplierId === NO_SUPPLIER ? undefined : values.supplierId, - receivedByUserId: values.receivedByUserId, - items, - }) - toast.add({ title: 'Direct receipt recorded', type: 'success' }) - router.push('/procurement/grn') - } catch (err) { - toast.add({ title: 'Failed to record receipt', description: getApiError(err), type: 'error' }) - } - } - - return ( -
-
- -
-

Direct Receipt

-

Receive stock into a warehouse without a purchase order.

-
-
- -
- -
- ( - - )} - /> - - ( - - )} - /> -
- - ( - - )} - /> -
- - - -
-

Items

- - {fields.map((f, index) => { - const v = watchedItems[index] - const info = v ? itemById(v.itemId) : undefined - return ( -
- ( - - )} - /> - - ( - field.onChange(Number(e.target.value))} - onBlur={field.onBlur} - aria-invalid={invalid} - /> - )} - /> - - ( - field.onChange(Number(e.target.value))} - onBlur={field.onBlur} - aria-invalid={invalid} - /> - )} - /> - - - - {info && v && ( - form.setValue(`items.${index}.serialText`, val)} - onBatches={val => form.setValue(`items.${index}.batches`, val, { shouldDirty: true })} - /> - )} -
- ) - })} - -
- -
-
- -
- - -
- -
- ) -} diff --git a/distribution_fe/app/(dashboard)/procurement/grn/new/page.tsx b/distribution_fe/app/(dashboard)/procurement/grn/new/page.tsx new file mode 100644 index 0000000..0ee05bd --- /dev/null +++ b/distribution_fe/app/(dashboard)/procurement/grn/new/page.tsx @@ -0,0 +1,659 @@ +'use client' + +import { Fragment, useEffect, useState } from 'react' +import { useRouter } from 'next/navigation' +import { HugeiconsIcon } from '@hugeicons/react' +import { ArrowLeft01Icon, Add01Icon, Delete02Icon } from '@hugeicons/core-free-icons' +import { useFieldArray } from 'react-hook-form' +import { useForm, zodResolver, z } from '@/lib/form' +import { FormField } from '@/components/shared/form-field' +import { Input } from '@/components/ui/input' +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from '@/components/ui/select' +import { Button } from '@/components/ui/button' +import { Field, FieldGroup, FieldLabel } from '@/components/ui/field' +import { Separator } from '@/components/ui/separator' +import { toast } from '@/components/ui/toast' +import type { GrnItemBody } from '@/lib/api/procurement' +import { + grn as grnApi, + purchaseOrders as purchaseOrdersApi, + warehouses as warehousesApi, + users as usersApi, + suppliers as suppliersApi, + items as itemsApi, + getApiError, + getPaginatedItems, +} from '@/lib/api/index' +import type { + PurchaseOrderResponse, + WarehouseResponse, + UserResponse, + SupplierResponse, + ItemSummaryResponse, +} from '@/types/api' +import { LineTracking, trackingPayload, trackingProgress } from '../_components/grn-tracking' +import { + DISCOUNT_OPTIONS, + VAT_OPTIONS, + GrnTotals, + NumberCell, + PercentSelect, + batchSummary, + lineAmounts, + money, +} from '../_components/grn-costing' + +const NO_SUPPLIER = 'none' + +type GrnMode = 'po' | 'direct' + +const percent = z.number().min(0, 'Min 0').max(100, 'Max 100') + +const lineSchema = z.object({ + // Set when the line comes from a purchase order; empty on a direct line. + purchaseOrderItemId: z.string(), + itemId: z.string().min(1, 'Select an item'), + acceptedQuantity: z.number().min(0, 'Must be 0 or more'), + rejectedQuantity: z.number().min(0, 'Must be 0 or more'), + freeQuantity: z.number().min(0, 'Must be 0 or more'), + unitCost: z.number().min(0, 'Must be 0 or more'), + discountPercent: percent, + vatPercent: percent, + // One serial number per line (newline or comma separated) — used for serial-tracked items. + serialText: z.string(), + // Used for batch- and/or expiry-tracked items. + batches: z.array(z.object({ batchNumber: z.string(), expiryDate: z.string(), quantity: z.number() })), +}) + +const schema = z.object({ + warehouseId: z.string().min(1, 'Warehouse is required'), + supplierId: z.string(), + receivedAt: z.string().min(1, 'Received date is required'), + receivedByUserId: z.string().min(1, 'Received by is required'), + discountPercent: percent, + vatPercent: percent, + items: z.array(lineSchema).min(1, 'Add at least one item'), +}) + +type FormValues = z.infer + +const emptyLine = { + purchaseOrderItemId: '', + itemId: '', + acceptedQuantity: 1, + rejectedQuantity: 0, + freeQuantity: 0, + unitCost: 0, + discountPercent: 0, + vatPercent: 0, + serialText: '', + batches: [], +} + +function todayIso() { + return new Date().toISOString().slice(0, 10) +} + +/** + * One GRN form for both ways of receiving goods. "Against PO" loads the order's lines and lets more + * lines be added from that order's items (e.g. a second batch); "Direct GRN" takes any catalogue item. + */ +export default function NewGrnPage() { + const router = useRouter() + const goBack = () => router.push('/procurement/grn') + const [warehousesList, setWarehousesList] = useState([]) + const [usersList, setUsersList] = useState([]) + const [suppliersList, setSuppliersList] = useState([]) + const [itemsList, setItemsList] = useState([]) + const [eligiblePOs, setEligiblePOs] = useState([]) + const [mode, setMode] = useState('po') + const [poId, setPoId] = useState('') + const [selectedPO, setSelectedPO] = useState(null) + const [loadingPO, setLoadingPO] = useState(false) + // Tracking flags for items loaded from a PO, keyed by itemId (the PO line response doesn't carry them). + const [poItemInfo, setPoItemInfo] = useState>({}) + + const form = useForm({ + resolver: zodResolver(schema), + defaultValues: { + warehouseId: '', + supplierId: NO_SUPPLIER, + receivedAt: todayIso(), + receivedByUserId: '', + discountPercent: 0, + vatPercent: 0, + items: [{ ...emptyLine }], + }, + }) + + const { fields, append, remove, replace } = useFieldArray({ control: form.control, name: 'items' }) + + useEffect(() => { + Promise.all([ + warehousesApi.list({ pageSize: 200 }), + usersApi.list({ pageSize: 200 }), + suppliersApi.list(), + itemsApi.list({ pageSize: 200 }), + purchaseOrdersApi.list({ pageSize: 200 }), + ]) + .then(([ws, us, ss, its, pos]) => { + setWarehousesList(getPaginatedItems(ws)) + setUsersList(getPaginatedItems(us)) + setSuppliersList(ss) + setItemsList(getPaginatedItems(its)) + // Only POs that can still receive goods + const all = getPaginatedItems(pos) as PurchaseOrderResponse[] + setEligiblePOs(all.filter(po => po.status === 'Issued' || po.status === 'PartiallyReceived')) + }) + .catch(err => + toast.add({ title: 'Failed to load reference data', description: getApiError(err), type: 'error' }) + ) + }, []) + + const isPoMode = mode === 'po' + const infoFor = (itemId: string) => poItemInfo[itemId] ?? itemsList.find(i => i.id === itemId) + const poLines = selectedPO?.items?.filter(i => !i.isCancelled) ?? [] + const poLine = (poItemId: string) => poLines.find(i => i.id === poItemId) + + const watchedItems = form.watch('items') + const headerDiscount = form.watch('discountPercent') + const headerVat = form.watch('vatPercent') + // Block Save until every tracked line has its serials/batches fully entered (accepted + free units). + const trackingIncomplete = watchedItems.some(v => { + const p = trackingProgress(infoFor(v.itemId), v.acceptedQuantity + v.freeQuantity, v.serialText, v.batches) + return p !== null && !p.complete + }) + + // The GRN-wide percentage is a default: choosing one fills every line, and a line can still be edited. + function applyToAllLines(field: 'discountPercent' | 'vatPercent', value: number) { + form.setValue(field, value, { shouldDirty: true }) + fields.forEach((_, i) => form.setValue(`items.${i}.${field}`, value, { shouldDirty: true })) + } + + function changeMode(next: GrnMode) { + if (next === mode) return + setMode(next) + setPoId('') + setSelectedPO(null) + const discountPercent = form.getValues('discountPercent') + const vatPercent = form.getValues('vatPercent') + // Direct starts with one blank line; Against PO stays empty until an order is picked. + form.setValue('supplierId', NO_SUPPLIER) + replace(next === 'direct' ? [{ ...emptyLine, discountPercent, vatPercent }] : []) + } + + // A line in PO mode points at one of the order's lines; re-picking resets the cost and tracking. + function selectPoLine(index: number, poItemId: string) { + const line = poLine(poItemId) + if (!line) return + form.setValue(`items.${index}.purchaseOrderItemId`, poItemId, { shouldValidate: true }) + form.setValue(`items.${index}.itemId`, line.itemId, { shouldValidate: true }) + form.setValue(`items.${index}.unitCost`, line.unitCost) + form.setValue(`items.${index}.serialText`, '') + form.setValue(`items.${index}.batches`, []) + } + + async function handlePOChange(nextId: string) { + setPoId(nextId) + const discountPercent = form.getValues('discountPercent') + const vatPercent = form.getValues('vatPercent') + + setLoadingPO(true) + try { + const po = await purchaseOrdersApi.get(nextId) + const activeLines = (po.items ?? []).filter(item => !item.isCancelled) + + const itemIds = [...new Set(activeLines.map(l => l.itemId))] + const details = await Promise.all(itemIds.map(id => itemsApi.get(id))) + setPoItemInfo(Object.fromEntries(details.map(d => [d.id, d]))) + + setSelectedPO(po) + form.setValue('supplierId', po.supplierId) + // Pre-fill each non-cancelled line with its remaining quantity and the PO's unit cost. + replace( + activeLines.map(item => ({ + ...emptyLine, + purchaseOrderItemId: item.id, + itemId: item.itemId, + acceptedQuantity: Math.max(0, item.quantity - item.receivedQuantity), + unitCost: item.unitCost, + discountPercent, + vatPercent, + })), + ) + } catch (err) { + toast.add({ title: 'Failed to load PO details', description: getApiError(err), type: 'error' }) + setPoId('') + setSelectedPO(null) + replace([]) + } finally { + setLoadingPO(false) + } + } + + async function onSubmit(values: FormValues) { + if (isPoMode && !selectedPO) { + toast.add({ title: 'Select a purchase order.', type: 'error' }) + return + } + const items: GrnItemBody[] = [] + for (const v of values.items) { + const info = infoFor(v.itemId) + const label = info?.sku ?? 'Line' + if (isPoMode && !v.purchaseOrderItemId) { + toast.add({ title: 'Select an item for every line.', type: 'error' }) + return + } + if (v.acceptedQuantity + v.rejectedQuantity + v.freeQuantity <= 0) { + toast.add({ title: `${label}: enter a quantity.`, type: 'error' }) + return + } + const payload = trackingPayload(info, { ...v, acceptedQuantity: v.acceptedQuantity + v.freeQuantity }) + if ('error' in payload) { + toast.add({ title: `${label}: ${payload.error}`, type: 'error' }) + return + } + items.push({ + ...(isPoMode ? { purchaseOrderItemId: v.purchaseOrderItemId } : { itemId: v.itemId }), + acceptedQuantity: v.acceptedQuantity, + rejectedQuantity: v.rejectedQuantity, + freeQuantity: v.freeQuantity, + unitCost: v.unitCost, + discountPercent: v.discountPercent, + vatPercent: v.vatPercent, + ...payload, + }) + } + + try { + if (isPoMode && selectedPO) { + await grnApi.create({ + purchaseOrderId: selectedPO.id, + warehouseId: values.warehouseId, + receivedAt: `${values.receivedAt}T00:00:00.000Z`, + items, + discountPercent: values.discountPercent, + vatPercent: values.vatPercent, + receivedByUserId: values.receivedByUserId, + }) + } else { + await grnApi.createDirect({ + warehouseId: values.warehouseId, + supplierId: values.supplierId === NO_SUPPLIER ? undefined : values.supplierId, + receivedByUserId: values.receivedByUserId, + discountPercent: values.discountPercent, + vatPercent: values.vatPercent, + items, + }) + } + toast.add({ title: 'Goods receipt recorded', type: 'success' }) + router.push('/procurement/grn') + } catch (err) { + toast.add({ title: 'Failed to record receipt', description: getApiError(err), type: 'error' }) + } + } + + return ( +
+
+ +
+

New GRN

+

+ Receive stock into a warehouse. Pick a purchase order to receive against it, or leave it as direct. +

+
+
+ +
+ + + Receipt Type +
+ + +
+
+ + {isPoMode && ( + + + Purchase Order * + + + + )} + +
+ ( + + )} + /> + + {isPoMode ? ( + + Supplier + + + ) : ( + ( + + )} + /> + )} + + {isPoMode && ( + ( + + )} + /> + )} + + applyToAllLines('discountPercent', v)} + /> + + applyToAllLines('vatPercent', v)} + /> +
+ + ( + + )} + /> +
+ + + +
+
+
+

Items

+

+ {isPoMode + ? 'Lines start from the purchase order. Add Item adds another line from the same order (e.g. a second batch). Unit cost starts at the PO price; change it if the supplier invoiced differently.' + : 'Add each received item as its own line.'}{' '} + GRN discount % and VAT % fill every line; edit a line to override it. Only accepted units are costed; + free units are stocked at no cost. +

+
+ +
+ + {loadingPO ? ( +

Loading items…

+ ) : isPoMode && !selectedPO ? ( +

Select a purchase order to load its items.

+ ) : fields.length === 0 ? ( +

+ {isPoMode ? 'No receivable items left. Use Add Item to add a line.' : 'Add an item to begin.'} +

+ ) : ( + <> +
+ + + + + + + + + + + + + + + + + {fields.map((f, index) => { + const v = watchedItems[index] + const info = v ? infoFor(v.itemId) : undefined + const po = v?.purchaseOrderItemId ? poLine(v.purchaseOrderItemId) : undefined + const amounts = lineAmounts({ + acceptedQuantity: v?.acceptedQuantity ?? 0, + unitCost: v?.unitCost ?? 0, + discountPercent: v?.discountPercent ?? 0, + vatPercent: v?.vatPercent ?? 0, + }) + return ( + + + + + + + + + + + + + + + + + + + {info && v && ( + + + + )} + + ) + })} + +
ItemBatch NoAcceptedRejectedFree QtyUnit CostDisc %VAT %Line Total
+ {isPoMode ? ( + ( + + )} + /> + ) : ( + ( + + )} + /> + )} + + {batchSummary(info, v?.batches ?? [], v?.serialText ?? '')} + + + + + + + + + + + + + {money(amounts.total)} + +
+ form.setValue(`items.${index}.serialText`, val)} + onBatches={val => form.setValue(`items.${index}.batches`, val, { shouldDirty: true })} + /> +
+
+ + + + )} +
+ +
+ + +
+ +
+ ) +} diff --git a/distribution_fe/app/(dashboard)/procurement/grn/po/new/page.tsx b/distribution_fe/app/(dashboard)/procurement/grn/po/new/page.tsx deleted file mode 100644 index 4e07265..0000000 --- a/distribution_fe/app/(dashboard)/procurement/grn/po/new/page.tsx +++ /dev/null @@ -1,383 +0,0 @@ -'use client' - -import { useEffect, useState } from 'react' -import { useRouter } from 'next/navigation' -import { HugeiconsIcon } from '@hugeicons/react' -import { ArrowLeft01Icon } from '@hugeicons/core-free-icons' -import { useFieldArray } from 'react-hook-form' -import { useForm, zodResolver, z } from '@/lib/form' -import { FormField } from '@/components/shared/form-field' -import { Input } from '@/components/ui/input' -import { - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue, -} from '@/components/ui/select' -import { Button } from '@/components/ui/button' -import { FieldGroup } from '@/components/ui/field' -import { Separator } from '@/components/ui/separator' -import { toast } from '@/components/ui/toast' -import type { GrnItemBody } from '@/lib/api/procurement' -import { LineTracking, trackingPayload, trackingProgress } from '../../_components/grn-tracking' -import { - grn as grnApi, - purchaseOrders as purchaseOrdersApi, - warehouses as warehousesApi, - users as usersApi, - items as itemsApi, - getApiError, - getPaginatedItems, -} from '@/lib/api/index' -import type { - PurchaseOrderResponse, - WarehouseResponse, - UserResponse, - ItemSummaryResponse, -} from '@/types/api' - -const batchLineSchema = z.object({ - batchNumber: z.string(), - expiryDate: z.string(), - quantity: z.number(), -}) - -const grnItemSchema = z.object({ - purchaseOrderItemId: z.string().min(1), - acceptedQuantity: z.number().min(0, 'Must be 0 or more'), - rejectedQuantity: z.number().min(0, 'Must be 0 or more'), - // One serial number per line (newline or comma separated) — used for serial-tracked items. - serialText: z.string(), - // Used for batch- and/or expiry-tracked items. - batches: z.array(batchLineSchema), -}) - -const schema = z.object({ - purchaseOrderId: z.string().min(1, 'Purchase order is required'), - warehouseId: z.string().min(1, 'Warehouse is required'), - receivedAt: z.string().min(1, 'Received date is required'), - receivedByUserId: z.string().min(1, 'Received by is required'), - items: z.array(grnItemSchema).min(1, 'PO must have at least one line item'), -}) - -type FormValues = z.infer - -function todayIso() { - return new Date().toISOString().slice(0, 10) -} - -export default function NewPoGrnPage() { - const router = useRouter() - const goBack = () => router.push('/procurement/grn') - const [eligiblePOs, setEligiblePOs] = useState([]) - const [warehousesList, setWarehousesList] = useState([]) - const [usersList, setUsersList] = useState([]) - const [selectedPO, setSelectedPO] = useState(null) - const [loadingPO, setLoadingPO] = useState(false) - // Tracking flags per catalogue item, keyed by itemId — the PO line response doesn't carry them. - const [itemInfo, setItemInfo] = useState>({}) - - const form = useForm({ - resolver: zodResolver(schema), - defaultValues: { - purchaseOrderId: '', - warehouseId: '', - receivedAt: todayIso(), - receivedByUserId: '', - items: [], - }, - }) - - const { fields, replace } = useFieldArray({ control: form.control, name: 'items' }) - - const poItems = selectedPO?.items?.filter(i => !i.isCancelled) ?? [] - - // Block Save until every tracked line has its serials/batches fully entered. - const watchedItems = form.watch('items') - const trackingIncomplete = watchedItems.some((v, i) => { - const info = poItems[i] ? itemInfo[poItems[i].itemId] : undefined - const p = trackingProgress(info, v.acceptedQuantity, v.serialText, v.batches) - return p !== null && !p.complete - }) - - // Load reference data on mount - useEffect(() => { - Promise.all([ - purchaseOrdersApi.list({ pageSize: 200 }), - warehousesApi.list({ pageSize: 200 }), - usersApi.list({ pageSize: 200 }), - ]) - .then(([pos, ws, usersRes]) => { - // Only POs that can still receive goods - const pos_ = getPaginatedItems(pos) as PurchaseOrderResponse[] - setEligiblePOs(pos_.filter(po => po.status === 'Issued' || po.status === 'PartiallyReceived')) - setWarehousesList(getPaginatedItems(ws)) - setUsersList(getPaginatedItems(usersRes)) - }) - .catch(err => - toast.add({ title: 'Failed to load reference data', description: getApiError(err), type: 'error' }) - ) - }, []) - - // When PO changes, fetch its details and populate items - async function handlePOChange(poId: string) { - form.setValue('purchaseOrderId', poId, { shouldValidate: true }) - if (!poId) { - setSelectedPO(null) - replace([]) - return - } - setLoadingPO(true) - try { - const po = await purchaseOrdersApi.get(poId) - const activeLines = (po.items ?? []).filter(item => !item.isCancelled) - - const itemIds = [...new Set(activeLines.map(l => l.itemId))] - const details = await Promise.all(itemIds.map(id => itemsApi.get(id))) - setItemInfo(Object.fromEntries(details.map(d => [d.id, d]))) - - setSelectedPO(po) - // Pre-fill items: remaining quantity for each non-cancelled line - const lineItems = activeLines.map(item => ({ - purchaseOrderItemId: item.id, - acceptedQuantity: Math.max(0, item.quantity - item.receivedQuantity), - rejectedQuantity: 0, - serialText: '', - batches: [], - })) - replace(lineItems) - } catch (err) { - toast.add({ title: 'Failed to load PO details', description: getApiError(err), type: 'error' }) - } finally { - setLoadingPO(false) - } - } - - function buildItems(values: FormValues, lines: typeof poItems): GrnItemBody[] | null { - const result: GrnItemBody[] = [] - for (let i = 0; i < values.items.length; i++) { - const v = values.items[i] - const line = lines[i] - const payload = trackingPayload(line ? itemInfo[line.itemId] : undefined, v) - if ('error' in payload) { - toast.add({ title: `${line?.sku ?? `line ${i + 1}`}: ${payload.error}`, type: 'error' }) - return null - } - result.push({ - purchaseOrderItemId: v.purchaseOrderItemId, - acceptedQuantity: v.acceptedQuantity, - rejectedQuantity: v.rejectedQuantity, - ...payload, - }) - } - return result - } - - async function onSubmit(values: FormValues) { - const items = buildItems(values, poItems) - if (!items) return - try { - await grnApi.create({ - purchaseOrderId: values.purchaseOrderId, - warehouseId: values.warehouseId, - receivedAt: `${values.receivedAt}T00:00:00.000Z`, - items, - receivedByUserId: values.receivedByUserId, - }) - toast.add({ title: 'Goods receipt recorded', type: 'success' }) - router.push('/procurement/grn') - } catch (err) { - toast.add({ title: 'Failed to record receipt', description: getApiError(err), type: 'error' }) - } - } - - return ( -
-
- -
-

Receipt Against PO

-

Record stock received into a warehouse against an issued purchase order.

-
-
- -
- - ( - - )} - /> - -
- ( - - )} - /> - - ( - - )} - /> -
- - ( - - )} - /> -
- - {/* Line items — populated from the selected PO */} - {selectedPO && ( - <> - -
-

Line Items

- - {loadingPO ? ( -

Loading items…

- ) : poItems.length === 0 ? ( -

No receivable items on this order.

- ) : ( - <> - {/* Header row */} -
-

Item

-

Accepted

-

Rejected

-
- - {fields.map((f, index) => { - const poItem = poItems[index] - return ( -
-
- {poItem?.itemName} - - {poItem?.sku} · ordered {poItem?.quantity}, received {poItem?.receivedQuantity} - -
- - ( - field.onChange(Number(e.target.value))} - onBlur={field.onBlur} - aria-invalid={invalid} - /> - )} - /> - - ( - field.onChange(Number(e.target.value))} - onBlur={field.onBlur} - aria-invalid={invalid} - /> - )} - /> - - {poItem && itemInfo[poItem.itemId] && ( - form.setValue(`items.${index}.serialText`, v)} - onBatches={v => form.setValue(`items.${index}.batches`, v, { shouldDirty: true })} - /> - )} -
- ) - })} - - )} -
- - )} - -
- - -
- -
- ) -} diff --git a/distribution_fe/lib/api/procurement.ts b/distribution_fe/lib/api/procurement.ts index db143c3..560ff38 100644 --- a/distribution_fe/lib/api/procurement.ts +++ b/distribution_fe/lib/api/procurement.ts @@ -77,6 +77,13 @@ export interface GrnItemBody { batches?: GrnBatchLineBody[] /** Only for items with TracksSerialNumbers — exactly acceptedQuantity entries. */ serialNumbers?: string[] + /** Cost per base-UOM unit. Omitted on a PO line falls back to the PO line's cost. */ + unitCost?: number + /** Omitted falls back to the GRN-wide percentage. */ + discountPercent?: number + vatPercent?: number + /** Free-issue units: stocked but not costed. Serials/batches must cover accepted + free. */ + freeQuantity?: number } export interface GrnBody { @@ -84,6 +91,9 @@ export interface GrnBody { warehouseId: string receivedAt: string items: GrnItemBody[] + /** GRN-wide defaults for lines that don't set their own. */ + discountPercent?: number + vatPercent?: number // TEMPORARY: required while auth doesn't resolve a logged-in user server-side — see testbug.md #28. receivedByUserId: string } @@ -92,6 +102,8 @@ export interface DirectGrnBody { warehouseId: string supplierId?: string items: GrnItemBody[] + discountPercent?: number + vatPercent?: number // TEMPORARY: required while auth doesn't resolve a logged-in user server-side — see testbug.md #28. receivedByUserId: string } diff --git a/distribution_fe/types/api.ts b/distribution_fe/types/api.ts index c027417..90c0766 100644 --- a/distribution_fe/types/api.ts +++ b/distribution_fe/types/api.ts @@ -418,6 +418,17 @@ export interface GrnItemResponse { rejectedQuantity: number batches: GrnBatchLineResponse[] serialNumbers: string[] + unitCost: number + discountPercent: number + vatPercent: number + /** Accepted qty x unit cost, before discount. */ + grossAmount: number + discountAmount: number + vatAmount: number + /** Gross - discount + VAT. */ + lineTotal: number + /** Free-issue units received at no charge (in stock, not costed). */ + freeQuantity: number } export interface GrnResponse { @@ -432,6 +443,14 @@ export interface GrnResponse { receivedAt: string createdAt: string items: GrnItemResponse[] + discountPercent: number + vatPercent: number + subTotal: number + discountAmount: number + /** Subtotal - discount; the base VAT is charged on. */ + taxableAmount: number + vatAmount: number + totalAmount: number } export interface PurchaseReturnItemResponse { From 55aa35308aff907176d8926a944439d8e32bd2ef Mon Sep 17 00:00:00 2001 From: DeepnaPooja Date: Thu, 8 Oct 2026 12:41:44 +0530 Subject: [PATCH 3/9] Add NBT support to GRN processing and pricing calculations --- Distribution/Features/Grn/GrnController.cs | 2 +- Distribution/Features/Grn/GrnDTOs.cs | 23 +++++-- Distribution/Features/Grn/GrnPricing.cs | 15 +++-- Distribution/Features/Grn/GrnService.cs | 76 ++++++++++++++++------ Distribution/Models/Enums.cs | 3 + Distribution/Models/GoodsReceipt.cs | 4 ++ Distribution/Models/GoodsReceiptItem.cs | 3 + 7 files changed, 92 insertions(+), 34 deletions(-) diff --git a/Distribution/Features/Grn/GrnController.cs b/Distribution/Features/Grn/GrnController.cs index a68c5a6..cf49af1 100644 --- a/Distribution/Features/Grn/GrnController.cs +++ b/Distribution/Features/Grn/GrnController.cs @@ -33,7 +33,7 @@ public class GrnController(GrnService service, ICurrentUserService currentUser) try { var inner = new CreateGrnRequest(request.WarehouseId, request.Items, - DiscountPercent: request.DiscountPercent, VatPercent: request.VatPercent); + DiscountPercent: request.DiscountPercent, VatPercent: request.VatPercent, NbtPercent: request.NbtPercent); var result = await service.CreateAsync(request.PurchaseOrderId, inner, userId.Value, ct); return CreatedAtAction(nameof(GetById), new { id = result.Id }, result); } diff --git a/Distribution/Features/Grn/GrnDTOs.cs b/Distribution/Features/Grn/GrnDTOs.cs index 6c5f7b4..1f7d910 100644 --- a/Distribution/Features/Grn/GrnDTOs.cs +++ b/Distribution/Features/Grn/GrnDTOs.cs @@ -20,7 +20,9 @@ public record GrnItemRequest( decimal? DiscountPercent = null, decimal? VatPercent = null, // Free-issue units: stocked but not costed. Serial/batch entries must cover accepted + free. - int FreeQuantity = 0); + int FreeQuantity = 0, + // Ignored when NBT is disabled in configuration; null falls back to the GRN-wide percentage. + decimal? NbtPercent = null); public record CreateGrnRequest( Guid WarehouseId, @@ -29,7 +31,8 @@ public record CreateGrnRequest( Guid? ReceivedByUserId = null, // GRN-wide defaults for lines that don't set their own. decimal? DiscountPercent = null, - decimal? VatPercent = null); + decimal? VatPercent = null, + decimal? NbtPercent = null); public record CreateFlatGrnRequest( Guid PurchaseOrderId, @@ -38,7 +41,8 @@ public record CreateFlatGrnRequest( // TEMPORARY: required while auth doesn't resolve a logged-in user server-side — see testbug.md #28. Guid? ReceivedByUserId = null, decimal? DiscountPercent = null, - decimal? VatPercent = null); + decimal? VatPercent = null, + decimal? NbtPercent = null); /// A goods receipt with no purchase order. SupplierId is optional. public record CreateDirectGrnRequest( @@ -48,7 +52,8 @@ public record CreateDirectGrnRequest( // TEMPORARY: required while auth doesn't resolve a logged-in user server-side — see testbug.md #28. Guid? ReceivedByUserId = null, decimal? DiscountPercent = null, - decimal? VatPercent = null); + decimal? VatPercent = null, + decimal? NbtPercent = null); public record GrnBatchLineResponse(string? BatchNumber, DateTime? ExpiryDate, int Quantity); @@ -71,7 +76,9 @@ public record GrnItemResponse( decimal VatAmount, // Gross - discount + VAT. decimal LineTotal, - int FreeQuantity); + int FreeQuantity, + decimal NbtPercent, + decimal NbtAmount); public record GrnResponse( Guid Id, @@ -89,7 +96,9 @@ public record GrnResponse( // Sum of line gross amounts. decimal SubTotal, decimal DiscountAmount, - // SubTotal - DiscountAmount (the base VAT is charged on). + // SubTotal - DiscountAmount (the base NBT is charged on; VAT is charged on this plus NBT). decimal TaxableAmount, decimal VatAmount, - decimal TotalAmount); + decimal TotalAmount, + decimal NbtPercent, + decimal NbtAmount); diff --git a/Distribution/Features/Grn/GrnPricing.cs b/Distribution/Features/Grn/GrnPricing.cs index da64363..9b98b94 100644 --- a/Distribution/Features/Grn/GrnPricing.cs +++ b/Distribution/Features/Grn/GrnPricing.cs @@ -1,21 +1,26 @@ namespace Distribution.Features.Grn; +public record LineAmounts(decimal Gross, decimal Discount, decimal Nbt, decimal Vat, decimal Total); + /// /// GRN costing. Per line: gross = accepted qty x unit cost; discount = gross x disc%; -/// VAT = (gross - discount) x VAT%; total = gross - discount + VAT. Rejected units are never costed. +/// NBT = (gross - discount) x NBT%; VAT = (gross - discount + NBT) x VAT%; +/// total = gross - discount + NBT + VAT. Rejected units are never costed. /// Each amount is rounded to 2 dp per line so the GRN totals equal the sum of what the lines show. /// public static class GrnPricing { public static decimal Round(decimal value) => Math.Round(value, 2, MidpointRounding.AwayFromZero); - public static (decimal Gross, decimal Discount, decimal Vat, decimal Total) Line( - int acceptedQty, decimal unitCost, decimal discountPercent, decimal vatPercent) + public static LineAmounts Line( + int acceptedQty, decimal unitCost, decimal discountPercent, decimal nbtPercent, decimal vatPercent) { var gross = Round(acceptedQty * unitCost); var discount = Round(gross * discountPercent / 100m); - var vat = Round((gross - discount) * vatPercent / 100m); - return (gross, discount, vat, gross - discount + vat); + var net = gross - discount; + var nbt = Round(net * nbtPercent / 100m); + var vat = Round((net + nbt) * vatPercent / 100m); + return new LineAmounts(gross, discount, nbt, vat, net + nbt + vat); } public static void ValidatePercent(decimal value, string label) diff --git a/Distribution/Features/Grn/GrnService.cs b/Distribution/Features/Grn/GrnService.cs index 40ebb0e..2e871cd 100644 --- a/Distribution/Features/Grn/GrnService.cs +++ b/Distribution/Features/Grn/GrnService.cs @@ -1,5 +1,6 @@ using Distribution.Features.Po; using Distribution.Features.Stock; +using Distribution.Features.SysConfig; using Distribution.Infra.UoW; using Distribution.Models; @@ -11,18 +12,20 @@ public class GrnService( IStockRepository stockRepo, IUnitOfWork uow, Distribution.Features.Item.IItemRepository itemRepo, - Distribution.Features.Supplier.ISupplierRepository supplierRepo) + Distribution.Features.Supplier.ISupplierRepository supplierRepo, + Distribution.Features.SysConfig.SysConfigService sysConfig) { private static GrnItemResponse MapItem(Models.GoodsReceiptItem i) { // Direct-receipt lines carry the item themselves; PO lines resolve it through the PO line. var item = i.Item ?? i.PurchaseOrderItem!.Item; - var (gross, discount, vat, total) = GrnPricing.Line(i.AcceptedQuantity, i.UnitCost, i.DiscountPercent, i.VatPercent); + var a = GrnPricing.Line(i.AcceptedQuantity, i.UnitCost, i.DiscountPercent, i.NbtPercent, i.VatPercent); return new(i.Id, i.FK_PurchaseOrderItemId, item.Id, item.Sku, item.Name, i.AcceptedQuantity, i.RejectedQuantity, i.Batches.Select(b => new GrnBatchLineResponse(b.ItemBatch.BatchNumber, b.ItemBatch.ExpiryDate, b.Quantity)).ToList(), i.Serials.Select(s => s.ItemSerialNumber.SerialNumber).ToList(), - i.UnitCost, i.DiscountPercent, i.VatPercent, gross, discount, vat, total, i.FreeQuantity); + i.UnitCost, i.DiscountPercent, i.VatPercent, a.Gross, a.Discount, a.Vat, a.Total, i.FreeQuantity, + i.NbtPercent, a.Nbt); } /// Units that actually enter stock: accepted plus free-issue. Serial/batch entries must cover this. @@ -34,33 +37,60 @@ public class GrnService( var subTotal = items.Sum(i => i.GrossAmount); var discount = items.Sum(i => i.DiscountAmount); var vat = items.Sum(i => i.VatAmount); + var nbt = items.Sum(i => i.NbtAmount); return new(gr.Id, gr.FK_PurchaseOrderId, gr.FK_ReceivedByUserId, gr.FK_WarehouseId, gr.Warehouse?.Name ?? string.Empty, gr.FK_SupplierId, gr.Supplier?.Name, gr.ReceivedAt, gr.CreatedAt, items, gr.DiscountPercent, gr.VatPercent, - subTotal, discount, subTotal - discount, vat, subTotal - discount + vat); + subTotal, discount, subTotal - discount, vat, subTotal - discount + nbt + vat, + gr.NbtPercent, nbt); } - /// Validates and resolves the GRN-wide percentages that lines fall back to. - private static (decimal Discount, decimal Vat) ResolveHeader(decimal? discountPercent, decimal? vatPercent) + /// The percentages a receipt is saved with, after the configured tax rules were applied. + private record GrnHeader(decimal Discount, decimal Vat, decimal Nbt, decimal? RequestedVat, decimal? RequestedNbt); + + /// + /// Validates and resolves the GRN-wide percentages that lines fall back to. A disabled tax is + /// forced to 0 whatever the client sent. + /// + private static GrnHeader ResolveHeader(GrnTaxSettings tax, decimal? discountPercent, decimal? vatPercent, decimal? nbtPercent) { var discount = discountPercent ?? 0m; - var vat = vatPercent ?? 0m; + var vat = tax.VatMode == TaxMode.Disabled ? 0m : vatPercent ?? 0m; + var nbt = tax.NbtMode == TaxMode.Disabled ? 0m : nbtPercent ?? 0m; GrnPricing.ValidatePercent(discount, "GRN discount %"); GrnPricing.ValidatePercent(vat, "GRN VAT %"); - return (discount, vat); + GrnPricing.ValidatePercent(nbt, "GRN NBT %"); + return new GrnHeader(discount, vat, nbt, vatPercent, nbtPercent); } - /// Copies a line's resolved cost, discount % and VAT % onto the entity after validating them. + /// + /// A line's rate for one tax: 0 when the tax is disabled; otherwise the line's own value, else the + /// GRN-wide one. A Mandatory tax must be supplied somewhere; an Optional one defaults to 0. + /// + private static decimal ResolveTax(TaxMode mode, decimal? headerValue, decimal? lineValue, string label) + { + if (mode == TaxMode.Disabled) return 0m; + var value = lineValue ?? headerValue; + if (value is null) + { + if (mode == TaxMode.Mandatory) throw new InvalidOperationException($"{label} is mandatory."); + return 0m; + } + GrnPricing.ValidatePercent(value.Value, label); + return value.Value; + } + + /// Copies a line's resolved cost, discount %, NBT % and VAT % onto the entity after validating them. private static void ApplyPricing(Models.GoodsReceiptItem entity, GrnItemRequest line, decimal fallbackUnitCost, - decimal headerDiscount, decimal headerVat, string sku) + GrnHeader header, GrnTaxSettings tax, string sku) { entity.UnitCost = line.UnitCost ?? fallbackUnitCost; - entity.DiscountPercent = line.DiscountPercent ?? headerDiscount; - entity.VatPercent = line.VatPercent ?? headerVat; + entity.DiscountPercent = line.DiscountPercent ?? header.Discount; + entity.VatPercent = ResolveTax(tax.VatMode, header.RequestedVat, line.VatPercent, $"VAT % for item {sku}"); + entity.NbtPercent = ResolveTax(tax.NbtMode, header.RequestedNbt, line.NbtPercent, $"NBT % for item {sku}"); GrnPricing.ValidateCost(entity.UnitCost, $"Unit cost for item {sku}"); GrnPricing.ValidatePercent(entity.DiscountPercent, $"Discount % for item {sku}"); - GrnPricing.ValidatePercent(entity.VatPercent, $"VAT % for item {sku}"); } public async Task> GetAllAsync(Guid? purchaseOrderId, int page, int pageSize) @@ -151,7 +181,8 @@ public class GrnService( if (request.Items is null || request.Items.Count == 0) throw new InvalidOperationException("A goods receipt must have at least one line item."); - var (headerDiscount, headerVat) = ResolveHeader(request.DiscountPercent, request.VatPercent); + var tax = await sysConfig.GetGrnTaxSettingsAsync(); + var header = ResolveHeader(tax, request.DiscountPercent, request.VatPercent, request.NbtPercent); var goodsReceipt = new Models.GoodsReceipt { @@ -159,8 +190,9 @@ public class GrnService( FK_ReceivedByUserId = receivedByUserId, FK_WarehouseId = request.WarehouseId, ReceivedAt = DateTime.Now, - DiscountPercent = headerDiscount, - VatPercent = headerVat + DiscountPercent = header.Discount, + VatPercent = header.Vat, + NbtPercent = header.Nbt }; var grLines = new List<(Models.GoodsReceiptItem Entity, GrnItemRequest Line, Models.Item Item)>(); @@ -203,7 +235,7 @@ public class GrnService( FreeQuantity = line.FreeQuantity, RejectedQuantity = line.RejectedQuantity }; - ApplyPricing(entity, line, poItem.UnitCost, headerDiscount, headerVat, poItem.Item.Sku); + ApplyPricing(entity, line, poItem.UnitCost, header, tax, poItem.Item.Sku); goodsReceipt.Items.Add(entity); grLines.Add((entity, line, poItem.Item)); } @@ -227,7 +259,8 @@ public class GrnService( if (request.SupplierId is not null && await supplierRepo.GetByIdAsync(request.SupplierId.Value) is not { IsActive: true }) throw new InvalidOperationException("Supplier not found."); - var (headerDiscount, headerVat) = ResolveHeader(request.DiscountPercent, request.VatPercent); + var tax = await sysConfig.GetGrnTaxSettingsAsync(); + var header = ResolveHeader(tax, request.DiscountPercent, request.VatPercent, request.NbtPercent); var goodsReceipt = new Models.GoodsReceipt { @@ -236,8 +269,9 @@ public class GrnService( FK_ReceivedByUserId = receivedByUserId, FK_WarehouseId = request.WarehouseId, ReceivedAt = DateTime.Now, - DiscountPercent = headerDiscount, - VatPercent = headerVat + DiscountPercent = header.Discount, + VatPercent = header.Vat, + NbtPercent = header.Nbt }; var grLines = new List<(Models.GoodsReceiptItem Entity, GrnItemRequest Line, Models.Item Item)>(); @@ -278,7 +312,7 @@ public class GrnService( AcceptedQuantity = line.AcceptedQuantity, RejectedQuantity = line.RejectedQuantity }; - ApplyPricing(entity, line, 0m, headerDiscount, headerVat, item.Sku); + ApplyPricing(entity, line, 0m, header, tax, item.Sku); goodsReceipt.Items.Add(entity); grLines.Add((entity, line, item)); } diff --git a/Distribution/Models/Enums.cs b/Distribution/Models/Enums.cs index 26a62c1..f74a70b 100644 --- a/Distribution/Models/Enums.cs +++ b/Distribution/Models/Enums.cs @@ -45,5 +45,8 @@ namespace Distribution.Models /// public enum InvoiceCreationMode { ModeA, ModeB } + /// How a tax applies on a document: not at all, may be left at 0, or must be filled in. + public enum TaxMode { Disabled, Optional, Mandatory } + public enum SerialStatus { InStock, Sold, Returned, Damaged } } diff --git a/Distribution/Models/GoodsReceipt.cs b/Distribution/Models/GoodsReceipt.cs index 025103b..9587c03 100644 --- a/Distribution/Models/GoodsReceipt.cs +++ b/Distribution/Models/GoodsReceipt.cs @@ -36,6 +36,10 @@ namespace Distribution.Models [Precision(5, 2)] public decimal VatPercent { get; set; } + /// GRN-wide default NBT %, applied to any line that doesn't set its own. + [Precision(5, 2)] + public decimal NbtPercent { get; set; } + public ICollection Items { get; set; } = []; } } diff --git a/Distribution/Models/GoodsReceiptItem.cs b/Distribution/Models/GoodsReceiptItem.cs index 734e8f7..55a33bc 100644 --- a/Distribution/Models/GoodsReceiptItem.cs +++ b/Distribution/Models/GoodsReceiptItem.cs @@ -41,6 +41,9 @@ namespace Distribution.Models [Precision(5, 2)] public decimal VatPercent { get; set; } + [Precision(5, 2)] + public decimal NbtPercent { get; set; } + /// Populated when the received item tracks batches and/or expiry dates. public ICollection Batches { get; set; } = []; From 702efba62e1a75eac78cd220363878fb9dd12a66 Mon Sep 17 00:00:00 2001 From: DeepnaPooja Date: Thu, 8 Oct 2026 12:42:50 +0530 Subject: [PATCH 4/9] Add GetNextSerialSequence functionality to Stock services and repository --- .../Features/Stock/IStockRepository.cs | 2 ++ .../Features/Stock/StockController.cs | 8 ++++++++ .../Features/Stock/StockRepository.cs | 20 +++++++++++++++++++ Distribution/Features/Stock/StockService.cs | 4 ++++ 4 files changed, 34 insertions(+) diff --git a/Distribution/Features/Stock/IStockRepository.cs b/Distribution/Features/Stock/IStockRepository.cs index 832ce86..57854ec 100644 --- a/Distribution/Features/Stock/IStockRepository.cs +++ b/Distribution/Features/Stock/IStockRepository.cs @@ -28,5 +28,7 @@ public interface IStockRepository // Serial numbers Task SerialNumberExistsAsync(Guid itemId, string serialNumber); + /// One past the highest numeric suffix among the item's existing serials that start with (1 if none). + Task GetNextSerialSequenceAsync(Guid itemId, string prefix); Task AddItemSerialNumberAsync(ItemSerialNumber serial); } diff --git a/Distribution/Features/Stock/StockController.cs b/Distribution/Features/Stock/StockController.cs index db6ed68..d9af4fd 100644 --- a/Distribution/Features/Stock/StockController.cs +++ b/Distribution/Features/Stock/StockController.cs @@ -37,6 +37,14 @@ public class StockController(StockService service, ICurrentUserService currentUs return Ok(result); } + /// Next free number for auto-generated serials (e.g. after ...-0005 the answer is 6). + [HttpGet("serials/next-sequence")] + public async Task GetNextSerialSequence([FromQuery] Guid itemId, [FromQuery] string prefix = "") + { + var next = await service.GetNextSerialSequenceAsync(itemId, prefix); + return Ok(new { next }); + } + [HttpGet("movements")] public async Task GetMovements( [FromQuery] Guid? locationId, diff --git a/Distribution/Features/Stock/StockRepository.cs b/Distribution/Features/Stock/StockRepository.cs index 087a888..4130c59 100644 --- a/Distribution/Features/Stock/StockRepository.cs +++ b/Distribution/Features/Stock/StockRepository.cs @@ -107,6 +107,26 @@ public class StockRepository(AppDBContext db) : IStockRepository public Task SerialNumberExistsAsync(Guid itemId, string serialNumber) => db.ItemSerialNumbers.AnyAsync(s => s.FK_ItemId == itemId && s.SerialNumber == serialNumber); + public async Task GetNextSerialSequenceAsync(Guid itemId, string prefix) + { + // Only serials of the form count; anything else (a supplier's own serial that + // happens to start with the prefix) is ignored for numbering. + var serials = await db.ItemSerialNumbers + .AsNoTracking() + .Where(s => s.FK_ItemId == itemId && s.SerialNumber.StartsWith(prefix)) + .Select(s => s.SerialNumber) + .ToListAsync(); + + var max = 0L; + foreach (var serial in serials) + { + var suffix = serial[prefix.Length..]; + if (suffix.Length is > 0 and <= 9 && suffix.All(char.IsDigit) && long.TryParse(suffix, out var n)) + max = Math.Max(max, n); + } + return (int)max + 1; + } + public async Task AddItemSerialNumberAsync(ItemSerialNumber serial) => await db.ItemSerialNumbers.AddAsync(serial); } diff --git a/Distribution/Features/Stock/StockService.cs b/Distribution/Features/Stock/StockService.cs index cfca6d1..dd29feb 100644 --- a/Distribution/Features/Stock/StockService.cs +++ b/Distribution/Features/Stock/StockService.cs @@ -96,6 +96,10 @@ public class StockService( b.Quantity, b.Item.ReorderLevel!.Value)).ToList(); } + /// The next unused sequence number for generating serials like "<prefix>0001" for an item. + public Task GetNextSerialSequenceAsync(Guid itemId, string prefix) => + repo.GetNextSerialSequenceAsync(itemId, prefix ?? string.Empty); + public async Task GetItemTotalAsync(Guid itemId) { var item = await itemRepo.GetByIdAsync(itemId); From b12f3c8d0895faf19aeea7d1966105838c598c1c Mon Sep 17 00:00:00 2001 From: DeepnaPooja Date: Thu, 8 Oct 2026 12:44:28 +0530 Subject: [PATCH 5/9] Add NbtPercent column to GoodsReceipts and GoodsReceiptItems --- .../Features/SysConfig/SysConfigDTOs.cs | 3 + .../Features/SysConfig/SysConfigService.cs | 44 + .../20261008060840_AddGrnNbt.Designer.cs | 4423 +++++++++++++++++ .../Migrations/20261008060840_AddGrnNbt.cs | 66 + .../Migrations/AppDBContextModelSnapshot.cs | 11 + 5 files changed, 4547 insertions(+) create mode 100644 Distribution/Migrations/20261008060840_AddGrnNbt.Designer.cs create mode 100644 Distribution/Migrations/20261008060840_AddGrnNbt.cs diff --git a/Distribution/Features/SysConfig/SysConfigDTOs.cs b/Distribution/Features/SysConfig/SysConfigDTOs.cs index 2907521..3269c26 100644 --- a/Distribution/Features/SysConfig/SysConfigDTOs.cs +++ b/Distribution/Features/SysConfig/SysConfigDTOs.cs @@ -7,3 +7,6 @@ public record SysConfigResponse(string Key, string Value, DateTime UpdatedAt, Gu public record SetSysConfigRequest(string Value, Guid? UserId); public record SetInvoiceCreationModeRequest(InvoiceCreationMode Mode); + +/// Which taxes apply to a GRN, whether each must be filled in, and the rate new GRNs start with. +public record GrnTaxSettings(TaxMode VatMode, decimal VatDefaultRate, TaxMode NbtMode, decimal NbtDefaultRate); diff --git a/Distribution/Features/SysConfig/SysConfigService.cs b/Distribution/Features/SysConfig/SysConfigService.cs index a47bca1..1d8615b 100644 --- a/Distribution/Features/SysConfig/SysConfigService.cs +++ b/Distribution/Features/SysConfig/SysConfigService.cs @@ -14,6 +14,14 @@ public class SysConfigService(ISysConfigRepository repo, IUnitOfWork uow) public const string KeyCustomWarrantyOverrideEnabled = "Feature.CustomWarrantyOverride"; public const string KeyDefaultWarrantyPeriodDays = "Feature.DefaultWarrantyPeriodDays"; + public const string KeyGrnVatMode = "Grn.Tax.Vat.Mode"; + public const string KeyGrnVatRate = "Grn.Tax.Vat.DefaultRate"; + public const string KeyGrnNbtMode = "Grn.Tax.Nbt.Mode"; + public const string KeyGrnNbtRate = "Grn.Tax.Nbt.DefaultRate"; + + private static readonly string[] TaxModeKeys = [KeyGrnVatMode, KeyGrnNbtMode]; + private static readonly string[] TaxRateKeys = [KeyGrnVatRate, KeyGrnNbtRate]; + private static readonly string[] BooleanFeatureKeys = [ KeySerialNumberTrackingEnabled, @@ -83,6 +91,33 @@ public class SysConfigService(ISysConfigRepository repo, IUnitOfWork uow) return config is not null && int.TryParse(config.Value, out var days) ? days : 0; } + /// + /// GRN tax settings. Unconfigured, VAT is Optional (how GRNs behaved before this setting existed) + /// and NBT is Disabled. + /// + public async Task GetGrnTaxSettingsAsync() => new( + await GetTaxModeAsync(KeyGrnVatMode, TaxMode.Optional), + await GetRateAsync(KeyGrnVatRate), + await GetTaxModeAsync(KeyGrnNbtMode, TaxMode.Disabled), + await GetRateAsync(KeyGrnNbtRate)); + + private async Task GetTaxModeAsync(string key, TaxMode fallback) + { + var config = await repo.GetByKeyAsync(key); + return config is not null && Enum.TryParse(config.Value, out var mode) && Enum.IsDefined(mode) + ? mode + : fallback; + } + + private async Task GetRateAsync(string key) + { + var config = await repo.GetByKeyAsync(key); + return config is not null + && decimal.TryParse(config.Value, System.Globalization.NumberStyles.Number, System.Globalization.CultureInfo.InvariantCulture, out var rate) + ? rate + : 0m; + } + private async Task GetBoolAsync(string key) { var config = await repo.GetByKeyAsync(key); @@ -98,6 +133,15 @@ public class SysConfigService(ISysConfigRepository repo, IUnitOfWork uow) if (BooleanFeatureKeys.Contains(key) && !bool.TryParse(value, out _)) throw new InvalidOperationException($"Invalid value '{value}' for '{key}'. Allowed values: true, false."); + if (TaxModeKeys.Contains(key) && (!Enum.TryParse(value, out var taxMode) || !Enum.IsDefined(taxMode))) + throw new InvalidOperationException( + $"Invalid value '{value}' for '{key}'. Allowed values: {string.Join(", ", Enum.GetNames())}."); + + if (TaxRateKeys.Contains(key) + && (!decimal.TryParse(value, System.Globalization.NumberStyles.Number, System.Globalization.CultureInfo.InvariantCulture, out var rate) + || rate < 0 || rate > 100)) + throw new InvalidOperationException($"Invalid value '{value}' for '{key}'. Must be a percentage from 0 to 100."); + if (key == KeyDefaultWarrantyPeriodDays && (!int.TryParse(value, out var days) || days < 0)) throw new InvalidOperationException($"Invalid value '{value}' for '{key}'. Must be a non-negative integer."); } diff --git a/Distribution/Migrations/20261008060840_AddGrnNbt.Designer.cs b/Distribution/Migrations/20261008060840_AddGrnNbt.Designer.cs new file mode 100644 index 0000000..9cabced --- /dev/null +++ b/Distribution/Migrations/20261008060840_AddGrnNbt.Designer.cs @@ -0,0 +1,4423 @@ +// +using System; +using Distribution.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace Distribution.Migrations +{ + [DbContext(typeof(AppDBContext))] + [Migration("20261008060840_AddGrnNbt")] + partial class AddGrnNbt + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "9.0.0") + .HasAnnotation("Relational:MaxIdentifierLength", 64); + + MySqlModelBuilderExtensions.AutoIncrementColumns(modelBuilder); + + modelBuilder.Entity("Distribution.Models.Brand", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("ExternalErpId") + .HasColumnType("int"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.ToTable("Brands"); + + b.HasData( + new + { + Id = new Guid("21000000-0000-0000-0000-000000000001"), + CreatedAt = new DateTime(2026, 8, 20, 0, 0, 0, 0, DateTimeKind.Unspecified), + IsActive = true, + Name = "Coca-Cola" + }, + new + { + Id = new Guid("21000000-0000-0000-0000-000000000002"), + CreatedAt = new DateTime(2026, 8, 20, 0, 0, 0, 0, DateTimeKind.Unspecified), + IsActive = true, + Name = "Lays" + }); + }); + + modelBuilder.Entity("Distribution.Models.Category", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("AllowCustomerReturn") + .HasColumnType("tinyint(1)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("ExternalErpId") + .HasColumnType("int"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.ToTable("Categories"); + + b.HasData( + new + { + Id = new Guid("20000000-0000-0000-0000-000000000001"), + AllowCustomerReturn = false, + CreatedAt = new DateTime(2026, 8, 20, 0, 0, 0, 0, DateTimeKind.Unspecified), + IsActive = true, + Name = "Beverages" + }, + new + { + Id = new Guid("20000000-0000-0000-0000-000000000002"), + AllowCustomerReturn = false, + CreatedAt = new DateTime(2026, 8, 20, 0, 0, 0, 0, DateTimeKind.Unspecified), + IsActive = true, + Name = "Snacks" + }); + }); + + modelBuilder.Entity("Distribution.Models.Claim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Details") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("FK_AgencyUserId") + .HasColumnType("char(36)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("SettledAt") + .HasColumnType("datetime(6)"); + + b.Property("SettledByUserId") + .HasColumnType("char(36)"); + + b.Property("SettlementAmount") + .HasColumnType("decimal(65,30)"); + + b.Property("Status") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("FK_AgencyUserId"); + + b.HasIndex("SettledByUserId"); + + b.ToTable("Claims"); + + b.HasData( + new + { + Id = new Guid("53000000-0000-0000-0000-000000000001"), + CreatedAt = new DateTime(2026, 8, 27, 0, 0, 0, 0, DateTimeKind.Unspecified), + Details = "Distributor margin claim for Q3 promotional shortfall", + FK_AgencyUserId = new Guid("70000000-0000-0000-0000-000000000001"), + IsActive = true, + SettledAt = new DateTime(2026, 8, 28, 0, 0, 0, 0, DateTimeKind.Unspecified), + SettledByUserId = new Guid("29000000-0000-0000-0000-000000000006"), + SettlementAmount = 15000m, + Status = 1 + }); + }); + + modelBuilder.Entity("Distribution.Models.CreditNote", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("Amount") + .HasColumnType("decimal(65,30)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("FK_SaleReturnId") + .HasColumnType("char(36)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("IssuedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("FK_SaleReturnId"); + + b.ToTable("CreditNotes"); + + b.HasData( + new + { + Id = new Guid("54000000-0000-0000-0000-000000000001"), + Amount = 300m, + CreatedAt = new DateTime(2026, 8, 26, 0, 0, 0, 0, DateTimeKind.Unspecified), + FK_SaleReturnId = new Guid("51000000-0000-0000-0000-000000000001"), + IsActive = true, + IssuedAt = new DateTime(2026, 8, 26, 0, 0, 0, 0, DateTimeKind.Unspecified) + }); + }); + + modelBuilder.Entity("Distribution.Models.DeliveryRun", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CancelReason") + .HasColumnType("longtext"); + + b.Property("CancelTypeCode") + .HasColumnType("int"); + + b.Property("ClosedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("FK_DriverProfileId") + .HasColumnType("char(36)"); + + b.Property("FK_VehicleId") + .HasColumnType("char(36)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("Meterend") + .HasColumnType("int"); + + b.Property("Meterstart") + .HasColumnType("int"); + + b.Property("StartedAt") + .HasColumnType("datetime(6)"); + + b.Property("Status") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("FK_DriverProfileId"); + + b.HasIndex("FK_VehicleId"); + + b.ToTable("DeliveryRuns"); + + b.HasData( + new + { + Id = new Guid("31000000-0000-0000-0000-000000000001"), + ClosedAt = new DateTime(2026, 8, 25, 14, 0, 0, 0, DateTimeKind.Unspecified), + CreatedAt = new DateTime(2026, 8, 25, 7, 30, 0, 0, DateTimeKind.Unspecified), + FK_DriverProfileId = new Guid("30000000-0000-0000-0000-000000000001"), + FK_VehicleId = new Guid("28000000-0000-0000-0000-000000000001"), + IsActive = true, + Meterend = 10550, + Meterstart = 10500, + StartedAt = new DateTime(2026, 8, 25, 8, 0, 0, 0, DateTimeKind.Unspecified), + Status = 2 + }, + new + { + Id = new Guid("31000000-0000-0000-0000-000000000002"), + CreatedAt = new DateTime(2026, 8, 30, 8, 45, 0, 0, DateTimeKind.Unspecified), + FK_DriverProfileId = new Guid("30000000-0000-0000-0000-000000000002"), + FK_VehicleId = new Guid("28000000-0000-0000-0000-000000000002"), + IsActive = true, + Meterend = 0, + Meterstart = 20300, + StartedAt = new DateTime(2026, 8, 30, 9, 0, 0, 0, DateTimeKind.Unspecified), + Status = 0 + }); + }); + + modelBuilder.Entity("Distribution.Models.DeliveryTask", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("ConfirmedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CustomerConfirmed") + .HasColumnType("tinyint(1)"); + + b.Property("FK_RideId") + .HasColumnType("char(36)"); + + b.Property("FK_ShopPOId") + .HasColumnType("char(36)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("Note") + .HasColumnType("longtext"); + + b.Property("Sequence") + .HasColumnType("int"); + + b.Property("Status") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("FK_RideId"); + + b.HasIndex("FK_ShopPOId"); + + b.ToTable("DeliveryTasks"); + + b.HasData( + new + { + Id = new Guid("37000000-0000-0000-0000-000000000001"), + CreatedAt = new DateTime(2026, 8, 30, 10, 5, 0, 0, DateTimeKind.Unspecified), + CustomerConfirmed = false, + FK_RideId = new Guid("31000000-0000-0000-0000-000000000002"), + FK_ShopPOId = new Guid("33000000-0000-0000-0000-000000000002"), + IsActive = true, + Sequence = 1, + Status = 0 + }); + }); + + modelBuilder.Entity("Distribution.Models.DriverComplianceConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("EnforceLicenseExpiry") + .HasColumnType("tinyint(1)"); + + b.Property("EnforceMedicalClearance") + .HasColumnType("tinyint(1)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("UpdatedBy") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.ToTable("DriverComplianceConfigs"); + + b.HasData( + new + { + Id = new Guid("64000000-0000-0000-0000-000000000001"), + EnforceLicenseExpiry = true, + EnforceMedicalClearance = true, + UpdatedAt = new DateTime(2026, 8, 20, 0, 0, 0, 0, DateTimeKind.Unspecified), + UpdatedBy = new Guid("29000000-0000-0000-0000-000000000001") + }); + }); + + modelBuilder.Entity("Distribution.Models.DriverProfile", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("FK_UserId") + .HasColumnType("char(36)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("LicenseExpiry") + .HasColumnType("datetime(6)"); + + b.Property("LicenseNumber") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("MedicalClearanceExpiry") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("FK_UserId") + .IsUnique(); + + b.ToTable("DriverProfiles"); + + b.HasData( + new + { + Id = new Guid("30000000-0000-0000-0000-000000000001"), + CreatedAt = new DateTime(2026, 8, 20, 0, 0, 0, 0, DateTimeKind.Unspecified), + FK_UserId = new Guid("29000000-0000-0000-0000-000000000004"), + IsActive = true, + LicenseExpiry = new DateTime(2027, 6, 30, 0, 0, 0, 0, DateTimeKind.Unspecified), + LicenseNumber = "B1234567", + MedicalClearanceExpiry = new DateTime(2027, 1, 31, 0, 0, 0, 0, DateTimeKind.Unspecified) + }, + new + { + Id = new Guid("30000000-0000-0000-0000-000000000002"), + CreatedAt = new DateTime(2026, 8, 20, 0, 0, 0, 0, DateTimeKind.Unspecified), + FK_UserId = new Guid("29000000-0000-0000-0000-000000000005"), + IsActive = true, + LicenseExpiry = new DateTime(2027, 9, 30, 0, 0, 0, 0, DateTimeKind.Unspecified), + LicenseNumber = "B7654321", + MedicalClearanceExpiry = new DateTime(2027, 3, 31, 0, 0, 0, 0, DateTimeKind.Unspecified) + }); + }); + + modelBuilder.Entity("Distribution.Models.GoodsReceipt", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("DiscountPercent") + .HasPrecision(5, 2) + .HasColumnType("decimal(5,2)"); + + b.Property("FK_PurchaseOrderId") + .HasColumnType("char(36)"); + + b.Property("FK_ReceivedByUserId") + .HasColumnType("char(36)"); + + b.Property("FK_SupplierId") + .HasColumnType("char(36)"); + + b.Property("FK_WarehouseId") + .HasColumnType("char(36)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("NbtPercent") + .HasPrecision(5, 2) + .HasColumnType("decimal(5,2)"); + + b.Property("ReceivedAt") + .HasColumnType("datetime(6)"); + + b.Property("VatPercent") + .HasPrecision(5, 2) + .HasColumnType("decimal(5,2)"); + + b.HasKey("Id"); + + b.HasIndex("FK_PurchaseOrderId"); + + b.HasIndex("FK_SupplierId"); + + b.HasIndex("FK_WarehouseId"); + + b.ToTable("GoodsReceipts"); + + b.HasData( + new + { + Id = new Guid("43000000-0000-0000-0000-000000000001"), + CreatedAt = new DateTime(2026, 8, 15, 0, 0, 0, 0, DateTimeKind.Unspecified), + DiscountPercent = 0m, + FK_PurchaseOrderId = new Guid("41000000-0000-0000-0000-000000000001"), + FK_ReceivedByUserId = new Guid("29000000-0000-0000-0000-000000000001"), + FK_WarehouseId = new Guid("27000000-0000-0000-0000-000000000001"), + IsActive = true, + NbtPercent = 0m, + ReceivedAt = new DateTime(2026, 8, 15, 0, 0, 0, 0, DateTimeKind.Unspecified), + VatPercent = 0m + }); + }); + + modelBuilder.Entity("Distribution.Models.GoodsReceiptItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("AcceptedQuantity") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("DiscountPercent") + .HasPrecision(5, 2) + .HasColumnType("decimal(5,2)"); + + b.Property("FK_GoodsReceiptId") + .HasColumnType("char(36)"); + + b.Property("FK_ItemId") + .HasColumnType("char(36)"); + + b.Property("FK_PurchaseOrderItemId") + .HasColumnType("char(36)"); + + b.Property("FreeQuantity") + .HasColumnType("int"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("NbtPercent") + .HasPrecision(5, 2) + .HasColumnType("decimal(5,2)"); + + b.Property("RejectedQuantity") + .HasColumnType("int"); + + b.Property("UnitCost") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("VatPercent") + .HasPrecision(5, 2) + .HasColumnType("decimal(5,2)"); + + b.HasKey("Id"); + + b.HasIndex("FK_GoodsReceiptId"); + + b.HasIndex("FK_ItemId"); + + b.HasIndex("FK_PurchaseOrderItemId"); + + b.ToTable("GoodsReceiptItems"); + + b.HasData( + new + { + Id = new Guid("44000000-0000-0000-0000-000000000001"), + AcceptedQuantity = 200, + CreatedAt = new DateTime(2026, 8, 15, 0, 0, 0, 0, DateTimeKind.Unspecified), + DiscountPercent = 0m, + FK_GoodsReceiptId = new Guid("43000000-0000-0000-0000-000000000001"), + FK_PurchaseOrderItemId = new Guid("42000000-0000-0000-0000-000000000001"), + FreeQuantity = 0, + IsActive = true, + NbtPercent = 0m, + RejectedQuantity = 0, + UnitCost = 0m, + VatPercent = 0m + }, + new + { + Id = new Guid("44000000-0000-0000-0000-000000000002"), + AcceptedQuantity = 98, + CreatedAt = new DateTime(2026, 8, 15, 0, 0, 0, 0, DateTimeKind.Unspecified), + DiscountPercent = 0m, + FK_GoodsReceiptId = new Guid("43000000-0000-0000-0000-000000000001"), + FK_PurchaseOrderItemId = new Guid("42000000-0000-0000-0000-000000000002"), + FreeQuantity = 0, + IsActive = true, + NbtPercent = 0m, + RejectedQuantity = 2, + UnitCost = 0m, + VatPercent = 0m + }); + }); + + modelBuilder.Entity("Distribution.Models.GoodsReceiptItemBatch", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("FK_GoodsReceiptItemId") + .HasColumnType("char(36)"); + + b.Property("FK_ItemBatchId") + .HasColumnType("char(36)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("Quantity") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("FK_GoodsReceiptItemId"); + + b.HasIndex("FK_ItemBatchId"); + + b.ToTable("GoodsReceiptItemBatches"); + }); + + modelBuilder.Entity("Distribution.Models.GoodsReceiptItemSerial", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("FK_GoodsReceiptItemId") + .HasColumnType("char(36)"); + + b.Property("FK_ItemSerialNumberId") + .HasColumnType("char(36)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.HasKey("Id"); + + b.HasIndex("FK_GoodsReceiptItemId"); + + b.HasIndex("FK_ItemSerialNumberId"); + + b.ToTable("GoodsReceiptItemSerials"); + }); + + modelBuilder.Entity("Distribution.Models.Invoice", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .HasColumnType("char(36)"); + + b.Property("FK_ShopPOId") + .HasColumnType("char(36)"); + + b.Property("FinalizedAt") + .HasColumnType("datetime(6)"); + + b.Property("FinalizedBy") + .HasColumnType("char(36)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("PaymentStatus") + .HasColumnType("int"); + + b.Property("PrintedAt") + .HasColumnType("datetime(6)"); + + b.Property("PrintedBy") + .HasColumnType("char(36)"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("TotalAmount") + .HasColumnType("decimal(65,30)"); + + b.HasKey("Id"); + + b.HasIndex("FK_ShopPOId"); + + b.ToTable("Invoices"); + + b.HasData( + new + { + Id = new Guid("34500000-0000-0000-0000-000000000001"), + CreatedAt = new DateTime(2026, 8, 25, 8, 30, 0, 0, DateTimeKind.Unspecified), + CreatedBy = new Guid("29000000-0000-0000-0000-000000000002"), + FK_ShopPOId = new Guid("33000000-0000-0000-0000-000000000001"), + FinalizedAt = new DateTime(2026, 8, 25, 9, 0, 0, 0, DateTimeKind.Unspecified), + FinalizedBy = new Guid("29000000-0000-0000-0000-000000000001"), + IsActive = true, + PaymentStatus = 2, + PrintedAt = new DateTime(2026, 8, 25, 8, 45, 0, 0, DateTimeKind.Unspecified), + PrintedBy = new Guid("29000000-0000-0000-0000-000000000002"), + Status = 2, + TotalAmount = 3000m + }, + new + { + Id = new Guid("34500000-0000-0000-0000-000000000002"), + CreatedAt = new DateTime(2026, 8, 31, 9, 0, 0, 0, DateTimeKind.Unspecified), + CreatedBy = new Guid("29000000-0000-0000-0000-000000000003"), + FK_ShopPOId = new Guid("33000000-0000-0000-0000-000000000003"), + IsActive = true, + PaymentStatus = 1, + PrintedAt = new DateTime(2026, 8, 31, 9, 15, 0, 0, DateTimeKind.Unspecified), + PrintedBy = new Guid("29000000-0000-0000-0000-000000000003"), + Status = 1, + TotalAmount = 2400m + }); + }); + + modelBuilder.Entity("Distribution.Models.Item", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("AllowCustomWarrantyPeriod") + .HasColumnType("tinyint(1)"); + + b.Property("CostPrice") + .HasColumnType("decimal(65,30)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Description") + .HasColumnType("longtext"); + + b.Property("ExternalErpItemId") + .HasColumnType("int"); + + b.Property("FK_BaseUomId") + .HasColumnType("char(36)"); + + b.Property("FK_BrandId") + .HasColumnType("char(36)"); + + b.Property("FK_CategoryId") + .HasColumnType("char(36)"); + + b.Property("FK_SubCategoryId") + .HasColumnType("char(36)"); + + b.Property("HasWarranty") + .HasColumnType("tinyint(1)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("LastSyncedAt") + .HasColumnType("datetime(6)"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("ReorderLevel") + .HasColumnType("decimal(65,30)"); + + b.Property("SalePrice") + .HasColumnType("decimal(65,30)"); + + b.Property("Sku") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("SyncSource") + .HasColumnType("int"); + + b.Property("TracksBatches") + .HasColumnType("tinyint(1)"); + + b.Property("TracksExpiryDate") + .HasColumnType("tinyint(1)"); + + b.Property("TracksSerialNumbers") + .HasColumnType("tinyint(1)"); + + b.Property("WarrantyPeriodDays") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("FK_BaseUomId"); + + b.HasIndex("FK_BrandId"); + + b.HasIndex("FK_CategoryId"); + + b.HasIndex("FK_SubCategoryId"); + + b.ToTable("Items"); + + b.HasData( + new + { + Id = new Guid("23000000-0000-0000-0000-000000000001"), + AllowCustomWarrantyPeriod = false, + CreatedAt = new DateTime(2026, 8, 20, 0, 0, 0, 0, DateTimeKind.Unspecified), + Description = "Coca-Cola 500ml bottle", + FK_BaseUomId = new Guid("22000000-0000-0000-0000-000000000001"), + FK_BrandId = new Guid("21000000-0000-0000-0000-000000000001"), + FK_CategoryId = new Guid("20000000-0000-0000-0000-000000000001"), + HasWarranty = false, + IsActive = true, + Name = "Coca-Cola 500ml", + SalePrice = 60m, + Sku = "BEV-COKE-500", + SyncSource = 0, + TracksBatches = false, + TracksExpiryDate = false, + TracksSerialNumbers = false + }, + new + { + Id = new Guid("23000000-0000-0000-0000-000000000002"), + AllowCustomWarrantyPeriod = false, + CreatedAt = new DateTime(2026, 8, 20, 0, 0, 0, 0, DateTimeKind.Unspecified), + Description = "Coca-Cola 1.5L bottle", + FK_BaseUomId = new Guid("22000000-0000-0000-0000-000000000001"), + FK_BrandId = new Guid("21000000-0000-0000-0000-000000000001"), + FK_CategoryId = new Guid("20000000-0000-0000-0000-000000000001"), + HasWarranty = false, + IsActive = true, + Name = "Coca-Cola 1.5L", + SalePrice = 120m, + Sku = "BEV-COKE-1500", + SyncSource = 0, + TracksBatches = false, + TracksExpiryDate = false, + TracksSerialNumbers = false + }, + new + { + Id = new Guid("23000000-0000-0000-0000-000000000003"), + AllowCustomWarrantyPeriod = false, + CreatedAt = new DateTime(2026, 8, 20, 0, 0, 0, 0, DateTimeKind.Unspecified), + Description = "Lays Classic Salted 52g pack", + FK_BaseUomId = new Guid("22000000-0000-0000-0000-000000000001"), + FK_BrandId = new Guid("21000000-0000-0000-0000-000000000002"), + FK_CategoryId = new Guid("20000000-0000-0000-0000-000000000002"), + HasWarranty = false, + IsActive = true, + Name = "Lays Classic 52g", + SalePrice = 50m, + Sku = "SNK-LAYS-52", + SyncSource = 0, + TracksBatches = false, + TracksExpiryDate = false, + TracksSerialNumbers = false + }, + new + { + Id = new Guid("23000000-0000-0000-0000-000000000004"), + AllowCustomWarrantyPeriod = false, + CreatedAt = new DateTime(2026, 8, 20, 0, 0, 0, 0, DateTimeKind.Unspecified), + Description = "Lays Family Pack 150g", + FK_BaseUomId = new Guid("22000000-0000-0000-0000-000000000001"), + FK_BrandId = new Guid("21000000-0000-0000-0000-000000000002"), + FK_CategoryId = new Guid("20000000-0000-0000-0000-000000000002"), + HasWarranty = false, + IsActive = true, + Name = "Lays Family Pack 150g", + SalePrice = 150m, + Sku = "SNK-LAYS-FAM", + SyncSource = 0, + TracksBatches = false, + TracksExpiryDate = false, + TracksSerialNumbers = false + }); + }); + + modelBuilder.Entity("Distribution.Models.ItemBatch", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("BatchNumber") + .HasColumnType("varchar(255)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("ExpiryDate") + .HasColumnType("datetime(6)"); + + b.Property("FK_ItemId") + .HasColumnType("char(36)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("ManufactureDate") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("FK_ItemId", "BatchNumber"); + + b.ToTable("ItemBatches"); + }); + + modelBuilder.Entity("Distribution.Models.ItemSerialNumber", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CurrentLocationId") + .HasColumnType("char(36)"); + + b.Property("CurrentLocationType") + .HasColumnType("int"); + + b.Property("FK_ItemId") + .HasColumnType("char(36)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("ReceivedAt") + .HasColumnType("datetime(6)"); + + b.Property("SerialNumber") + .IsRequired() + .HasColumnType("varchar(255)"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("WarrantyExpiresAt") + .HasColumnType("datetime(6)"); + + b.Property("WarrantyPeriodDaysOverride") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("FK_ItemId", "SerialNumber") + .IsUnique(); + + b.ToTable("ItemSerialNumbers"); + }); + + modelBuilder.Entity("Distribution.Models.LoadingSheet", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("ConfirmedAt") + .HasColumnType("datetime(6)"); + + b.Property("ConfirmedBy") + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("FK_RideId") + .HasColumnType("char(36)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("PopulatedAt") + .HasColumnType("datetime(6)"); + + b.Property("PopulatedBy") + .HasColumnType("char(36)"); + + b.Property("Status") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("FK_RideId"); + + b.ToTable("LoadingSheets"); + + b.HasData( + new + { + Id = new Guid("35000000-0000-0000-0000-000000000001"), + ConfirmedAt = new DateTime(2026, 8, 25, 7, 45, 0, 0, DateTimeKind.Unspecified), + ConfirmedBy = new Guid("29000000-0000-0000-0000-000000000001"), + CreatedAt = new DateTime(2026, 8, 25, 7, 30, 0, 0, DateTimeKind.Unspecified), + FK_RideId = new Guid("31000000-0000-0000-0000-000000000001"), + IsActive = true, + PopulatedAt = new DateTime(2026, 8, 25, 7, 30, 0, 0, DateTimeKind.Unspecified), + PopulatedBy = new Guid("29000000-0000-0000-0000-000000000001"), + Status = 1 + }); + }); + + modelBuilder.Entity("Distribution.Models.LoadingSheetItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("FK_ItemId") + .HasColumnType("char(36)"); + + b.Property("FK_LoadingSheetId") + .HasColumnType("char(36)"); + + b.Property("FK_ShopPOItemId") + .HasColumnType("char(36)"); + + b.Property("FK_WarehouseId") + .HasColumnType("char(36)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("QuantityDispatched") + .HasColumnType("decimal(65,30)"); + + b.HasKey("Id"); + + b.HasIndex("FK_ItemId"); + + b.HasIndex("FK_LoadingSheetId"); + + b.HasIndex("FK_ShopPOItemId"); + + b.HasIndex("FK_WarehouseId"); + + b.ToTable("LoadingSheetItems"); + + b.HasData( + new + { + Id = new Guid("36000000-0000-0000-0000-000000000001"), + CreatedAt = new DateTime(2026, 8, 25, 7, 30, 0, 0, DateTimeKind.Unspecified), + FK_ItemId = new Guid("23000000-0000-0000-0000-000000000001"), + FK_LoadingSheetId = new Guid("35000000-0000-0000-0000-000000000001"), + FK_WarehouseId = new Guid("27000000-0000-0000-0000-000000000001"), + IsActive = true, + QuantityDispatched = 50m + }, + new + { + Id = new Guid("36000000-0000-0000-0000-000000000002"), + CreatedAt = new DateTime(2026, 8, 25, 7, 30, 0, 0, DateTimeKind.Unspecified), + FK_ItemId = new Guid("23000000-0000-0000-0000-000000000003"), + FK_LoadingSheetId = new Guid("35000000-0000-0000-0000-000000000001"), + FK_WarehouseId = new Guid("27000000-0000-0000-0000-000000000001"), + IsActive = true, + QuantityDispatched = 30m + }); + }); + + modelBuilder.Entity("Distribution.Models.LoadingSheetOrder", b => + { + b.Property("FK_LoadingSheetId") + .HasColumnType("char(36)"); + + b.Property("FK_ShopPOId") + .HasColumnType("char(36)"); + + b.HasKey("FK_LoadingSheetId", "FK_ShopPOId"); + + b.HasIndex("FK_ShopPOId"); + + b.ToTable("LoadingSheetOrders"); + }); + + modelBuilder.Entity("Distribution.Models.Payment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("Amount") + .HasColumnType("decimal(65,30)"); + + b.Property("ChequeDate") + .HasColumnType("datetime(6)"); + + b.Property("ChequeNumber") + .HasColumnType("longtext"); + + b.Property("CollectedAt") + .HasColumnType("datetime(6)"); + + b.Property("CollectedBy") + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("EntryMode") + .HasColumnType("int"); + + b.Property("FK_InvoiceId") + .HasColumnType("char(36)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("Method") + .HasColumnType("int"); + + b.Property("RecordedBy") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("FK_InvoiceId"); + + b.ToTable("Payments"); + + b.HasData( + new + { + Id = new Guid("47000000-0000-0000-0000-000000000001"), + Amount = 3000m, + CollectedAt = new DateTime(2026, 8, 25, 9, 30, 0, 0, DateTimeKind.Unspecified), + CollectedBy = new Guid("29000000-0000-0000-0000-000000000002"), + CreatedAt = new DateTime(2026, 8, 25, 9, 30, 0, 0, DateTimeKind.Unspecified), + EntryMode = 0, + FK_InvoiceId = new Guid("34500000-0000-0000-0000-000000000001"), + IsActive = true, + Method = 0 + }, + new + { + Id = new Guid("47000000-0000-0000-0000-000000000002"), + Amount = 1000m, + CollectedAt = new DateTime(2026, 8, 31, 9, 20, 0, 0, DateTimeKind.Unspecified), + CollectedBy = new Guid("29000000-0000-0000-0000-000000000003"), + CreatedAt = new DateTime(2026, 8, 31, 9, 20, 0, 0, DateTimeKind.Unspecified), + EntryMode = 0, + FK_InvoiceId = new Guid("34500000-0000-0000-0000-000000000002"), + IsActive = true, + Method = 0 + }, + new + { + Id = new Guid("47000000-0000-0000-0000-000000000003"), + Amount = 800m, + ChequeDate = new DateTime(2026, 8, 31, 0, 0, 0, 0, DateTimeKind.Unspecified), + ChequeNumber = "000456", + CollectedAt = new DateTime(2026, 8, 31, 11, 0, 0, 0, DateTimeKind.Unspecified), + CollectedBy = new Guid("29000000-0000-0000-0000-000000000003"), + CreatedAt = new DateTime(2026, 8, 31, 18, 0, 0, 0, DateTimeKind.Unspecified), + EntryMode = 1, + FK_InvoiceId = new Guid("34500000-0000-0000-0000-000000000002"), + IsActive = true, + Method = 1, + RecordedBy = new Guid("29000000-0000-0000-0000-000000000001") + }, + new + { + Id = new Guid("47000000-0000-0000-0000-000000000004"), + Amount = 300m, + CollectedAt = new DateTime(2026, 8, 31, 11, 5, 0, 0, DateTimeKind.Unspecified), + CollectedBy = new Guid("29000000-0000-0000-0000-000000000003"), + CreatedAt = new DateTime(2026, 8, 31, 11, 5, 0, 0, DateTimeKind.Unspecified), + EntryMode = 0, + FK_InvoiceId = new Guid("34500000-0000-0000-0000-000000000002"), + IsActive = true, + Method = 2 + }); + }); + + modelBuilder.Entity("Distribution.Models.Permission", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("Code") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Description") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.HasKey("Id"); + + b.ToTable("Permissions"); + }); + + modelBuilder.Entity("Distribution.Models.PriceList", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("EffectiveFrom") + .HasColumnType("datetime(6)"); + + b.Property("EffectiveTo") + .HasColumnType("datetime(6)"); + + b.Property("FK_RegionId") + .HasColumnType("char(36)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.HasIndex("FK_RegionId"); + + b.ToTable("PriceLists"); + + b.HasData( + new + { + Id = new Guid("39000000-0000-0000-0000-000000000001"), + CreatedAt = new DateTime(2026, 8, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), + EffectiveFrom = new DateTime(2026, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), + EffectiveTo = new DateTime(2026, 12, 31, 0, 0, 0, 0, DateTimeKind.Unspecified), + FK_RegionId = new Guid("24000000-0000-0000-0000-000000000001"), + IsActive = true, + Name = "Western Province Price List" + }); + }); + + modelBuilder.Entity("Distribution.Models.PriceListItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("AgencyMarginPercent") + .HasColumnType("decimal(65,30)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("FK_ItemId") + .HasColumnType("char(36)"); + + b.Property("FK_PriceListId") + .HasColumnType("char(36)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("MinPrice") + .HasColumnType("decimal(65,30)"); + + b.Property("Price") + .HasColumnType("decimal(65,30)"); + + b.Property("RetailPrice") + .HasColumnType("decimal(65,30)"); + + b.HasKey("Id"); + + b.HasIndex("FK_ItemId"); + + b.HasIndex("FK_PriceListId", "FK_ItemId") + .IsUnique(); + + b.ToTable("PriceListItems"); + + b.HasData( + new + { + Id = new Guid("40000000-0000-0000-0000-000000000001"), + CreatedAt = new DateTime(2026, 8, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), + FK_ItemId = new Guid("23000000-0000-0000-0000-000000000001"), + FK_PriceListId = new Guid("39000000-0000-0000-0000-000000000001"), + IsActive = true, + MinPrice = 55m, + Price = 60m + }, + new + { + Id = new Guid("40000000-0000-0000-0000-000000000002"), + CreatedAt = new DateTime(2026, 8, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), + FK_ItemId = new Guid("23000000-0000-0000-0000-000000000003"), + FK_PriceListId = new Guid("39000000-0000-0000-0000-000000000001"), + IsActive = true, + MinPrice = 45m, + Price = 50m + }); + }); + + modelBuilder.Entity("Distribution.Models.PurchaseOrder", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CancelReason") + .HasColumnType("longtext"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("FK_CreatedByUserId") + .HasColumnType("char(36)"); + + b.Property("FK_SupplierId") + .HasColumnType("char(36)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("Status") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("FK_SupplierId"); + + b.ToTable("PurchaseOrders"); + + b.HasData( + new + { + Id = new Guid("41000000-0000-0000-0000-000000000001"), + CreatedAt = new DateTime(2026, 8, 14, 0, 0, 0, 0, DateTimeKind.Unspecified), + FK_CreatedByUserId = new Guid("29000000-0000-0000-0000-000000000001"), + FK_SupplierId = new Guid("38000000-0000-0000-0000-000000000001"), + IsActive = true, + Status = 2 + }, + new + { + Id = new Guid("41000000-0000-0000-0000-000000000002"), + CreatedAt = new DateTime(2026, 8, 14, 0, 0, 0, 0, DateTimeKind.Unspecified), + FK_CreatedByUserId = new Guid("29000000-0000-0000-0000-000000000001"), + FK_SupplierId = new Guid("38000000-0000-0000-0000-000000000002"), + IsActive = true, + Status = 2 + }); + }); + + modelBuilder.Entity("Distribution.Models.PurchaseOrderItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CancelledQuantity") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("FK_ItemId") + .HasColumnType("char(36)"); + + b.Property("FK_PurchaseOrderId") + .HasColumnType("char(36)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("IsCancelled") + .HasColumnType("tinyint(1)"); + + b.Property("Quantity") + .HasColumnType("int"); + + b.Property("ReceivedQuantity") + .HasColumnType("int"); + + b.Property("UnitCost") + .HasColumnType("decimal(65,30)"); + + b.HasKey("Id"); + + b.HasIndex("FK_ItemId"); + + b.HasIndex("FK_PurchaseOrderId"); + + b.ToTable("PurchaseOrderItems"); + + b.HasData( + new + { + Id = new Guid("42000000-0000-0000-0000-000000000001"), + CancelledQuantity = 0, + CreatedAt = new DateTime(2026, 8, 14, 0, 0, 0, 0, DateTimeKind.Unspecified), + FK_ItemId = new Guid("23000000-0000-0000-0000-000000000001"), + FK_PurchaseOrderId = new Guid("41000000-0000-0000-0000-000000000001"), + IsActive = true, + IsCancelled = false, + Quantity = 200, + ReceivedQuantity = 200, + UnitCost = 40m + }, + new + { + Id = new Guid("42000000-0000-0000-0000-000000000002"), + CancelledQuantity = 0, + CreatedAt = new DateTime(2026, 8, 14, 0, 0, 0, 0, DateTimeKind.Unspecified), + FK_ItemId = new Guid("23000000-0000-0000-0000-000000000002"), + FK_PurchaseOrderId = new Guid("41000000-0000-0000-0000-000000000001"), + IsActive = true, + IsCancelled = false, + Quantity = 100, + ReceivedQuantity = 98, + UnitCost = 80m + }, + new + { + Id = new Guid("42000000-0000-0000-0000-000000000003"), + CancelledQuantity = 0, + CreatedAt = new DateTime(2026, 8, 14, 0, 0, 0, 0, DateTimeKind.Unspecified), + FK_ItemId = new Guid("23000000-0000-0000-0000-000000000003"), + FK_PurchaseOrderId = new Guid("41000000-0000-0000-0000-000000000002"), + IsActive = true, + IsCancelled = false, + Quantity = 150, + ReceivedQuantity = 150, + UnitCost = 35m + }); + }); + + modelBuilder.Entity("Distribution.Models.PurchaseReturn", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("FK_PurchaseOrderId") + .HasColumnType("char(36)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("Reason") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.HasIndex("FK_PurchaseOrderId"); + + b.ToTable("PurchaseReturns"); + + b.HasData( + new + { + Id = new Guid("45000000-0000-0000-0000-000000000001"), + CreatedAt = new DateTime(2026, 8, 16, 0, 0, 0, 0, DateTimeKind.Unspecified), + FK_PurchaseOrderId = new Guid("41000000-0000-0000-0000-000000000002"), + IsActive = true, + Reason = "Damaged in transit" + }); + }); + + modelBuilder.Entity("Distribution.Models.PurchaseReturnItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("FK_ItemId") + .HasColumnType("char(36)"); + + b.Property("FK_PurchaseReturnId") + .HasColumnType("char(36)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("Quantity") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("FK_ItemId"); + + b.HasIndex("FK_PurchaseReturnId"); + + b.ToTable("PurchaseReturnItems"); + + b.HasData( + new + { + Id = new Guid("46000000-0000-0000-0000-000000000001"), + CreatedAt = new DateTime(2026, 8, 16, 0, 0, 0, 0, DateTimeKind.Unspecified), + FK_ItemId = new Guid("23000000-0000-0000-0000-000000000003"), + FK_PurchaseReturnId = new Guid("45000000-0000-0000-0000-000000000001"), + IsActive = true, + Quantity = 10 + }); + }); + + modelBuilder.Entity("Distribution.Models.Region", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.HasIndex("Code") + .IsUnique(); + + b.ToTable("Regions"); + + b.HasData( + new + { + Id = new Guid("24000000-0000-0000-0000-000000000001"), + Code = "REG-0001", + CreatedAt = new DateTime(2026, 8, 20, 0, 0, 0, 0, DateTimeKind.Unspecified), + IsActive = true, + Name = "Western Province" + }, + new + { + Id = new Guid("24000000-0000-0000-0000-000000000002"), + Code = "REG-0002", + CreatedAt = new DateTime(2026, 8, 20, 0, 0, 0, 0, DateTimeKind.Unspecified), + IsActive = true, + Name = "Central Province" + }); + }); + + modelBuilder.Entity("Distribution.Models.RegionAgencyMap", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("FK_AgencyUserId") + .HasColumnType("char(36)"); + + b.Property("FK_RegionId") + .HasColumnType("char(36)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.HasKey("Id"); + + b.HasIndex("FK_AgencyUserId"); + + b.HasIndex("FK_RegionId", "FK_AgencyUserId") + .IsUnique(); + + b.ToTable("RegionAgencyMaps"); + + b.HasData( + new + { + Id = new Guid("57000000-0000-0000-0000-000000000001"), + CreatedAt = new DateTime(2026, 8, 20, 0, 0, 0, 0, DateTimeKind.Unspecified), + FK_AgencyUserId = new Guid("70000000-0000-0000-0000-000000000001"), + FK_RegionId = new Guid("24000000-0000-0000-0000-000000000001"), + IsActive = true + }); + }); + + modelBuilder.Entity("Distribution.Models.RegionalBudget", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("BudgetAmount") + .HasColumnType("decimal(65,30)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("FK_RegionId") + .HasColumnType("char(36)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("Period") + .IsRequired() + .HasColumnType("varchar(255)"); + + b.HasKey("Id"); + + b.HasIndex("FK_RegionId", "Period") + .IsUnique(); + + b.ToTable("RegionalBudgets"); + + b.HasData( + new + { + Id = new Guid("58000000-0000-0000-0000-000000000001"), + BudgetAmount = 200000m, + CreatedAt = new DateTime(2026, 8, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), + FK_RegionId = new Guid("24000000-0000-0000-0000-000000000001"), + IsActive = true, + Period = "2026-08" + }); + }); + + modelBuilder.Entity("Distribution.Models.RegionalTarget", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("FK_RegionId") + .HasColumnType("char(36)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("Period") + .IsRequired() + .HasColumnType("varchar(255)"); + + b.Property("TargetValue") + .HasColumnType("decimal(65,30)"); + + b.HasKey("Id"); + + b.HasIndex("FK_RegionId", "Period") + .IsUnique(); + + b.ToTable("RegionalTargets"); + + b.HasData( + new + { + Id = new Guid("59000000-0000-0000-0000-000000000001"), + CreatedAt = new DateTime(2026, 8, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), + FK_RegionId = new Guid("24000000-0000-0000-0000-000000000001"), + IsActive = true, + Period = "2026-08", + TargetValue = 500000m + }); + }); + + modelBuilder.Entity("Distribution.Models.RegionalTaxRule", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("FK_RegionId") + .HasColumnType("char(36)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("RuleConfig") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.HasIndex("FK_RegionId") + .IsUnique(); + + b.ToTable("RegionalTaxRules"); + + b.HasData( + new + { + Id = new Guid("60000000-0000-0000-0000-000000000001"), + CreatedAt = new DateTime(2026, 8, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), + FK_RegionId = new Guid("24000000-0000-0000-0000-000000000001"), + IsActive = true, + RuleConfig = "{\"rate\": 0.15}" + }); + }); + + modelBuilder.Entity("Distribution.Models.Role", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("Level") + .HasColumnType("int"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.ToTable("Roles"); + + b.HasData( + new + { + Id = new Guid("10000000-0000-0000-0000-000000000001"), + CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), + IsActive = true, + Level = 10, + Name = "SalesRep" + }, + new + { + Id = new Guid("10000000-0000-0000-0000-000000000002"), + CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), + IsActive = true, + Level = 20, + Name = "Supervisor" + }, + new + { + Id = new Guid("10000000-0000-0000-0000-000000000003"), + CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), + IsActive = true, + Level = 30, + Name = "AreaManager" + }, + new + { + Id = new Guid("10000000-0000-0000-0000-000000000004"), + CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), + IsActive = true, + Level = 40, + Name = "AreaSupervisor" + }, + new + { + Id = new Guid("10000000-0000-0000-0000-000000000005"), + CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), + IsActive = true, + Level = 50, + Name = "RegionalManager" + }, + new + { + Id = new Guid("10000000-0000-0000-0000-000000000006"), + CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), + IsActive = true, + Level = 60, + Name = "RegionalAgency" + }); + }); + + modelBuilder.Entity("Distribution.Models.RolePermission", b => + { + b.Property("FK_RoleId") + .HasColumnType("char(36)"); + + b.Property("FK_PermissionId") + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.HasKey("FK_RoleId", "FK_PermissionId"); + + b.HasIndex("FK_PermissionId"); + + b.ToTable("RolePermissions"); + }); + + modelBuilder.Entity("Distribution.Models.Route", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("EndPoint") + .HasColumnType("longtext"); + + b.Property("FK_OwnerUserId") + .HasColumnType("char(36)"); + + b.Property("FK_RegionId") + .HasColumnType("char(36)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("Length") + .HasColumnType("decimal(65,30)"); + + b.Property("RouteName") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("StartPoint") + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.HasIndex("FK_OwnerUserId"); + + b.HasIndex("FK_RegionId"); + + b.ToTable("Routes"); + + b.HasData( + new + { + Id = new Guid("25000000-0000-0000-0000-000000000001"), + CreatedAt = new DateTime(2026, 8, 20, 0, 0, 0, 0, DateTimeKind.Unspecified), + EndPoint = "Dehiwala", + FK_OwnerUserId = new Guid("29000000-0000-0000-0000-000000000002"), + FK_RegionId = new Guid("24000000-0000-0000-0000-000000000001"), + IsActive = true, + Length = 18.5m, + RouteName = "Colombo City Route", + StartPoint = "Colombo Fort" + }, + new + { + Id = new Guid("25000000-0000-0000-0000-000000000002"), + CreatedAt = new DateTime(2026, 8, 20, 0, 0, 0, 0, DateTimeKind.Unspecified), + EndPoint = "Negombo", + FK_OwnerUserId = new Guid("29000000-0000-0000-0000-000000000002"), + FK_RegionId = new Guid("24000000-0000-0000-0000-000000000001"), + IsActive = true, + Length = 32.0m, + RouteName = "Negombo Coastal Route", + StartPoint = "Wattala" + }, + new + { + Id = new Guid("25000000-0000-0000-0000-000000000003"), + CreatedAt = new DateTime(2026, 8, 20, 0, 0, 0, 0, DateTimeKind.Unspecified), + EndPoint = "Peradeniya", + FK_OwnerUserId = new Guid("29000000-0000-0000-0000-000000000003"), + FK_RegionId = new Guid("24000000-0000-0000-0000-000000000002"), + IsActive = true, + Length = 12.0m, + RouteName = "Kandy Town Route", + StartPoint = "Kandy Lake" + }, + new + { + Id = new Guid("25000000-0000-0000-0000-000000000004"), + CreatedAt = new DateTime(2026, 8, 20, 0, 0, 0, 0, DateTimeKind.Unspecified), + EndPoint = "Matale", + FK_OwnerUserId = new Guid("29000000-0000-0000-0000-000000000003"), + FK_RegionId = new Guid("24000000-0000-0000-0000-000000000002"), + IsActive = true, + Length = 26.0m, + RouteName = "Matale Route", + StartPoint = "Kandy" + }); + }); + + modelBuilder.Entity("Distribution.Models.RouteVisitLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CheckInAt") + .HasColumnType("datetime(6)"); + + b.Property("CheckInLatitude") + .HasColumnType("decimal(65,30)"); + + b.Property("CheckInLongitude") + .HasColumnType("decimal(65,30)"); + + b.Property("CheckOutAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("FK_RouteId") + .HasColumnType("char(36)"); + + b.Property("FK_StoreId") + .HasColumnType("char(36)"); + + b.Property("FK_UserId") + .HasColumnType("char(36)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("IsDeviationFlagged") + .HasColumnType("tinyint(1)"); + + b.HasKey("Id"); + + b.HasIndex("FK_RouteId"); + + b.HasIndex("FK_StoreId"); + + b.HasIndex("FK_UserId"); + + b.ToTable("RouteVisitLogs"); + + b.HasData( + new + { + Id = new Guid("66000000-0000-0000-0000-000000000001"), + CheckInAt = new DateTime(2026, 8, 25, 9, 0, 0, 0, DateTimeKind.Unspecified), + CheckInLatitude = 6.9344m, + CheckInLongitude = 79.8428m, + CheckOutAt = new DateTime(2026, 8, 25, 9, 25, 0, 0, DateTimeKind.Unspecified), + CreatedAt = new DateTime(2026, 8, 25, 9, 0, 0, 0, DateTimeKind.Unspecified), + FK_RouteId = new Guid("25000000-0000-0000-0000-000000000001"), + FK_StoreId = new Guid("26000000-0000-0000-0000-000000000001"), + FK_UserId = new Guid("29000000-0000-0000-0000-000000000002"), + IsActive = true, + IsDeviationFlagged = false + }, + new + { + Id = new Guid("66000000-0000-0000-0000-000000000002"), + CheckInAt = new DateTime(2026, 8, 25, 10, 0, 0, 0, DateTimeKind.Unspecified), + CheckInLatitude = 6.9000m, + CheckInLongitude = 79.8654m, + CreatedAt = new DateTime(2026, 8, 25, 10, 0, 0, 0, DateTimeKind.Unspecified), + FK_RouteId = new Guid("25000000-0000-0000-0000-000000000001"), + FK_StoreId = new Guid("26000000-0000-0000-0000-000000000002"), + FK_UserId = new Guid("29000000-0000-0000-0000-000000000002"), + IsActive = true, + IsDeviationFlagged = true + }); + }); + + modelBuilder.Entity("Distribution.Models.SaleReturn", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedByUserId") + .HasColumnType("char(36)"); + + b.Property("FK_ShopPOId") + .HasColumnType("char(36)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("Mode") + .HasColumnType("int"); + + b.Property("TotalCreditAmount") + .HasColumnType("decimal(65,30)"); + + b.HasKey("Id"); + + b.HasIndex("CreatedByUserId"); + + b.HasIndex("FK_ShopPOId"); + + b.ToTable("SaleReturns"); + + b.HasData( + new + { + Id = new Guid("51000000-0000-0000-0000-000000000001"), + CreatedAt = new DateTime(2026, 8, 26, 0, 0, 0, 0, DateTimeKind.Unspecified), + CreatedByUserId = new Guid("29000000-0000-0000-0000-000000000002"), + FK_ShopPOId = new Guid("33000000-0000-0000-0000-000000000001"), + IsActive = true, + Mode = 0, + TotalCreditAmount = 300m + }); + }); + + modelBuilder.Entity("Distribution.Models.SaleReturnItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("FK_ItemId") + .HasColumnType("char(36)"); + + b.Property("FK_SaleReturnId") + .HasColumnType("char(36)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("Quantity") + .HasColumnType("decimal(65,30)"); + + b.Property("ReasonCode") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasColumnType("decimal(65,30)"); + + b.HasKey("Id"); + + b.HasIndex("FK_ItemId"); + + b.HasIndex("FK_SaleReturnId"); + + b.ToTable("SaleReturnItems"); + + b.HasData( + new + { + Id = new Guid("52000000-0000-0000-0000-000000000001"), + CreatedAt = new DateTime(2026, 8, 26, 0, 0, 0, 0, DateTimeKind.Unspecified), + FK_ItemId = new Guid("23000000-0000-0000-0000-000000000001"), + FK_SaleReturnId = new Guid("51000000-0000-0000-0000-000000000001"), + IsActive = true, + Quantity = 5m, + ReasonCode = 1, + UnitPrice = 60m + }); + }); + + modelBuilder.Entity("Distribution.Models.SaleRoute", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.HasKey("Id"); + + b.ToTable("SaleRoutes"); + + b.HasData( + new + { + Id = new Guid("69000000-0000-0000-0000-000000000001"), + CreatedAt = new DateTime(2026, 8, 20, 0, 0, 0, 0, DateTimeKind.Unspecified), + IsActive = true + }, + new + { + Id = new Guid("69000000-0000-0000-0000-000000000002"), + CreatedAt = new DateTime(2026, 8, 20, 0, 0, 0, 0, DateTimeKind.Unspecified), + IsActive = true + }); + }); + + modelBuilder.Entity("Distribution.Models.Scheme", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("AgentLimitExempt") + .HasColumnType("tinyint(1)"); + + b.Property("BaselineExpectedSales") + .HasColumnType("decimal(65,30)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("DateRangeFrom") + .HasColumnType("datetime(6)"); + + b.Property("DateRangeTo") + .HasColumnType("datetime(6)"); + + b.Property("EligibleSegment") + .HasColumnType("longtext"); + + b.Property("FK_FreeItemId") + .HasColumnType("char(36)"); + + b.Property("FK_RegionId") + .HasColumnType("char(36)"); + + b.Property("FK_TriggerItemId") + .HasColumnType("char(36)"); + + b.Property("FreeQty") + .HasColumnType("decimal(65,30)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("RegionId") + .HasColumnType("char(36)"); + + b.Property("RuleConfig") + .HasColumnType("longtext"); + + b.Property("RuleType") + .HasColumnType("int"); + + b.Property("TriggerQty") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("FK_FreeItemId"); + + b.HasIndex("FK_TriggerItemId"); + + b.HasIndex("RegionId"); + + b.ToTable("Schemes"); + + b.HasData( + new + { + Id = new Guid("48000000-0000-0000-0000-000000000001"), + AgentLimitExempt = false, + BaselineExpectedSales = 500000m, + CreatedAt = new DateTime(2026, 8, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), + DateRangeFrom = new DateTime(2026, 8, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), + DateRangeTo = new DateTime(2026, 12, 31, 0, 0, 0, 0, DateTimeKind.Unspecified), + FK_FreeItemId = new Guid("23000000-0000-0000-0000-000000000001"), + FK_RegionId = new Guid("24000000-0000-0000-0000-000000000001"), + FK_TriggerItemId = new Guid("23000000-0000-0000-0000-000000000001"), + FreeQty = 1m, + IsActive = true, + Name = "Buy 10 Coke Get 1 Free", + RuleType = 0, + TriggerQty = 10 + }, + new + { + Id = new Guid("48000000-0000-0000-0000-000000000002"), + AgentLimitExempt = false, + CreatedAt = new DateTime(2026, 8, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), + DateRangeFrom = new DateTime(2026, 8, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), + DateRangeTo = new DateTime(2026, 12, 31, 0, 0, 0, 0, DateTimeKind.Unspecified), + FK_FreeItemId = new Guid("23000000-0000-0000-0000-000000000004"), + FK_RegionId = new Guid("24000000-0000-0000-0000-000000000002"), + FreeQty = 1m, + IsActive = true, + Name = "Snack Combo Bundle", + RuleType = 3 + }); + }); + + modelBuilder.Entity("Distribution.Models.SchemeBundleComponent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("FK_ItemId") + .HasColumnType("char(36)"); + + b.Property("FK_SchemeId") + .HasColumnType("char(36)"); + + b.Property("Qty") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("FK_ItemId"); + + b.HasIndex("FK_SchemeId", "FK_ItemId") + .IsUnique(); + + b.ToTable("SchemeBundleComponents"); + + b.HasData( + new + { + Id = new Guid("49000000-0000-0000-0000-000000000001"), + FK_ItemId = new Guid("23000000-0000-0000-0000-000000000003"), + FK_SchemeId = new Guid("48000000-0000-0000-0000-000000000002"), + Qty = 5 + }); + }); + + modelBuilder.Entity("Distribution.Models.SchemeRedemption", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("DiscountAmount") + .HasColumnType("decimal(65,30)"); + + b.Property("FK_FreeItemId") + .HasColumnType("char(36)"); + + b.Property("FK_SchemeId") + .HasColumnType("char(36)"); + + b.Property("FK_ShopPOId") + .HasColumnType("char(36)"); + + b.Property("FieldInvoiceId") + .HasColumnType("char(36)"); + + b.Property("FreeItemCostValue") + .HasColumnType("decimal(65,30)"); + + b.Property("FreeQty") + .HasColumnType("decimal(65,30)"); + + b.Property("RedeemedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("FK_FreeItemId"); + + b.HasIndex("FK_SchemeId"); + + b.HasIndex("FK_ShopPOId"); + + b.ToTable("SchemeRedemptions"); + + b.HasData( + new + { + Id = new Guid("50000000-0000-0000-0000-000000000001"), + DiscountAmount = 0m, + FK_FreeItemId = new Guid("23000000-0000-0000-0000-000000000001"), + FK_SchemeId = new Guid("48000000-0000-0000-0000-000000000001"), + FK_ShopPOId = new Guid("33000000-0000-0000-0000-000000000001"), + FreeItemCostValue = 40m, + FreeQty = 1m, + RedeemedAt = new DateTime(2026, 8, 25, 9, 30, 0, 0, DateTimeKind.Unspecified) + }); + }); + + modelBuilder.Entity("Distribution.Models.ShopPO", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CancelReason") + .HasColumnType("longtext"); + + b.Property("CancelledAt") + .HasColumnType("datetime(6)"); + + b.Property("CancelledBy") + .HasColumnType("char(36)"); + + b.Property("ConfirmedAt") + .HasColumnType("datetime(6)"); + + b.Property("ConfirmedBy") + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("FK_StoreId") + .HasColumnType("char(36)"); + + b.Property("FulfilledAt") + .HasColumnType("datetime(6)"); + + b.Property("InvoiceId") + .HasColumnType("char(36)") + .HasColumnName("InvoiceId"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("RaisedById") + .HasColumnType("char(36)"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("TotalAmount") + .HasColumnType("decimal(65,30)"); + + b.HasKey("Id"); + + b.HasIndex("FK_StoreId"); + + b.ToTable("ShopPOs"); + + b.HasData( + new + { + Id = new Guid("33000000-0000-0000-0000-000000000001"), + ConfirmedAt = new DateTime(2026, 8, 25, 8, 30, 0, 0, DateTimeKind.Unspecified), + ConfirmedBy = new Guid("29000000-0000-0000-0000-000000000001"), + CreatedAt = new DateTime(2026, 8, 25, 9, 0, 0, 0, DateTimeKind.Unspecified), + FK_StoreId = new Guid("26000000-0000-0000-0000-000000000001"), + FulfilledAt = new DateTime(2026, 8, 25, 13, 0, 0, 0, DateTimeKind.Unspecified), + InvoiceId = new Guid("34500000-0000-0000-0000-000000000001"), + IsActive = true, + RaisedById = new Guid("29000000-0000-0000-0000-000000000002"), + Status = 2, + TotalAmount = 3000m + }, + new + { + Id = new Guid("33000000-0000-0000-0000-000000000002"), + CreatedAt = new DateTime(2026, 8, 30, 10, 0, 0, 0, DateTimeKind.Unspecified), + FK_StoreId = new Guid("26000000-0000-0000-0000-000000000005"), + IsActive = true, + RaisedById = new Guid("29000000-0000-0000-0000-000000000003"), + Status = 0, + TotalAmount = 1200m + }, + new + { + Id = new Guid("33000000-0000-0000-0000-000000000003"), + ConfirmedAt = new DateTime(2026, 8, 31, 8, 30, 0, 0, DateTimeKind.Unspecified), + ConfirmedBy = new Guid("29000000-0000-0000-0000-000000000001"), + CreatedAt = new DateTime(2026, 8, 31, 9, 0, 0, 0, DateTimeKind.Unspecified), + FK_StoreId = new Guid("26000000-0000-0000-0000-000000000005"), + FulfilledAt = new DateTime(2026, 8, 31, 13, 0, 0, 0, DateTimeKind.Unspecified), + InvoiceId = new Guid("34500000-0000-0000-0000-000000000002"), + IsActive = true, + RaisedById = new Guid("29000000-0000-0000-0000-000000000003"), + Status = 2, + TotalAmount = 2400m + }); + }); + + modelBuilder.Entity("Distribution.Models.ShopPOItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("FK_ItemId") + .HasColumnType("char(36)"); + + b.Property("FK_ShopPOId") + .HasColumnType("char(36)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("Quantity") + .HasColumnType("int"); + + b.Property("QuantityFulfilled") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasColumnType("decimal(65,30)"); + + b.HasKey("Id"); + + b.HasIndex("FK_ItemId"); + + b.HasIndex("FK_ShopPOId"); + + b.ToTable("ShopPOItems"); + + b.HasData( + new + { + Id = new Guid("34000000-0000-0000-0000-000000000001"), + CreatedAt = new DateTime(2026, 8, 25, 9, 0, 0, 0, DateTimeKind.Unspecified), + FK_ItemId = new Guid("23000000-0000-0000-0000-000000000001"), + FK_ShopPOId = new Guid("33000000-0000-0000-0000-000000000001"), + IsActive = true, + Quantity = 30, + QuantityFulfilled = 30, + UnitPrice = 60m + }, + new + { + Id = new Guid("34000000-0000-0000-0000-000000000002"), + CreatedAt = new DateTime(2026, 8, 25, 9, 0, 0, 0, DateTimeKind.Unspecified), + FK_ItemId = new Guid("23000000-0000-0000-0000-000000000003"), + FK_ShopPOId = new Guid("33000000-0000-0000-0000-000000000001"), + IsActive = true, + Quantity = 24, + QuantityFulfilled = 24, + UnitPrice = 50m + }, + new + { + Id = new Guid("34000000-0000-0000-0000-000000000003"), + CreatedAt = new DateTime(2026, 8, 30, 10, 0, 0, 0, DateTimeKind.Unspecified), + FK_ItemId = new Guid("23000000-0000-0000-0000-000000000002"), + FK_ShopPOId = new Guid("33000000-0000-0000-0000-000000000002"), + IsActive = true, + Quantity = 10, + QuantityFulfilled = 0, + UnitPrice = 120m + }, + new + { + Id = new Guid("34000000-0000-0000-0000-000000000004"), + CreatedAt = new DateTime(2026, 8, 31, 9, 0, 0, 0, DateTimeKind.Unspecified), + FK_ItemId = new Guid("23000000-0000-0000-0000-000000000002"), + FK_ShopPOId = new Guid("33000000-0000-0000-0000-000000000003"), + IsActive = true, + Quantity = 20, + QuantityFulfilled = 20, + UnitPrice = 120m + }); + }); + + modelBuilder.Entity("Distribution.Models.StockBalance", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("FK_ItemBatchId") + .HasColumnType("char(36)"); + + b.Property("FK_ItemId") + .HasColumnType("char(36)"); + + b.Property("FK_ItemSerialNumberId") + .HasColumnType("char(36)"); + + b.Property("LocationId") + .HasColumnType("char(36)"); + + b.Property("LocationType") + .HasColumnType("int"); + + b.Property("Quantity") + .HasColumnType("decimal(65,30)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("FK_ItemBatchId"); + + b.HasIndex("FK_ItemSerialNumberId"); + + b.HasIndex("FK_ItemId", "LocationId", "FK_ItemBatchId", "FK_ItemSerialNumberId") + .IsUnique(); + + b.ToTable("StockBalances"); + + b.HasData( + new + { + Id = new Guid("61000000-0000-0000-0000-000000000001"), + FK_ItemId = new Guid("23000000-0000-0000-0000-000000000001"), + LocationId = new Guid("27000000-0000-0000-0000-000000000001"), + LocationType = 0, + Quantity = 150m, + UpdatedAt = new DateTime(2026, 8, 25, 9, 30, 0, 0, DateTimeKind.Unspecified) + }, + new + { + Id = new Guid("61000000-0000-0000-0000-000000000002"), + FK_ItemId = new Guid("23000000-0000-0000-0000-000000000003"), + LocationId = new Guid("27000000-0000-0000-0000-000000000001"), + LocationType = 0, + Quantity = 90m, + UpdatedAt = new DateTime(2026, 8, 25, 9, 30, 0, 0, DateTimeKind.Unspecified) + }, + new + { + Id = new Guid("61000000-0000-0000-0000-000000000003"), + FK_ItemId = new Guid("23000000-0000-0000-0000-000000000001"), + LocationId = new Guid("28000000-0000-0000-0000-000000000001"), + LocationType = 1, + Quantity = 20m, + UpdatedAt = new DateTime(2026, 8, 25, 9, 30, 0, 0, DateTimeKind.Unspecified) + }); + }); + + modelBuilder.Entity("Distribution.Models.StockMovement", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedBy") + .HasColumnType("char(36)"); + + b.Property("FK_ItemBatchId") + .HasColumnType("char(36)"); + + b.Property("FK_ItemId") + .HasColumnType("char(36)"); + + b.Property("FK_ItemSerialNumberId") + .HasColumnType("char(36)"); + + b.Property("FromLocationId") + .HasColumnType("char(36)"); + + b.Property("FromLocationType") + .HasColumnType("int"); + + b.Property("Quantity") + .HasColumnType("decimal(65,30)"); + + b.Property("ReasonCode") + .HasColumnType("longtext"); + + b.Property("ReferenceId") + .HasColumnType("char(36)"); + + b.Property("ToLocationId") + .HasColumnType("char(36)"); + + b.Property("ToLocationType") + .HasColumnType("int"); + + b.Property("Type") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("FK_ItemBatchId"); + + b.HasIndex("FK_ItemId"); + + b.HasIndex("FK_ItemSerialNumberId"); + + b.ToTable("StockMovements"); + + b.HasData( + new + { + Id = new Guid("62000000-0000-0000-0000-000000000001"), + CreatedAt = new DateTime(2026, 8, 15, 0, 0, 0, 0, DateTimeKind.Unspecified), + CreatedBy = new Guid("29000000-0000-0000-0000-000000000001"), + FK_ItemId = new Guid("23000000-0000-0000-0000-000000000001"), + FromLocationId = new Guid("00000000-0000-0000-0000-000000000000"), + FromLocationType = 2, + Quantity = 200m, + ReferenceId = new Guid("43000000-0000-0000-0000-000000000001"), + ToLocationId = new Guid("27000000-0000-0000-0000-000000000001"), + ToLocationType = 0, + Type = 6 + }, + new + { + Id = new Guid("62000000-0000-0000-0000-000000000002"), + CreatedAt = new DateTime(2026, 8, 25, 7, 30, 0, 0, DateTimeKind.Unspecified), + CreatedBy = new Guid("29000000-0000-0000-0000-000000000001"), + FK_ItemId = new Guid("23000000-0000-0000-0000-000000000001"), + FromLocationId = new Guid("27000000-0000-0000-0000-000000000001"), + FromLocationType = 0, + Quantity = 50m, + ReferenceId = new Guid("35000000-0000-0000-0000-000000000001"), + ToLocationId = new Guid("28000000-0000-0000-0000-000000000001"), + ToLocationType = 1, + Type = 5 + }, + new + { + Id = new Guid("62000000-0000-0000-0000-000000000003"), + CreatedAt = new DateTime(2026, 8, 25, 9, 30, 0, 0, DateTimeKind.Unspecified), + CreatedBy = new Guid("29000000-0000-0000-0000-000000000002"), + FK_ItemId = new Guid("23000000-0000-0000-0000-000000000001"), + FromLocationId = new Guid("28000000-0000-0000-0000-000000000001"), + FromLocationType = 1, + Quantity = 30m, + ReferenceId = new Guid("32000000-0000-0000-0000-000000000001"), + ToLocationId = new Guid("00000000-0000-0000-0000-000000000000"), + ToLocationType = 2, + Type = 0 + }); + }); + + modelBuilder.Entity("Distribution.Models.Store", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("ContactNumber") + .HasColumnType("longtext"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreditAmount") + .HasColumnType("decimal(65,30)"); + + b.Property("CreditLimit") + .HasColumnType("decimal(65,30)"); + + b.Property("FK_RouteId") + .HasColumnType("char(36)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("Latitude") + .HasColumnType("decimal(65,30)"); + + b.Property("Longitude") + .HasColumnType("decimal(65,30)"); + + b.Property("OneTimeAmount") + .HasColumnType("decimal(65,30)"); + + b.Property("OutstandingBalance") + .HasColumnType("decimal(65,30)"); + + b.Property("OwnerName") + .HasColumnType("longtext"); + + b.Property("Segment") + .HasColumnType("longtext"); + + b.Property("StoreAddress") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("StoreName") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.HasIndex("FK_RouteId"); + + b.ToTable("Stores"); + + b.HasData( + new + { + Id = new Guid("26000000-0000-0000-0000-000000000001"), + CreatedAt = new DateTime(2026, 8, 20, 0, 0, 0, 0, DateTimeKind.Unspecified), + CreditAmount = 0m, + CreditLimit = 50000m, + FK_RouteId = new Guid("25000000-0000-0000-0000-000000000001"), + IsActive = true, + Latitude = 6.9344m, + Longitude = 79.8428m, + OneTimeAmount = 0m, + OutstandingBalance = 0m, + OwnerName = "Saman Kumara", + Segment = "Gold", + StoreAddress = "12 York Street, Colombo Fort", + StoreName = "Fort Mini Mart" + }, + new + { + Id = new Guid("26000000-0000-0000-0000-000000000002"), + CreatedAt = new DateTime(2026, 8, 20, 0, 0, 0, 0, DateTimeKind.Unspecified), + CreditAmount = 0m, + CreditLimit = 30000m, + FK_RouteId = new Guid("25000000-0000-0000-0000-000000000001"), + IsActive = true, + Latitude = 6.8508m, + Longitude = 79.8654m, + OneTimeAmount = 0m, + OutstandingBalance = 5000m, + OwnerName = "Nadeesha Silva", + Segment = "Silver", + StoreAddress = "45 Galle Road, Dehiwala", + StoreName = "Dehiwala Grocery" + }, + new + { + Id = new Guid("26000000-0000-0000-0000-000000000003"), + CreatedAt = new DateTime(2026, 8, 20, 0, 0, 0, 0, DateTimeKind.Unspecified), + CreditAmount = 0m, + CreditLimit = 40000m, + FK_RouteId = new Guid("25000000-0000-0000-0000-000000000002"), + IsActive = true, + Latitude = 6.9890m, + Longitude = 79.8912m, + OneTimeAmount = 0m, + OutstandingBalance = 1200m, + OwnerName = "Ruwan Gunasekara", + Segment = "Gold", + StoreAddress = "88 Negombo Road, Wattala", + StoreName = "Wattala Supermarket" + }, + new + { + Id = new Guid("26000000-0000-0000-0000-000000000004"), + CreatedAt = new DateTime(2026, 8, 20, 0, 0, 0, 0, DateTimeKind.Unspecified), + CreditAmount = 0m, + CreditLimit = 25000m, + FK_RouteId = new Guid("25000000-0000-0000-0000-000000000002"), + IsActive = true, + Latitude = 7.2094m, + Longitude = 79.8380m, + OneTimeAmount = 0m, + OutstandingBalance = 0m, + OwnerName = "Chamari Peiris", + StoreAddress = "5 Beach Road, Negombo", + StoreName = "Negombo Beach Store" + }, + new + { + Id = new Guid("26000000-0000-0000-0000-000000000005"), + CreatedAt = new DateTime(2026, 8, 20, 0, 0, 0, 0, DateTimeKind.Unspecified), + CreditAmount = 0m, + CreditLimit = 35000m, + FK_RouteId = new Guid("25000000-0000-0000-0000-000000000003"), + IsActive = true, + Latitude = 7.2906m, + Longitude = 80.6337m, + OneTimeAmount = 0m, + OutstandingBalance = 0m, + OwnerName = "Dinesh Wickramasinghe", + Segment = "Key Account", + StoreAddress = "3 Lake Road, Kandy", + StoreName = "Kandy Lake View Store" + }, + new + { + Id = new Guid("26000000-0000-0000-0000-000000000006"), + CreatedAt = new DateTime(2026, 8, 20, 0, 0, 0, 0, DateTimeKind.Unspecified), + CreditAmount = 0m, + CreditLimit = 20000m, + FK_RouteId = new Guid("25000000-0000-0000-0000-000000000004"), + IsActive = true, + Latitude = 7.4675m, + Longitude = 80.6234m, + OneTimeAmount = 0m, + OutstandingBalance = 0m, + OwnerName = "Ishara Bandara", + StoreAddress = "20 Main Street, Matale", + StoreName = "Matale Central Store" + }); + }); + + modelBuilder.Entity("Distribution.Models.SubCategory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("ExternalErpId") + .HasColumnType("int"); + + b.Property("FK_CategoryId") + .HasColumnType("char(36)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.HasIndex("FK_CategoryId"); + + b.ToTable("SubCategories"); + }); + + modelBuilder.Entity("Distribution.Models.Supplier", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("ContactInfo") + .HasColumnType("longtext"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("PerformanceScore") + .HasColumnType("decimal(65,30)"); + + b.HasKey("Id"); + + b.ToTable("Suppliers"); + + b.HasData( + new + { + Id = new Guid("38000000-0000-0000-0000-000000000001"), + ContactInfo = "+94 11 234 5678", + CreatedAt = new DateTime(2026, 8, 14, 0, 0, 0, 0, DateTimeKind.Unspecified), + IsActive = true, + Name = "Ceylon Beverages Ltd", + PerformanceScore = 4.2m + }, + new + { + Id = new Guid("38000000-0000-0000-0000-000000000002"), + ContactInfo = "+94 11 876 5432", + CreatedAt = new DateTime(2026, 8, 14, 0, 0, 0, 0, DateTimeKind.Unspecified), + IsActive = true, + Name = "Snack Foods (Pvt) Ltd" + }); + }); + + modelBuilder.Entity("Distribution.Models.SupplierClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Details") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("EvidenceUrl") + .HasColumnType("longtext"); + + b.Property("FK_GoodsReceiptId") + .HasColumnType("char(36)"); + + b.Property("FK_PurchaseOrderId") + .HasColumnType("char(36)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("RaisedByUserId") + .HasColumnType("char(36)"); + + b.Property("ResolutionAmount") + .HasColumnType("decimal(65,30)"); + + b.Property("ResolutionNotes") + .HasColumnType("longtext"); + + b.Property("ResolutionType") + .HasColumnType("int"); + + b.Property("ResolvedAt") + .HasColumnType("datetime(6)"); + + b.Property("ResolvedByUserId") + .HasColumnType("char(36)"); + + b.Property("Status") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("FK_GoodsReceiptId"); + + b.HasIndex("FK_PurchaseOrderId"); + + b.HasIndex("RaisedByUserId"); + + b.HasIndex("ResolvedByUserId"); + + b.ToTable("SupplierClaims"); + + b.HasData( + new + { + Id = new Guid("55000000-0000-0000-0000-000000000001"), + CreatedAt = new DateTime(2026, 8, 15, 0, 0, 0, 0, DateTimeKind.Unspecified), + Details = "2 units of Coke 1.5L received damaged", + FK_GoodsReceiptId = new Guid("43000000-0000-0000-0000-000000000001"), + FK_PurchaseOrderId = new Guid("41000000-0000-0000-0000-000000000001"), + IsActive = true, + RaisedByUserId = new Guid("29000000-0000-0000-0000-000000000001"), + ResolutionAmount = 160m, + ResolutionNotes = "Credited against next invoice", + ResolutionType = 1, + ResolvedAt = new DateTime(2026, 8, 16, 0, 0, 0, 0, DateTimeKind.Unspecified), + ResolvedByUserId = new Guid("29000000-0000-0000-0000-000000000006"), + Status = 1 + }); + }); + + modelBuilder.Entity("Distribution.Models.SupplierClaimItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("FK_ItemId") + .HasColumnType("char(36)"); + + b.Property("FK_SupplierClaimId") + .HasColumnType("char(36)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("IssueType") + .HasColumnType("int"); + + b.Property("Quantity") + .HasColumnType("decimal(65,30)"); + + b.HasKey("Id"); + + b.HasIndex("FK_ItemId"); + + b.HasIndex("FK_SupplierClaimId"); + + b.ToTable("SupplierClaimItems"); + + b.HasData( + new + { + Id = new Guid("56000000-0000-0000-0000-000000000001"), + CreatedAt = new DateTime(2026, 8, 15, 0, 0, 0, 0, DateTimeKind.Unspecified), + FK_ItemId = new Guid("23000000-0000-0000-0000-000000000002"), + FK_SupplierClaimId = new Guid("55000000-0000-0000-0000-000000000001"), + IsActive = true, + IssueType = 0, + Quantity = 2m + }); + }); + + modelBuilder.Entity("Distribution.Models.SysConfig", b => + { + b.Property("Key") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("UpdatedBy") + .HasColumnType("char(36)"); + + b.Property("Value") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Key"); + + b.ToTable("SysConfigs"); + + b.HasData( + new + { + Key = "InvoiceCreationMode", + UpdatedAt = new DateTime(2026, 9, 17, 0, 0, 0, 0, DateTimeKind.Unspecified), + Value = "ModeB" + }, + new + { + Key = "Feature.SerialNumberTracking", + UpdatedAt = new DateTime(2026, 10, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), + Value = "false" + }, + new + { + Key = "Feature.BatchTracking", + UpdatedAt = new DateTime(2026, 10, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), + Value = "false" + }, + new + { + Key = "Feature.ExpiryDateTracking", + UpdatedAt = new DateTime(2026, 10, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), + Value = "false" + }, + new + { + Key = "Feature.WarrantyTracking", + UpdatedAt = new DateTime(2026, 10, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), + Value = "false" + }, + new + { + Key = "Feature.CustomWarrantyOverride", + UpdatedAt = new DateTime(2026, 10, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), + Value = "false" + }, + new + { + Key = "Feature.DefaultWarrantyPeriodDays", + UpdatedAt = new DateTime(2026, 10, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), + Value = "0" + }); + }); + + modelBuilder.Entity("Distribution.Models.Uom", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("ExternalErpId") + .HasColumnType("int"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.ToTable("Uoms"); + + b.HasData( + new + { + Id = new Guid("22000000-0000-0000-0000-000000000001"), + CreatedAt = new DateTime(2026, 8, 20, 0, 0, 0, 0, DateTimeKind.Unspecified), + IsActive = true, + Name = "Piece" + }, + new + { + Id = new Guid("67000000-0000-0000-0000-000000000001"), + CreatedAt = new DateTime(2026, 8, 20, 0, 0, 0, 0, DateTimeKind.Unspecified), + IsActive = true, + Name = "Case" + }); + }); + + modelBuilder.Entity("Distribution.Models.UomConversion", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("FK_FromUomId") + .HasColumnType("char(36)"); + + b.Property("FK_ItemId") + .HasColumnType("char(36)"); + + b.Property("FK_ToUomId") + .HasColumnType("char(36)"); + + b.Property("Factor") + .HasColumnType("decimal(65,30)"); + + b.HasKey("Id"); + + b.HasIndex("FK_FromUomId"); + + b.HasIndex("FK_ItemId"); + + b.HasIndex("FK_ToUomId"); + + b.ToTable("UomConversions"); + + b.HasData( + new + { + Id = new Guid("67000000-0000-0000-0000-000000000002"), + FK_FromUomId = new Guid("67000000-0000-0000-0000-000000000001"), + FK_ItemId = new Guid("23000000-0000-0000-0000-000000000001"), + FK_ToUomId = new Guid("22000000-0000-0000-0000-000000000001"), + Factor = 24m + }); + }); + + modelBuilder.Entity("Distribution.Models.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("FK_ReportsToUserId") + .HasColumnType("char(36)"); + + b.Property("FK_RoleId") + .HasColumnType("char(36)"); + + b.Property("FullName") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("PasswordHash") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Username") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.HasIndex("FK_ReportsToUserId"); + + b.HasIndex("FK_RoleId"); + + b.ToTable("Users"); + + b.HasData( + new + { + Id = new Guid("29000000-0000-0000-0000-000000000001"), + CreatedAt = new DateTime(2026, 8, 20, 0, 0, 0, 0, DateTimeKind.Unspecified), + FK_RoleId = new Guid("10000000-0000-0000-0000-000000000002"), + FullName = "Sunil Fernando", + IsActive = true, + PasswordHash = "AQAAAAIAAYagAAAAEKGnq/U4j6uM9g5mFkxbUE8WU/AhnSNiwrKCiPAqGnzNcaMgZAF5Zal/rJUUYPZPpg==", + Username = "sunil.fernando" + }, + new + { + Id = new Guid("29000000-0000-0000-0000-000000000002"), + CreatedAt = new DateTime(2026, 8, 20, 0, 0, 0, 0, DateTimeKind.Unspecified), + FK_ReportsToUserId = new Guid("29000000-0000-0000-0000-000000000001"), + FK_RoleId = new Guid("10000000-0000-0000-0000-000000000001"), + FullName = "Nimal Perera", + IsActive = true, + PasswordHash = "AQAAAAIAAYagAAAAEKGnq/U4j6uM9g5mFkxbUE8WU/AhnSNiwrKCiPAqGnzNcaMgZAF5Zal/rJUUYPZPpg==", + Username = "nimal.perera" + }, + new + { + Id = new Guid("29000000-0000-0000-0000-000000000003"), + CreatedAt = new DateTime(2026, 8, 20, 0, 0, 0, 0, DateTimeKind.Unspecified), + FK_ReportsToUserId = new Guid("29000000-0000-0000-0000-000000000001"), + FK_RoleId = new Guid("10000000-0000-0000-0000-000000000001"), + FullName = "Kamal Silva", + IsActive = true, + PasswordHash = "AQAAAAIAAYagAAAAEKGnq/U4j6uM9g5mFkxbUE8WU/AhnSNiwrKCiPAqGnzNcaMgZAF5Zal/rJUUYPZPpg==", + Username = "kamal.silva" + }, + new + { + Id = new Guid("29000000-0000-0000-0000-000000000004"), + CreatedAt = new DateTime(2026, 8, 20, 0, 0, 0, 0, DateTimeKind.Unspecified), + FK_ReportsToUserId = new Guid("29000000-0000-0000-0000-000000000001"), + FK_RoleId = new Guid("10000000-0000-0000-0000-000000000001"), + FullName = "Ruwan Jayasuriya", + IsActive = true, + PasswordHash = "AQAAAAIAAYagAAAAEKGnq/U4j6uM9g5mFkxbUE8WU/AhnSNiwrKCiPAqGnzNcaMgZAF5Zal/rJUUYPZPpg==", + Username = "ruwan.jayasuriya" + }, + new + { + Id = new Guid("29000000-0000-0000-0000-000000000005"), + CreatedAt = new DateTime(2026, 8, 20, 0, 0, 0, 0, DateTimeKind.Unspecified), + FK_ReportsToUserId = new Guid("29000000-0000-0000-0000-000000000001"), + FK_RoleId = new Guid("10000000-0000-0000-0000-000000000001"), + FullName = "Priyantha Kumara", + IsActive = true, + PasswordHash = "AQAAAAIAAYagAAAAEKGnq/U4j6uM9g5mFkxbUE8WU/AhnSNiwrKCiPAqGnzNcaMgZAF5Zal/rJUUYPZPpg==", + Username = "priyantha.kumara" + }, + new + { + Id = new Guid("29000000-0000-0000-0000-000000000006"), + CreatedAt = new DateTime(2026, 8, 20, 0, 0, 0, 0, DateTimeKind.Unspecified), + FK_RoleId = new Guid("10000000-0000-0000-0000-000000000005"), + FullName = "Priya Rajapaksa", + IsActive = true, + PasswordHash = "AQAAAAIAAYagAAAAEKGnq/U4j6uM9g5mFkxbUE8WU/AhnSNiwrKCiPAqGnzNcaMgZAF5Zal/rJUUYPZPpg==", + Username = "priya.rajapaksa" + }, + new + { + Id = new Guid("70000000-0000-0000-0000-000000000001"), + CreatedAt = new DateTime(2026, 8, 20, 0, 0, 0, 0, DateTimeKind.Unspecified), + FK_RoleId = new Guid("10000000-0000-0000-0000-000000000006"), + FullName = "Channa De Silva", + IsActive = true, + PasswordHash = "AQAAAAIAAYagAAAAEKGnq/U4j6uM9g5mFkxbUE8WU/AhnSNiwrKCiPAqGnzNcaMgZAF5Zal/rJUUYPZPpg==", + Username = "channa.desilva" + }); + }); + + modelBuilder.Entity("Distribution.Models.UserRegionScope", b => + { + b.Property("FK_UserId") + .HasColumnType("char(36)"); + + b.Property("FK_RegionId") + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.HasKey("FK_UserId", "FK_RegionId"); + + b.HasIndex("FK_RegionId"); + + b.ToTable("UserRegionScopes"); + + b.HasData( + new + { + FK_UserId = new Guid("29000000-0000-0000-0000-000000000006"), + FK_RegionId = new Guid("24000000-0000-0000-0000-000000000001"), + CreatedAt = new DateTime(2026, 8, 20, 0, 0, 0, 0, DateTimeKind.Unspecified), + Id = new Guid("68000000-0000-0000-0000-000000000001"), + IsActive = true + }); + }); + + modelBuilder.Entity("Distribution.Models.UserRouteScope", b => + { + b.Property("FK_UserId") + .HasColumnType("char(36)"); + + b.Property("FK_RouteId") + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.HasKey("FK_UserId", "FK_RouteId"); + + b.HasIndex("FK_RouteId"); + + b.ToTable("UserRouteScopes"); + + b.HasData( + new + { + FK_UserId = new Guid("29000000-0000-0000-0000-000000000001"), + FK_RouteId = new Guid("25000000-0000-0000-0000-000000000001"), + CreatedAt = new DateTime(2026, 8, 20, 0, 0, 0, 0, DateTimeKind.Unspecified), + Id = new Guid("68000000-0000-0000-0000-000000000002"), + IsActive = true + }); + }); + + modelBuilder.Entity("Distribution.Models.UserStoreScope", b => + { + b.Property("FK_UserId") + .HasColumnType("char(36)"); + + b.Property("FK_StoreId") + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.HasKey("FK_UserId", "FK_StoreId"); + + b.HasIndex("FK_StoreId"); + + b.ToTable("UserStoreScopes"); + + b.HasData( + new + { + FK_UserId = new Guid("29000000-0000-0000-0000-000000000003"), + FK_StoreId = new Guid("26000000-0000-0000-0000-000000000005"), + CreatedAt = new DateTime(2026, 8, 20, 0, 0, 0, 0, DateTimeKind.Unspecified), + Id = new Guid("68000000-0000-0000-0000-000000000003"), + IsActive = true + }); + }); + + modelBuilder.Entity("Distribution.Models.Vehicle", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("DefaultWarehouseId") + .HasColumnType("char(36)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("NumberPlate") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("RegNumber") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Type") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("DefaultWarehouseId"); + + b.ToTable("Vehicles"); + + b.HasData( + new + { + Id = new Guid("28000000-0000-0000-0000-000000000001"), + CreatedAt = new DateTime(2026, 8, 20, 0, 0, 0, 0, DateTimeKind.Unspecified), + DefaultWarehouseId = new Guid("27000000-0000-0000-0000-000000000001"), + IsActive = true, + NumberPlate = "CAB-1234", + RegNumber = "WP-CAB-1234", + Type = 2 + }, + new + { + Id = new Guid("28000000-0000-0000-0000-000000000002"), + CreatedAt = new DateTime(2026, 8, 20, 0, 0, 0, 0, DateTimeKind.Unspecified), + DefaultWarehouseId = new Guid("27000000-0000-0000-0000-000000000002"), + IsActive = true, + NumberPlate = "KY-5678", + RegNumber = "CP-KY-5678", + Type = 1 + }); + }); + + modelBuilder.Entity("Distribution.Models.Warehouse", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("Location") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Type") + .HasColumnType("int"); + + b.Property("VehicleId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("VehicleId"); + + b.ToTable("Warehouses"); + + b.HasData( + new + { + Id = new Guid("27000000-0000-0000-0000-000000000001"), + CreatedAt = new DateTime(2026, 8, 20, 0, 0, 0, 0, DateTimeKind.Unspecified), + IsActive = true, + Location = "Peliyagoda, Colombo", + Name = "Colombo Distribution Center", + Type = 0 + }, + new + { + Id = new Guid("27000000-0000-0000-0000-000000000002"), + CreatedAt = new DateTime(2026, 8, 20, 0, 0, 0, 0, DateTimeKind.Unspecified), + IsActive = true, + Location = "Katugastota, Kandy", + Name = "Kandy Fulfillment Center", + Type = 1 + }); + }); + + modelBuilder.Entity("Distribution.Models.Claim", b => + { + b.HasOne("Distribution.Models.User", "AgencyUser") + .WithMany() + .HasForeignKey("FK_AgencyUserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Distribution.Models.User", "SettledByUser") + .WithMany() + .HasForeignKey("SettledByUserId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("AgencyUser"); + + b.Navigation("SettledByUser"); + }); + + modelBuilder.Entity("Distribution.Models.CreditNote", b => + { + b.HasOne("Distribution.Models.SaleReturn", "SaleReturn") + .WithMany() + .HasForeignKey("FK_SaleReturnId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("SaleReturn"); + }); + + modelBuilder.Entity("Distribution.Models.DeliveryRun", b => + { + b.HasOne("Distribution.Models.DriverProfile", "DriverProfile") + .WithMany() + .HasForeignKey("FK_DriverProfileId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Distribution.Models.Vehicle", "Vehicle") + .WithMany() + .HasForeignKey("FK_VehicleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("DriverProfile"); + + b.Navigation("Vehicle"); + }); + + modelBuilder.Entity("Distribution.Models.DeliveryTask", b => + { + b.HasOne("Distribution.Models.DeliveryRun", "Ride") + .WithMany() + .HasForeignKey("FK_RideId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Distribution.Models.ShopPO", "ShopPO") + .WithMany() + .HasForeignKey("FK_ShopPOId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Ride"); + + b.Navigation("ShopPO"); + }); + + modelBuilder.Entity("Distribution.Models.DriverProfile", b => + { + b.HasOne("Distribution.Models.User", "User") + .WithMany() + .HasForeignKey("FK_UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Distribution.Models.GoodsReceipt", b => + { + b.HasOne("Distribution.Models.PurchaseOrder", "PurchaseOrder") + .WithMany() + .HasForeignKey("FK_PurchaseOrderId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("Distribution.Models.Supplier", "Supplier") + .WithMany() + .HasForeignKey("FK_SupplierId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("Distribution.Models.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("FK_WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("PurchaseOrder"); + + b.Navigation("Supplier"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("Distribution.Models.GoodsReceiptItem", b => + { + b.HasOne("Distribution.Models.GoodsReceipt", "GoodsReceipt") + .WithMany("Items") + .HasForeignKey("FK_GoodsReceiptId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Distribution.Models.Item", "Item") + .WithMany() + .HasForeignKey("FK_ItemId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("Distribution.Models.PurchaseOrderItem", "PurchaseOrderItem") + .WithMany() + .HasForeignKey("FK_PurchaseOrderItemId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("GoodsReceipt"); + + b.Navigation("Item"); + + b.Navigation("PurchaseOrderItem"); + }); + + modelBuilder.Entity("Distribution.Models.GoodsReceiptItemBatch", b => + { + b.HasOne("Distribution.Models.GoodsReceiptItem", "GoodsReceiptItem") + .WithMany("Batches") + .HasForeignKey("FK_GoodsReceiptItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Distribution.Models.ItemBatch", "ItemBatch") + .WithMany() + .HasForeignKey("FK_ItemBatchId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("GoodsReceiptItem"); + + b.Navigation("ItemBatch"); + }); + + modelBuilder.Entity("Distribution.Models.GoodsReceiptItemSerial", b => + { + b.HasOne("Distribution.Models.GoodsReceiptItem", "GoodsReceiptItem") + .WithMany("Serials") + .HasForeignKey("FK_GoodsReceiptItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Distribution.Models.ItemSerialNumber", "ItemSerialNumber") + .WithMany() + .HasForeignKey("FK_ItemSerialNumberId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("GoodsReceiptItem"); + + b.Navigation("ItemSerialNumber"); + }); + + modelBuilder.Entity("Distribution.Models.Invoice", b => + { + b.HasOne("Distribution.Models.ShopPO", "ShopPO") + .WithMany() + .HasForeignKey("FK_ShopPOId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("ShopPO"); + }); + + modelBuilder.Entity("Distribution.Models.Item", b => + { + b.HasOne("Distribution.Models.Uom", "BaseUom") + .WithMany() + .HasForeignKey("FK_BaseUomId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Distribution.Models.Brand", "Brand") + .WithMany() + .HasForeignKey("FK_BrandId"); + + b.HasOne("Distribution.Models.Category", "Category") + .WithMany() + .HasForeignKey("FK_CategoryId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Distribution.Models.SubCategory", "SubCategory") + .WithMany() + .HasForeignKey("FK_SubCategoryId"); + + b.Navigation("BaseUom"); + + b.Navigation("Brand"); + + b.Navigation("Category"); + + b.Navigation("SubCategory"); + }); + + modelBuilder.Entity("Distribution.Models.ItemBatch", b => + { + b.HasOne("Distribution.Models.Item", "Item") + .WithMany() + .HasForeignKey("FK_ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Item"); + }); + + modelBuilder.Entity("Distribution.Models.ItemSerialNumber", b => + { + b.HasOne("Distribution.Models.Item", "Item") + .WithMany() + .HasForeignKey("FK_ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Item"); + }); + + modelBuilder.Entity("Distribution.Models.LoadingSheet", b => + { + b.HasOne("Distribution.Models.DeliveryRun", "Ride") + .WithMany() + .HasForeignKey("FK_RideId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Ride"); + }); + + modelBuilder.Entity("Distribution.Models.LoadingSheetItem", b => + { + b.HasOne("Distribution.Models.Item", "Item") + .WithMany() + .HasForeignKey("FK_ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Distribution.Models.LoadingSheet", "LoadingSheet") + .WithMany("Items") + .HasForeignKey("FK_LoadingSheetId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Distribution.Models.ShopPOItem", "ShopPOItem") + .WithMany() + .HasForeignKey("FK_ShopPOItemId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Distribution.Models.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("FK_WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Item"); + + b.Navigation("LoadingSheet"); + + b.Navigation("ShopPOItem"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("Distribution.Models.LoadingSheetOrder", b => + { + b.HasOne("Distribution.Models.LoadingSheet", "LoadingSheet") + .WithMany("Orders") + .HasForeignKey("FK_LoadingSheetId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Distribution.Models.ShopPO", "ShopPO") + .WithMany() + .HasForeignKey("FK_ShopPOId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("LoadingSheet"); + + b.Navigation("ShopPO"); + }); + + modelBuilder.Entity("Distribution.Models.Payment", b => + { + b.HasOne("Distribution.Models.Invoice", "Invoice") + .WithMany("Payments") + .HasForeignKey("FK_InvoiceId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Invoice"); + }); + + modelBuilder.Entity("Distribution.Models.PriceList", b => + { + b.HasOne("Distribution.Models.Region", "Region") + .WithMany() + .HasForeignKey("FK_RegionId"); + + b.Navigation("Region"); + }); + + modelBuilder.Entity("Distribution.Models.PriceListItem", b => + { + b.HasOne("Distribution.Models.Item", "Item") + .WithMany() + .HasForeignKey("FK_ItemId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Distribution.Models.PriceList", "PriceList") + .WithMany("Items") + .HasForeignKey("FK_PriceListId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Item"); + + b.Navigation("PriceList"); + }); + + modelBuilder.Entity("Distribution.Models.PurchaseOrder", b => + { + b.HasOne("Distribution.Models.Supplier", "Supplier") + .WithMany() + .HasForeignKey("FK_SupplierId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Supplier"); + }); + + modelBuilder.Entity("Distribution.Models.PurchaseOrderItem", b => + { + b.HasOne("Distribution.Models.Item", "Item") + .WithMany() + .HasForeignKey("FK_ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Distribution.Models.PurchaseOrder", "PurchaseOrder") + .WithMany("Items") + .HasForeignKey("FK_PurchaseOrderId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Item"); + + b.Navigation("PurchaseOrder"); + }); + + modelBuilder.Entity("Distribution.Models.PurchaseReturn", b => + { + b.HasOne("Distribution.Models.PurchaseOrder", "PurchaseOrder") + .WithMany() + .HasForeignKey("FK_PurchaseOrderId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("PurchaseOrder"); + }); + + modelBuilder.Entity("Distribution.Models.PurchaseReturnItem", b => + { + b.HasOne("Distribution.Models.Item", "Item") + .WithMany() + .HasForeignKey("FK_ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Distribution.Models.PurchaseReturn", "PurchaseReturn") + .WithMany("Items") + .HasForeignKey("FK_PurchaseReturnId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Item"); + + b.Navigation("PurchaseReturn"); + }); + + modelBuilder.Entity("Distribution.Models.RegionAgencyMap", b => + { + b.HasOne("Distribution.Models.User", "AgencyUser") + .WithMany() + .HasForeignKey("FK_AgencyUserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Distribution.Models.Region", "Region") + .WithMany() + .HasForeignKey("FK_RegionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AgencyUser"); + + b.Navigation("Region"); + }); + + modelBuilder.Entity("Distribution.Models.RegionalBudget", b => + { + b.HasOne("Distribution.Models.Region", "Region") + .WithMany() + .HasForeignKey("FK_RegionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Region"); + }); + + modelBuilder.Entity("Distribution.Models.RegionalTarget", b => + { + b.HasOne("Distribution.Models.Region", "Region") + .WithMany() + .HasForeignKey("FK_RegionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Region"); + }); + + modelBuilder.Entity("Distribution.Models.RegionalTaxRule", b => + { + b.HasOne("Distribution.Models.Region", "Region") + .WithMany() + .HasForeignKey("FK_RegionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Region"); + }); + + modelBuilder.Entity("Distribution.Models.RolePermission", b => + { + b.HasOne("Distribution.Models.Permission", "Permission") + .WithMany() + .HasForeignKey("FK_PermissionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Distribution.Models.Role", "Role") + .WithMany() + .HasForeignKey("FK_RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Permission"); + + b.Navigation("Role"); + }); + + modelBuilder.Entity("Distribution.Models.Route", b => + { + b.HasOne("Distribution.Models.User", "OwnerUser") + .WithMany() + .HasForeignKey("FK_OwnerUserId"); + + b.HasOne("Distribution.Models.Region", "Region") + .WithMany() + .HasForeignKey("FK_RegionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("OwnerUser"); + + b.Navigation("Region"); + }); + + modelBuilder.Entity("Distribution.Models.RouteVisitLog", b => + { + b.HasOne("Distribution.Models.Route", "Route") + .WithMany() + .HasForeignKey("FK_RouteId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Distribution.Models.Store", "Store") + .WithMany() + .HasForeignKey("FK_StoreId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Distribution.Models.User", "User") + .WithMany() + .HasForeignKey("FK_UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Route"); + + b.Navigation("Store"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Distribution.Models.SaleReturn", b => + { + b.HasOne("Distribution.Models.User", "CreatedByUser") + .WithMany() + .HasForeignKey("CreatedByUserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Distribution.Models.ShopPO", "ShopPO") + .WithMany() + .HasForeignKey("FK_ShopPOId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("CreatedByUser"); + + b.Navigation("ShopPO"); + }); + + modelBuilder.Entity("Distribution.Models.SaleReturnItem", b => + { + b.HasOne("Distribution.Models.Item", "Item") + .WithMany() + .HasForeignKey("FK_ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Distribution.Models.SaleReturn", "SaleReturn") + .WithMany("Items") + .HasForeignKey("FK_SaleReturnId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Item"); + + b.Navigation("SaleReturn"); + }); + + modelBuilder.Entity("Distribution.Models.Scheme", b => + { + b.HasOne("Distribution.Models.Item", "FreeItem") + .WithMany() + .HasForeignKey("FK_FreeItemId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("Distribution.Models.Item", "TriggerItem") + .WithMany() + .HasForeignKey("FK_TriggerItemId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("Distribution.Models.Region", "Region") + .WithMany() + .HasForeignKey("RegionId"); + + b.Navigation("FreeItem"); + + b.Navigation("Region"); + + b.Navigation("TriggerItem"); + }); + + modelBuilder.Entity("Distribution.Models.SchemeBundleComponent", b => + { + b.HasOne("Distribution.Models.Item", "Item") + .WithMany() + .HasForeignKey("FK_ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Distribution.Models.Scheme", "Scheme") + .WithMany("BundleComponents") + .HasForeignKey("FK_SchemeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Item"); + + b.Navigation("Scheme"); + }); + + modelBuilder.Entity("Distribution.Models.SchemeRedemption", b => + { + b.HasOne("Distribution.Models.Item", "FreeItem") + .WithMany() + .HasForeignKey("FK_FreeItemId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("Distribution.Models.Scheme", "Scheme") + .WithMany("Redemptions") + .HasForeignKey("FK_SchemeId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Distribution.Models.ShopPO", "ShopPO") + .WithMany() + .HasForeignKey("FK_ShopPOId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("FreeItem"); + + b.Navigation("Scheme"); + + b.Navigation("ShopPO"); + }); + + modelBuilder.Entity("Distribution.Models.ShopPO", b => + { + b.HasOne("Distribution.Models.Store", "Store") + .WithMany() + .HasForeignKey("FK_StoreId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Store"); + }); + + modelBuilder.Entity("Distribution.Models.ShopPOItem", b => + { + b.HasOne("Distribution.Models.Item", "Item") + .WithMany() + .HasForeignKey("FK_ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Distribution.Models.ShopPO", "ShopPO") + .WithMany("Items") + .HasForeignKey("FK_ShopPOId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Item"); + + b.Navigation("ShopPO"); + }); + + modelBuilder.Entity("Distribution.Models.StockBalance", b => + { + b.HasOne("Distribution.Models.ItemBatch", "ItemBatch") + .WithMany() + .HasForeignKey("FK_ItemBatchId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("Distribution.Models.Item", "Item") + .WithMany() + .HasForeignKey("FK_ItemId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Distribution.Models.ItemSerialNumber", "ItemSerialNumber") + .WithMany() + .HasForeignKey("FK_ItemSerialNumberId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Item"); + + b.Navigation("ItemBatch"); + + b.Navigation("ItemSerialNumber"); + }); + + modelBuilder.Entity("Distribution.Models.StockMovement", b => + { + b.HasOne("Distribution.Models.ItemBatch", "ItemBatch") + .WithMany() + .HasForeignKey("FK_ItemBatchId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("Distribution.Models.Item", "Item") + .WithMany() + .HasForeignKey("FK_ItemId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Distribution.Models.ItemSerialNumber", "ItemSerialNumber") + .WithMany() + .HasForeignKey("FK_ItemSerialNumberId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Item"); + + b.Navigation("ItemBatch"); + + b.Navigation("ItemSerialNumber"); + }); + + modelBuilder.Entity("Distribution.Models.Store", b => + { + b.HasOne("Distribution.Models.Route", "Route") + .WithMany() + .HasForeignKey("FK_RouteId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Route"); + }); + + modelBuilder.Entity("Distribution.Models.SubCategory", b => + { + b.HasOne("Distribution.Models.Category", "Category") + .WithMany() + .HasForeignKey("FK_CategoryId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Category"); + }); + + modelBuilder.Entity("Distribution.Models.SupplierClaim", b => + { + b.HasOne("Distribution.Models.GoodsReceipt", "GoodsReceipt") + .WithMany() + .HasForeignKey("FK_GoodsReceiptId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("Distribution.Models.PurchaseOrder", "PurchaseOrder") + .WithMany() + .HasForeignKey("FK_PurchaseOrderId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Distribution.Models.User", "RaisedByUser") + .WithMany() + .HasForeignKey("RaisedByUserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Distribution.Models.User", "ResolvedByUser") + .WithMany() + .HasForeignKey("ResolvedByUserId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("GoodsReceipt"); + + b.Navigation("PurchaseOrder"); + + b.Navigation("RaisedByUser"); + + b.Navigation("ResolvedByUser"); + }); + + modelBuilder.Entity("Distribution.Models.SupplierClaimItem", b => + { + b.HasOne("Distribution.Models.Item", "Item") + .WithMany() + .HasForeignKey("FK_ItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Distribution.Models.SupplierClaim", "SupplierClaim") + .WithMany("Items") + .HasForeignKey("FK_SupplierClaimId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Item"); + + b.Navigation("SupplierClaim"); + }); + + modelBuilder.Entity("Distribution.Models.UomConversion", b => + { + b.HasOne("Distribution.Models.Uom", "FromUom") + .WithMany() + .HasForeignKey("FK_FromUomId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Distribution.Models.Item", "Item") + .WithMany("Conversions") + .HasForeignKey("FK_ItemId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Distribution.Models.Uom", "ToUom") + .WithMany() + .HasForeignKey("FK_ToUomId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("FromUom"); + + b.Navigation("Item"); + + b.Navigation("ToUom"); + }); + + modelBuilder.Entity("Distribution.Models.User", b => + { + b.HasOne("Distribution.Models.User", "ReportsTo") + .WithMany() + .HasForeignKey("FK_ReportsToUserId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("Distribution.Models.Role", "Role") + .WithMany() + .HasForeignKey("FK_RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ReportsTo"); + + b.Navigation("Role"); + }); + + modelBuilder.Entity("Distribution.Models.UserRegionScope", b => + { + b.HasOne("Distribution.Models.Region", "Region") + .WithMany() + .HasForeignKey("FK_RegionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Distribution.Models.User", "User") + .WithMany() + .HasForeignKey("FK_UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Region"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Distribution.Models.UserRouteScope", b => + { + b.HasOne("Distribution.Models.Route", "Route") + .WithMany() + .HasForeignKey("FK_RouteId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Distribution.Models.User", "User") + .WithMany() + .HasForeignKey("FK_UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Route"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Distribution.Models.UserStoreScope", b => + { + b.HasOne("Distribution.Models.Store", "Store") + .WithMany() + .HasForeignKey("FK_StoreId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Distribution.Models.User", "User") + .WithMany() + .HasForeignKey("FK_UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Store"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Distribution.Models.Vehicle", b => + { + b.HasOne("Distribution.Models.Warehouse", null) + .WithMany() + .HasForeignKey("DefaultWarehouseId") + .OnDelete(DeleteBehavior.Restrict); + }); + + modelBuilder.Entity("Distribution.Models.Warehouse", b => + { + b.HasOne("Distribution.Models.Vehicle", "Vehicle") + .WithMany() + .HasForeignKey("VehicleId"); + + b.Navigation("Vehicle"); + }); + + modelBuilder.Entity("Distribution.Models.GoodsReceipt", b => + { + b.Navigation("Items"); + }); + + modelBuilder.Entity("Distribution.Models.GoodsReceiptItem", b => + { + b.Navigation("Batches"); + + b.Navigation("Serials"); + }); + + modelBuilder.Entity("Distribution.Models.Invoice", b => + { + b.Navigation("Payments"); + }); + + modelBuilder.Entity("Distribution.Models.Item", b => + { + b.Navigation("Conversions"); + }); + + modelBuilder.Entity("Distribution.Models.LoadingSheet", b => + { + b.Navigation("Items"); + + b.Navigation("Orders"); + }); + + modelBuilder.Entity("Distribution.Models.PriceList", b => + { + b.Navigation("Items"); + }); + + modelBuilder.Entity("Distribution.Models.PurchaseOrder", b => + { + b.Navigation("Items"); + }); + + modelBuilder.Entity("Distribution.Models.PurchaseReturn", b => + { + b.Navigation("Items"); + }); + + modelBuilder.Entity("Distribution.Models.SaleReturn", b => + { + b.Navigation("Items"); + }); + + modelBuilder.Entity("Distribution.Models.Scheme", b => + { + b.Navigation("BundleComponents"); + + b.Navigation("Redemptions"); + }); + + modelBuilder.Entity("Distribution.Models.ShopPO", b => + { + b.Navigation("Items"); + }); + + modelBuilder.Entity("Distribution.Models.SupplierClaim", b => + { + b.Navigation("Items"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Distribution/Migrations/20261008060840_AddGrnNbt.cs b/Distribution/Migrations/20261008060840_AddGrnNbt.cs new file mode 100644 index 0000000..71f9a36 --- /dev/null +++ b/Distribution/Migrations/20261008060840_AddGrnNbt.cs @@ -0,0 +1,66 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Distribution.Migrations +{ + /// + public partial class AddGrnNbt : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "NbtPercent", + table: "GoodsReceipts", + type: "decimal(5,2)", + precision: 5, + scale: 2, + nullable: false, + defaultValue: 0m); + + migrationBuilder.AddColumn( + name: "NbtPercent", + table: "GoodsReceiptItems", + type: "decimal(5,2)", + precision: 5, + scale: 2, + nullable: false, + defaultValue: 0m); + + migrationBuilder.UpdateData( + table: "GoodsReceiptItems", + keyColumn: "Id", + keyValue: new Guid("44000000-0000-0000-0000-000000000001"), + column: "NbtPercent", + value: 0m); + + migrationBuilder.UpdateData( + table: "GoodsReceiptItems", + keyColumn: "Id", + keyValue: new Guid("44000000-0000-0000-0000-000000000002"), + column: "NbtPercent", + value: 0m); + + migrationBuilder.UpdateData( + table: "GoodsReceipts", + keyColumn: "Id", + keyValue: new Guid("43000000-0000-0000-0000-000000000001"), + column: "NbtPercent", + value: 0m); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "NbtPercent", + table: "GoodsReceipts"); + + migrationBuilder.DropColumn( + name: "NbtPercent", + table: "GoodsReceiptItems"); + } + } +} diff --git a/Distribution/Migrations/AppDBContextModelSnapshot.cs b/Distribution/Migrations/AppDBContextModelSnapshot.cs index bd3c2ef..47e5e65 100644 --- a/Distribution/Migrations/AppDBContextModelSnapshot.cs +++ b/Distribution/Migrations/AppDBContextModelSnapshot.cs @@ -446,6 +446,10 @@ namespace Distribution.Migrations b.Property("IsActive") .HasColumnType("tinyint(1)"); + b.Property("NbtPercent") + .HasPrecision(5, 2) + .HasColumnType("decimal(5,2)"); + b.Property("ReceivedAt") .HasColumnType("datetime(6)"); @@ -473,6 +477,7 @@ namespace Distribution.Migrations FK_ReceivedByUserId = new Guid("29000000-0000-0000-0000-000000000001"), FK_WarehouseId = new Guid("27000000-0000-0000-0000-000000000001"), IsActive = true, + NbtPercent = 0m, ReceivedAt = new DateTime(2026, 8, 15, 0, 0, 0, 0, DateTimeKind.Unspecified), VatPercent = 0m }); @@ -509,6 +514,10 @@ namespace Distribution.Migrations b.Property("IsActive") .HasColumnType("tinyint(1)"); + b.Property("NbtPercent") + .HasPrecision(5, 2) + .HasColumnType("decimal(5,2)"); + b.Property("RejectedQuantity") .HasColumnType("int"); @@ -541,6 +550,7 @@ namespace Distribution.Migrations FK_PurchaseOrderItemId = new Guid("42000000-0000-0000-0000-000000000001"), FreeQuantity = 0, IsActive = true, + NbtPercent = 0m, RejectedQuantity = 0, UnitCost = 0m, VatPercent = 0m @@ -555,6 +565,7 @@ namespace Distribution.Migrations FK_PurchaseOrderItemId = new Guid("42000000-0000-0000-0000-000000000002"), FreeQuantity = 0, IsActive = true, + NbtPercent = 0m, RejectedQuantity = 2, UnitCost = 0m, VatPercent = 0m From 0451a0f41827ee1456b689a6aa3a5bca2d85e5b5 Mon Sep 17 00:00:00 2001 From: DeepnaPooja Date: Thu, 8 Oct 2026 12:54:38 +0530 Subject: [PATCH 6/9] Add documentation for GRN costing, taxes, and item tracking configuration --- .../refactor_docs/grn_costing_and_taxes.md | 166 +++++++++++++ .../refactor_docs/item_tracking_config.md | 18 +- .../docs/refactor_docs/product_report_plan.md | 142 +++++++++++ .../refactor_docs/shoppo_invoice_pipeline.md | 227 ++++++++++++++++++ 4 files changed, 547 insertions(+), 6 deletions(-) create mode 100644 Distribution/docs/refactor_docs/grn_costing_and_taxes.md create mode 100644 Distribution/docs/refactor_docs/product_report_plan.md create mode 100644 Distribution/docs/refactor_docs/shoppo_invoice_pipeline.md diff --git a/Distribution/docs/refactor_docs/grn_costing_and_taxes.md b/Distribution/docs/refactor_docs/grn_costing_and_taxes.md new file mode 100644 index 0000000..5e3e91c --- /dev/null +++ b/Distribution/docs/refactor_docs/grn_costing_and_taxes.md @@ -0,0 +1,166 @@ +# GRN: single entry screen, costing, free issues and configurable taxes + +**Status**: Implemented in code. Three migrations are written but must be applied with +`dotnet ef database update` before GRNs can be saved or listed (see Migrations). +**Touches**: `Features/Grn`, `Features/SysConfig`, `Models/GoodsReceipt*.cs`, the Configuration page, +`distribution_fe/app/(dashboard)/procurement/grn/*`. +**Related**: [15_procurement.md](../15_procurement.md) (PO → GRN flow), +[item_tracking_config.md](./item_tracking_config.md) (serial / batch / expiry capture). + +--- + +## 1. One screen for both kinds of receipt + +**Is a purchase order mandatory? No.** A PO is the order placed with a supplier *before* goods +arrive; a GRN records what *actually* arrived and increases stock. Stock posting is identical either +way. What a PO adds: auto-filled lines and costs, a hard stop on over-receiving, PO status tracking +(Issued → Partially Received → Received), and the features that hang off a PO (purchase returns, +invoice matching, supplier evaluation). A Direct GRN has none of these controls. Use a PO for normal +ordering; use Direct GRN for unplanned deliveries, walk-in purchases or opening stock. Nothing +currently restricts Direct GRN (no permission, no switch) — add one if every receipt must have an order. + +The old two pages (`/procurement/grn/po/new`, `/procurement/grn/direct/new`) are replaced by +**`/procurement/grn/new`**. The GRN list has a single **New GRN** button. + +A **Receipt Type** switch at the top picks the behaviour: + +| | Against PO (default) | Direct GRN | +|---|---|---| +| Purchase order | required dropdown (Issued / PartiallyReceived only) | none | +| Supplier | read-only, taken from the PO | optional dropdown | +| Received date | shown | not shown (server stamps the time) | +| Lines | prefilled from the PO: remaining qty, PO unit cost | any catalogue item, added by hand | +| **Add Item** | adds a line chosen from **that PO's items** (e.g. a second batch of the same PO line) | adds a line with any item | +| Endpoint | `POST /api/goods-receipts` | `POST /api/goods-receipts/direct` | +| Service method | `GrnService.CreateAsync` | `GrnService.CreateDirectAsync` | + +Switching the type resets the lines. A receipt against a PO can only contain items that are on the PO; +anything else must be a Direct GRN. Several lines may point at the same PO line; the over-receipt +check runs against the running total as each line is applied. + +## 2. The items table + +Columns: **Item · Batch No · Accepted · Rejected · Free Qty · Unit Cost · Disc % · NBT % · VAT % · +Line Total** (NBT % / VAT % only when enabled in Configuration), plus a delete icon. + +- **Batch No** summarises the lots (`B1 ×5, B2 ×3`), a serial count, or an expiry-lot count. The + existing panel under the row still edits the serials / batches. +- **Totals card** under the table: Subtotal, Discount, Taxable amount, NBT, VAT, Grand total. +- **GRN Discount %, NBT %, VAT %** at the top are *defaults*: picking or typing a value (0–100, + decimals allowed) fills every line; any line can still be edited afterwards. + +## 3. Costing rules (server is the source of truth) + +Per line, rounded to 2 dp (half away from zero), in `Features/Grn/GrnPricing.cs`: + +``` +gross = acceptedQty x unitCost +discount = gross x disc% +net = gross - discount +NBT = net x NBT% +VAT = (net + NBT) x VAT% // Sri Lanka order: VAT is charged on the NBT-inclusive amount +total = net + NBT + VAT +``` + +- Only **accepted** units are costed. Rejected units never are; **free** units are stocked but not + costed. +- Unit cost: the request's value, else the PO line's `UnitCost` (PO receipts), else 0 (direct). +- Disc % / NBT % / VAT %: the line's value, else the GRN-wide value. +- Percentages must be 0–100, cost cannot be negative; violations are `409` with a message. +- GRN totals are **not stored**. They are recomputed from the stored line percentages whenever a GRN + is read, so they cannot drift. The frontend mirrors the same formula (`grn-costing.tsx`). + +## 4. Free issues + +`GoodsReceiptItem.FreeQuantity`: units the supplier gave at no charge. + +- Added to stock together with accepted units (`StockBalance` / `StockMovement`). +- Not costed and **not** counted against the PO line's ordered quantity (`ReceivedQuantity` grows by + accepted units only). +- Serial / batch entries must cover **accepted + free** (10 accepted + 2 free = 12 serials, or batch + quantities summing to 12). +- A line may be entirely free. A direct line is rejected only if accepted + rejected + free is 0. + +## 5. Configurable GRN taxes (Configuration → GRN Taxes) + +Stored in the existing `SysConfig` key/value table (no table change): + +| Key | Values | Unconfigured default | +|---|---|---| +| `Grn.Tax.Vat.Mode` | `Disabled` / `Optional` / `Mandatory` | `Optional` (how GRNs behaved before) | +| `Grn.Tax.Vat.DefaultRate` | 0–100 | 0 | +| `Grn.Tax.Nbt.Mode` | `Disabled` / `Optional` / `Mandatory` | `Disabled` | +| `Grn.Tax.Nbt.DefaultRate` | 0–100 | 0 | + +Set through the generic `PUT /api/sys-config/{key}`; the service validates modes and rates. +`SysConfigService.GetGrnTaxSettingsAsync()` reads them. The GRN screen reads the same keys from +`GET /api/sys-config`. + +| Mode | GRN form | Server | +|---|---|---| +| Disabled ("Not applied") | field and column hidden | percentage forced to 0, client values ignored | +| Optional | shown; may stay at 0 | missing value = 0 | +| Mandatory | shown, red `*`; the GRN-wide field cannot be left blank (0% is allowed); Save is disabled while blank | request rejected (`409`, "… is mandatory") if neither the line nor the GRN supplies the value | + +Applies to **GRNs only**. The default rate seeds new GRNs (header and lines); users can change it. + +## 6. Data model changes + +| Table | New columns | +|---|---| +| `GoodsReceipts` | `DiscountPercent`, `VatPercent`, `NbtPercent` (`decimal(5,2)`, default 0) | +| `GoodsReceiptItems` | `UnitCost` (`decimal(18,2)`), `DiscountPercent`, `VatPercent`, `NbtPercent` (`decimal(5,2)`), `FreeQuantity` (`int`), all default 0 | + +New enum `TaxMode { Disabled, Optional, Mandatory }`. + +### API surface + +Requests (`POST /api/goods-receipts`, `/api/goods-receipts/direct`, `/api/purchase-orders/{id}/goods-receipts`): +header `discountPercent`, `vatPercent`, `nbtPercent`; per line `unitCost`, `discountPercent`, +`vatPercent`, `nbtPercent`, `freeQuantity` (all optional). +Responses add the same fields plus per-line `grossAmount`, `discountAmount`, `nbtAmount`, +`vatAmount`, `lineTotal` and GRN-level `subTotal`, `discountAmount`, `taxableAmount`, `nbtAmount`, +`vatAmount`, `totalAmount`. See `sharedDocs/BACKEND_API_REFERENCE.md` §6.19. + +## 7. Migrations + +1. `20261007063956_AddGrnPricing` — header + line discount / VAT, line unit cost. *Applied.* +2. `20261007065602_AddGrnFreeQuantity` — line free quantity. *Applied.* +3. `20261008060840_AddGrnNbt` — header + line NBT. ***Pending** when last checked + (`dotnet ef migrations list`).* + +All only add columns with default 0 (existing GRNs are unaffected). Run +`dotnet ef database update` from `Distribution/` (stop a running API first, it locks the build). +The app does not migrate on startup, so until the NBT migration is applied every call that reads or +writes GRNs fails with `Unknown column 'g.NbtPercent'` (surfacing on save as a `DbUpdateException`). +Re-check with `dotnet ef migrations list` before assuming a database is current. + +## 8. Auto-generated serial numbers continue from what is saved + +The panel's **Auto-generate** used to start at 1 each time, so a second receipt of the same item +regenerated `BEV-Fanta-100-0001…` and the server rejected it ("Serial number … already exists for +item …"). Now: + +- `GET /api/stock/serials/next-sequence?itemId=&prefix=` returns `{ "next": n }`: one past the highest + numeric suffix among the item's saved serials that look like `` (1 when none). + Serials that don't fit the pattern (e.g. a supplier's own numbers) are ignored. + (`StockRepository.GetNextSerialSequenceAsync`.) +- Auto-generate calls it, then starts at `max(start field, next)`: first 5 units → `0001–0005`, next + receipt → `0006–0010`. The "start" field is updated after each run. +- It also skips serials already in the same list and serials on **other lines of the same GRN for the + same item**, so two lines of one item cannot collide. +- If the lookup fails, it warns and falls back to the start field; the server still rejects any + duplicate at save. +- Serials already sitting in an open form are not rewritten: clear the list and generate again. + +## 9. Known limits / follow-ups + +- The PO receipt request carries no received date; the server stamps `DateTime.Now`. The date field on + the screen is sent but ignored. +- In the over-receipt check, a PO line's *rejected* units from earlier lines / receipts are not + carried forward (only accepted units advance `ReceivedQuantity`). Pre-existing behaviour. +- The GRN list table does not show costs or totals; the details dialog does (NBT only when present). +- Mandatory means "must be filled in"; there is no rule forcing a rate above 0. +- Tests: `Distribution.Tests/Services/GrnPricingTests.cs` covers the arithmetic and validation. The + server-side tax-mode handling, free-quantity stock posting and serial numbering have no automated + tests yet. diff --git a/Distribution/docs/refactor_docs/item_tracking_config.md b/Distribution/docs/refactor_docs/item_tracking_config.md index 63b322f..9f0aa75 100644 --- a/Distribution/docs/refactor_docs/item_tracking_config.md +++ b/Distribution/docs/refactor_docs/item_tracking_config.md @@ -188,8 +188,10 @@ an item that doesn't track batches, or a missing expiry date on an item that req warranty-period number input, and (only when `Feature.CustomWarrantyOverride` is on) an `Allow custom warranty period` checkbox. Checkboxes for a disabled global feature are hidden entirely, not just disabled, to avoid confusing item editors. -- **GRN entry** — built as two full pages (against a PO: `/procurement/grn/po/new`; direct: - `/procurement/grn/direct/new`). Tracked lines show a status chip that opens a panel for serial +- **GRN entry** — now **one page**, `/procurement/grn/new`, with an *Against PO / Direct GRN* switch + (the former `/procurement/grn/po/new` and `/procurement/grn/direct/new` pages were merged; see + [grn_costing_and_taxes.md](./grn_costing_and_taxes.md)). Serial / batch counts on a line must cover + *accepted + free* units. Tracked lines show a status chip that opens a panel for serial entry (scan / auto-generate / type-paste-edit) or batch rows (batch no. with Auto, expiry, qty). Save is disabled until every tracked line is complete. Details in the plan, Phases 10–12. - **configuration page**: a single Products card on `/configuration` holds the switches and warranty @@ -233,13 +235,17 @@ Rejected quantity never touches stock. Everything posts in one unit of work and failure. A serial that already exists for the item, or a mismatch with the item's flags, is rejected. **Two ways to receive**: *against a PO* (lines come from the PO, PO received-quantities and status -update) or *direct* (no PO; item chosen per line, optional supplier). The UI exposes them as two -pages and both share the same serial/batch entry panel. +update) or *direct* (no PO; item chosen per line, optional supplier). The UI exposes them as one +page with a receipt-type switch, sharing the same serial/batch entry panel. Free-issue units are +stocked like accepted ones, so the serial / batch totals include them. ### 3. Entering serials / batches in the UI - Each tracked line shows a chip (amber until complete, green ✓ when complete) → opens the panel. -- **Serials**: scan (Enter adds one), auto-generate (prefix + start number, fills only missing units), - or type/paste one per line; every entry stays editable; duplicates are flagged. +- **Serials**: scan (Enter adds one), auto-generate (prefix + start number, fills only missing units; + numbering continues after the highest `` already saved for the item, so a later + receipt gets `0006–0010` rather than repeating `0001–0005` — see + [grn_costing_and_taxes.md](./grn_costing_and_taxes.md) §8), or type/paste one per line; every + entry stays editable; duplicates are flagged. - **Batches**: add rows; batch number can be typed, scanned or auto-generated; expiry date warns if expired or within 30 days; quantities must add up to the accepted quantity. - Save is disabled until all tracked lines are complete; the server re-validates. diff --git a/Distribution/docs/refactor_docs/product_report_plan.md b/Distribution/docs/refactor_docs/product_report_plan.md new file mode 100644 index 0000000..2361c27 --- /dev/null +++ b/Distribution/docs/refactor_docs/product_report_plan.md @@ -0,0 +1,142 @@ +# Plan: Product Report + +## Status — implemented (Phases 1–3) + +Built as `Features/ProductReport` (`api/reports/products`: `stock`, `sales`, `returns`, `summary`) +and the page `/reports/products` (Stock / Sales / Returns tabs, summary cards). Not built: Excel/PDF +export, a `reports.view` permission and region/route/store scoping (the page reuses +`warehouses.view`), purchase-return figures, the slow-stock / expiry phase (4). + +Decisions taken for the open questions below: **sales** = fulfilled quantity on non-cancelled shop +POs whose invoice is `Finalized`, dated by `Invoice.FinalizedAt`; **margin** uses `Item.CostPrice`. + +Implementation notes (the database is **MySQL** via Pomelo, which constrains the queries): +- EF's `GroupBy` + `Distinct().Count()` over a navigation (distinct stores per item) generates a + derived table that references the outer query, which MySQL rejects (`Unknown column … in 'WHERE'`). + Distinct stores are therefore counted in a second small query for the items on the current page. +- Grouped queries project to an anonymous type, are ordered and paged on that, then mapped to the + response records. The stock report counts warehouse / vehicle quantity with per-item subqueries + instead of a `GroupBy` + `DefaultIfEmpty` join. +- Limits: `pageSize` ≤ 200, date range ≤ 366 days. + +--- + +## Context + +There is no reporting module yet. "Product" in this codebase is `Item` +(`Distribution/Models/Item.cs`). A product report is a read-only aggregation over data that +already exists, so **no new tables or migrations** are needed for the first phases. + +Source data (verified in the models): + +| Area | Entity | Useful fields | +|---|---|---| +| Master | `Item` | `Sku`, `Name`, `FK_CategoryId`, `FK_SubCategoryId`, `FK_BrandId`, `FK_BaseUomId`, `SalePrice`, `CostPrice`, `ReorderLevel`, tracking flags, `IsActive` (via `Template`) | +| Stock | `StockBalance` | `FK_ItemId`, `LocationId` + `LocationType` (Warehouse/Vehicle/External), batch/serial, `Quantity` | +| Stock history | `StockMovement` | `Type` (Sale, CustomerReturn, Transfer, Damage, Adjustment, Load, PurchaseReceipt, PurchaseReturn), `Quantity`, `CreatedAt` | +| Sales | `ShopPOItem` | `Quantity`, `QuantityFulfilled`, `UnitPrice`; parent `ShopPO` (Status, ConfirmedAt, store) | +| Invoice | `Invoice` | Has **no line items**. Links to `ShopPO` via `FK_ShopPOId`; `Status` (Draft/Printed/Finalized), `TotalAmount` | +| Purchase | `PurchaseOrderItem`, `GoodsReceiptItem` | `UnitCost`, ordered qty; `AcceptedQuantity`, `RejectedQuantity` | +| Returns | `SaleReturnItem`, `PurchaseReturnItem` | `Quantity`, `ReasonCode` (Expired/Damaged/OrderError/Other), `UnitPrice` | + +Important consequence: because `Invoice` has no line items, **product-level sales must come from +`ShopPOItem` joined to a finalized `Invoice`** (via `ShopPO`), not from invoice rows. + +## Scope (phased) + +Each phase is independently shippable. Build in order. + +### Phase 1: Product Stock Report (no new data) + +One row per item (optionally per warehouse): + +- Sku, name, brand, category, sub-category, base UOM +- Sale price, cost price, stock value (`Quantity * CostPrice`) +- On-hand qty (sum of `StockBalance` for `LocationType.Warehouse`), on-vehicle qty + (`LocationType.Vehicle`), total +- Low-stock flag (`Quantity <= ReorderLevel`), reuses the logic behind `GET /api/stock/low-stock` +- Active/inactive + +Filters: brand, category, sub-category, warehouse, active only, low-stock only, search (sku/name). + +### Phase 2: Product Sales Report + +Per item for a date range: + +- Qty ordered, qty fulfilled, gross sales (`QuantityFulfilled * UnitPrice`) +- Number of distinct shops/orders +- Rank (top/bottom N) +- Breakdown by region/route/store (grouping option) + +Rules: +- Count only `ShopPO` rows where `Status != Cancelled`. +- **Decision needed:** define "sales" once. Recommended: fulfilled quantity on shop POs whose + invoice is `Finalized` (matches the pipeline in `shoppo_invoice_pipeline.md`). +- Date basis: `Invoice.FinalizedAt`. + +### Phase 3: Returns and Margin + +- Sales returns per item: qty, value, reason breakdown (`ReturnReasonCode`), return % of sold qty +- Purchase returns per item +- Gross margin: `(avg selling price - CostPrice) * qty`. Use `Item.CostPrice` first; later switch + to latest `PurchaseOrderItem.UnitCost` if cost history matters. + +### Phase 4: Movement / ageing (optional) + +- Slow/dead stock: items with no `StockMovement` of type `Sale`/`Load` in N days +- Movement ledger per item (reuse `GET /api/stock/movements`) +- Batch/expiry report for items with `TracksBatches`/`TracksExpiryDate` + +## Backend design + +Follow the existing feature layout (`Controller`, `Service`, `IRepository`, `Repository`, `DTOs`). + +New feature folder: `Distribution/Features/ProductReport/` + +- `ProductReportController.cs`, route `api/reports/products` + - `GET /stock` (Phase 1) + - `GET /sales` (Phase 2) + - `GET /returns` (Phase 3) + - `GET /summary`: top-line totals for the report header + - `GET /export?format=xlsx` (last step) +- `IProductReportRepository` / `ProductReportRepository`: EF Core grouped queries + (`GroupBy` on `FK_ItemId`, `AsNoTracking`, projection straight into DTOs). No in-memory loops. +- `ProductReportService`: applies filters, scope and paging; computes derived fields + (stock value, return %, margin). +- `ProductReportDTOs.cs`: `ProductReportQuery` (filters, `From`, `To`, `Page`, `PageSize`, `SortBy`), + row DTOs per report, `PagedResult` (reuse whatever the other features already use). +- Register in `Program.cs` the same way as the other feature services. + +Cross-cutting: +- **Authorization:** add a `reports.view` permission (see `Permission.cs`/`RolePermission.cs`) and + honour the existing `UserRegionScope`/`UserRouteScope`/`UserStoreScope` so users only see data + in their scope. +- **Paging and limits:** default page size 50; cap date range (e.g. 366 days). +- **Indexes:** check `ShopPOItem(FK_ItemId)`, `StockMovement(FK_ItemId, CreatedAt)`, + `StockBalance(FK_ItemId, LocationId)`; add only if the query plans need them. + +## Frontend design + +- Page: `distribution_fe/app/(dashboard)/reports/products/page.tsx` + (tabs: Stock, Sales, Returns) +- API client: `distribution_fe/lib/api/reports.ts`, exported from `lib/api/index.ts` + (same pattern as `stock.ts`, `sales.ts`) +- Filter bar (date range, brand, category, warehouse, region/route), summary tiles, table with + server-side paging and sorting, Export button. +- Add a "Reports" entry to the dashboard nav. + +## Open questions + +1. Who is the primary reader (management, warehouse, sales)? This sets Phase 1 vs 2 priority. +2. Definition of "sales": fulfilled qty on finalized invoices, or all confirmed shop POs? +3. Cost basis for margin: `Item.CostPrice` or latest PO cost? +4. Export format: Excel only, or PDF too? +5. Do reports need to respect region/route/store scope for all roles? + +## Verification + +- Unit tests in `Distribution.Tests` for the service (filter, paging, derived fields) with seeded data. +- Cross-check: Phase 1 on-hand totals equal `GET /api/stock/balances/{itemId}/total`. +- Cross-check: Phase 2 sales total for a date range equals the sum of finalized `Invoice.TotalAmount` + for the same range (excluding any invoice-level adjustments). +- Run with a large seed and check query time and that exports match the on-screen table. diff --git a/Distribution/docs/refactor_docs/shoppo_invoice_pipeline.md b/Distribution/docs/refactor_docs/shoppo_invoice_pipeline.md new file mode 100644 index 0000000..df649de --- /dev/null +++ b/Distribution/docs/refactor_docs/shoppo_invoice_pipeline.md @@ -0,0 +1,227 @@ +# Plan: Close the Ride → Loading Sheet → Shop PO → Delivery → Invoice → Payment pipeline + +## Context + +The goal was a step-by-step frontend flow for Operations (Rides/Loading Sheets/Delivery Tasks) +and for Shop POs/Invoices, grounded in what the backend actually implements. + +Investigation found the pipeline was **not end-to-end**, even though most of the frontend UI for it +already existed: + +- **Ride** (`RideController`) and **LoadingSheet** (`LoadingSheetController`) were fully implemented, + backend and frontend, and already worked (create → set items → confirm → stock moves warehouse→vehicle). +- **ShopPO** (`ShopPOController`) was **GET-only** on the backend — no create/update-items/confirm/ + confirm-and-create-invoice/cancel logic existed — despite the frontend + (`distribution_fe/app/(dashboard)/sales/new/page.tsx`, `lib/api/sales.ts`'s `shopPos` client) + already calling all five of those routes. Those calls previously 404'd. +- **Invoice** (`InvoiceController`) was likewise **GET-only** — no print/finalize logic — despite the + frontend (`sales/orders/_components/orders-client.tsx`) already calling `invoices.print()` / + `invoices.finalize()`. +- **DeliveryTask** generation (`DeliveryTaskService.GenerateAsync`) was stubbed: + `throw new NotImplementedException(...)`. No frontend trigger for it existed either. +- **LoadingSheetOrder** (the link table between a `LoadingSheet` and the `ShopPO`s it's carrying) was + never populated anywhere in code — only by seed data — even though `LoadingSheetItem.FK_ShopPOItemId` + (optional, already wired end-to-end) was exactly the data needed to derive it. +- **Payment** (`PaymentController`/`PaymentService`) was fully implemented on both ends already — + no changes needed there. + +The DTOs for the missing ShopPO endpoints (`CreateShopPORequest`, `UpdateShopPOItemsRequest`, +`ShopPOItemRequest`) already existed in `Distribution/Features/ShopPO/ShopPODTOs.cs`, unused — the +frontend and DTOs were clearly built ahead of the service/controller logic. This plan closes that +gap: implement the missing backend pieces, wire the one still-missing frontend action (delivery task +generation trigger), and document the resulting step-by-step flow. + +## Backend work + +### 1. `ShopPOService` — Create / UpdateItems / Confirm / ConfirmAndCreateInvoice / Cancel + +File: `Distribution/Features/ShopPO/ShopPOService.cs` (previously only had `GetAllAsync`/`GetByIdAsync`). +New endpoints on `ShopPOController.cs` (previously GET-only), matching the routes the frontend +already calls: + +- `POST /api/shop-pos` → `CreateAsync(CreateShopPORequest, Guid raisedById, ct)` + - Creates `ShopPO` with `Status = Pending`, `TotalAmount` computed from line items + (`Quantity * UnitPrice`, resolving `UnitPrice` server-side via `PriceListService.ResolveByStoreAsync` + when not supplied — the same resolution `sales/new/page.tsx` calls client-side at + `priceListsApi.resolve`). + - Creates `ShopPOItem` rows, `QuantityFulfilled = 0`. + +- `PUT /api/shop-pos/{id}/items` → `UpdateItemsAsync(id, UpdateShopPOItemsRequest, ct)` + - Guard: only allowed while `ConfirmedAt is null` (mirrors `LoadingSheetService.SetItemsAsync`'s + Planned-only guard) — same replace-all-items pattern (`ReplaceItemsAsync`). + - Recomputes `TotalAmount`. + +- `POST /api/shop-pos/{id}/confirm` → `ConfirmAsync(id, Guid confirmedBy, ct)` + - Guard: `ConfirmedAt is null` and `Status != Cancelled`. + - Sets `ConfirmedAt = DateTime.Now`, `ConfirmedBy = confirmedBy`. Status stays `Pending` + (fulfillment tracking is a separate axis, same pattern as `DeliveryTaskService.ConfirmAsync` + already updating `ShopPO.Status` independently of `ConfirmedAt`). + - Does **not** create an Invoice (delivery-later path — invoice creation trigger for that path + remains an open question, see below). + +- `POST /api/shop-pos/{id}/confirm-and-create-invoice` → `ConfirmAndCreateInvoiceAsync(id, Guid userId, ct)` + - Does everything `ConfirmAsync` does, plus creates an `Invoice` row (`FK_ShopPOId`, `Status = Draft`, + `PaymentStatus = Pending`, `CreatedBy = userId`, `TotalAmount = po.TotalAmount`) and sets + `po.InvoiceId`. This is the on-the-spot sale path (rep+driver together). + +- `POST /api/shop-pos/{id}/cancel` → `CancelAsync(id, ct)` + - Guard: `Status != Cancelled` and not already `PartiallyFulfilled`/`Fulfilled`. + - Sets `Status = Cancelled`. + +Follows the existing service pattern used by `RideService.CloseAsync`/`CancelAsync` and +`LoadingSheetService.ConfirmAsync`: `uow.BeginAsync` → mutate → `repo.Update` → +`uow.CommitAsync`/`RollbackAsync` on exception; controller catches `KeyNotFoundException` → 404, +`InvalidOperationException` → 400. + +### 2. `LoadingSheetService.SetItemsAsync` — derive `LoadingSheetOrder` rows + +File: `Distribution/Features/LoadingEvent/LoadingSheetService.cs`. After replacing items, the +distinct `FK_ShopPOId` values are looked up from each item's `FK_ShopPOItemId` (via the new +`ILoadingSheetRepository.GetShopPOIdsByItemIdsAsync`), and `LoadingSheetOrder` rows for the sheet are +replaced accordingly (`ReplaceOrdersAsync` — same replace-all-on-each-call approach as the items +themselves). This makes `LoadingSheetResponse.Orders` (already returned, previously always empty +outside seed data) actually populate, and is the data `DeliveryTaskService.GenerateAsync` needs. + +### 3. `DeliveryTaskService.GenerateAsync` — implemented + +File: `Distribution/Features/DeliveryRide/DeliveryTaskService.cs`. For the given `rideId`: finds the +ride's confirmed `LoadingSheet`(s), reads their `LoadingSheetOrder` rows, and creates one +`DeliveryTask` per distinct `ShopPO` referenced — `Status = Pending`, `Sequence` assigned in creation +order, `CustomerConfirmed = false`. Skips POs that already have a `DeliveryTask` for this ride +(idempotent, via `IDeliveryTaskRepository.ExistsForOrderAndRideAsync`). Guard: throws if the ride has +no confirmed loading sheet. + +### 4. `InvoiceService` — Print / Finalize + +File: `Distribution/Features/Invoice/InvoiceService.cs` (previously only Get/GetAll). Adds: + +- `POST /api/invoices/{id}/print` → `PrintAsync(id, Guid printedBy, ct)` + - Guard: `Status == Draft`. Sets `Status = Printed`, `PrintedAt = Now`, `PrintedBy = printedBy`. +- `POST /api/invoices/{id}/finalize` → `FinalizeAsync(id, FinalizeInvoiceRequest, Guid finalizedBy, ct)` + - Guard: `Status == Printed`. Sets `Status = Finalized`, `FinalizedAt = Now`, `FinalizedBy = finalizedBy`. + - Uses the pre-existing `FinalizeInvoiceRequest(List? Adjustments)` DTO + (was already defined, unused, in `InvoiceDTOs.cs` — `Adjustments` was made optional/nullable so + the current no-body frontend call still binds). When adjustments are supplied, each + `ShopPOItemId`'s billed quantity is overridden (validated to be between 0 and the item's ordered + `Quantity`) and `Invoice.TotalAmount` is recomputed as `Σ billedQuantity × UnitPrice`; with no + adjustments, `TotalAmount` is left as-is. This does **not** touch `ShopPOItem.QuantityFulfilled` + (that field is owned by the delivery-confirmation flow) — adjustments are billing-only. The + deeper CreditNote-based deduction design from `order_invoice_lifecycle.md` remains unbuilt; this + is a minimal, non-persisted (no adjustment audit trail) implementation. + - `InvoiceRepository.GetByIdAsync` was extended to also include `ShopPO.Items` (previously only + `ShopPO.Store`), needed to compute the adjustment total. + - Matches the frontend's existing `visible` guards in `orders-client.tsx` (`print` shown only when + `Draft`, `finalize` only when `Printed`) — no frontend change needed there, since the frontend + currently calls `finalize` with no body/adjustments. + +`PaymentService.CreateAsync` already blocks payments once `Status == Finalized` — consistent with +this state machine. + +## Frontend work + +Almost everything needed already existed and was correctly wired to the (previously missing) backend +routes above — once the backend work lands, `sales/new`, `sales/orders`, `sales/payments`, +`operations/rides`, `operations/loading` all start working without changes. The one gap: + +- **Trigger for `DeliveryTaskService.GenerateAsync`**: no frontend call existed anywhere. Added a + `generate: (rideId: string) => api.post(\`/rides/${rideId}/tasks/generate\`)` + method to `deliveryTasks` in `distribution_fe/lib/api/operations.ts` (route already defined in + `DeliveryTaskController.cs`), and a "Generate delivery tasks" **row action** on `Confirmed` sheets + in the Loading Sheets table (`operations/loading/_components/loading-sheets-client.tsx`) — not the + read-only `loading-sheet-view-sheet.tsx`, which stayed untouched — since that's where every other + sheet action (Set items, Confirm) already lives as a row action, and that's the point at which + `LoadingSheetOrder` data becomes available. Also required adding `'generate'` to the row-action + icon map, which turned out to be duplicated in two places + (`components/shared/row-actions.tsx`'s `ACTION_ICONS` and `components/shared/data-table/types.ts`'s + `RowActionKind` union) — both needed updating. + +## End-to-end flow (once the above lands) + +1. **Sales rep**: `/sales/new` → creates a `ShopPO` (`Pending`, unconfirmed) for a store with line items. +2. **Confirm PO**: either `confirm` (delivery-later — no invoice yet) or `confirm-and-create-invoice` + (on-the-spot sale — `Invoice` created immediately, `Draft`). +3. **Operations**: `/operations/rides` → create a `Ride` (`Open`) for a vehicle+driver. +4. `/operations/loading` → create a `LoadingSheet` for that ride, set items (optionally tagging each + with the `ShopPOItem` it's fulfilling via `FK_ShopPOItemId`), confirm → stock moves warehouse→vehicle, + `LoadingSheetOrder` rows derived. +5. Trigger **Generate delivery tasks** for the ride → one `DeliveryTask` per PO on the sheet. +6. `/operations/deliveries` → for each task, set delivered quantities, confirm (stock moves + vehicle→external, `ShopPO.Status` synced to `Fulfilled`/`PartiallyFulfilled`) or fail. +7. If not already created in step 2, an `Invoice` should exist by now for delivery-later POs — + **open question, not resolved by this plan**: no endpoint currently creates an Invoice for the + delivery-later path after fulfillment; flagged as a follow-up. +8. `/sales/orders` (Invoices list) → `print` (`Draft → Printed`) → `finalize` (`Printed → Finalized`). +9. `/sales/payments` → record payments against the invoice until `PaymentStatus = Paid` (blocked once + `Finalized`). +10. Back in `/operations/rides` → **close** the ride (requires all vehicle stock cleared, i.e. every + loaded item delivered or otherwise moved off) or **cancel** it. + +## Addendum: Loading Sheet "Set Items" screen (region/route-filtered PO picker) + +While implementing the above, a further gap was found: the frontend never had any UI to actually set +items on a `LoadingSheet` — `LoadingSheetCreateDialog` only picks a ride and creates an empty +`Planned` sheet; `loadingSheets.updateItems()` existed in the API client but nothing called it. + +Practically, a loading sheet should be built *from* confirmed Shop POs (what to load = what's been +ordered), narrowed by Region/Route so the rep isn't picking through every PO in the system. +`Ride`/`Vehicle`/`DriverProfile` carry no route assignment in this app (rides/drivers/vehicles are +not fixed to a route — confirmed as intentional, so a "derive route from the driver" auto-filter was +rejected in favor of a manual filter). The resolution: + +- **Backend**: `ShopPOController`/`ShopPOService`/`ShopPORepository` `GetAll`/`GetCount` gained + `regionId` and `status` query filters (previously only `agentId`/`routeId`/`shopId`/`confirmed`), + filtering on `Store.Route.FK_RegionId` and `ShopPO.Status`. `GetAllAsync` now also includes each + PO's active `ShopPOItem`s (with `Item`) so the picker can render line items without N+1 calls. +- **Frontend**: new `LoadingSheetItemsDialog` + (`operations/loading/_components/loading-sheet-items-dialog.tsx`), opened via a "Set items" row + action on `Planned` sheets in `loading-sheets-client.tsx`. It has manual Region and Route filter + dropdowns (Route list narrows to the selected Region via the existing `routes.list({ regionId })`), + fetches confirmed `Pending`/`PartiallyFulfilled` POs matching the filter, lists their outstanding + line items (`Quantity - QuantityFulfilled`) with a checkbox and quantity input per line, a single + Warehouse selector applied to all picked lines, and saves via the existing + `loadingSheets.updateItems()` call — which already tags each `LoadingSheetItem` with its source + `FK_ShopPOItemId`, so `LoadingSheetService.SetItemsAsync`'s `LoadingSheetOrder` derivation (see + above) picks it up automatically. +- The "Confirm & commit stock" row action now also requires `sheet.items.length > 0`, since + confirming an item-less sheet was possible before but meaningless. + +## Addendum: Loading Sheets list filterable by Region/Route + +The Loading Sheets list (`loading-sheets-client.tsx`) previously only filtered by Ride and Status. +Since a `LoadingSheet` has no direct region/route of its own, filtering is derived through +`LoadingSheetOrder → ShopPO → Store → Route(.FK_RegionId)` — i.e. "sheets carrying at least one PO +for a store on this route/region." `ILoadingSheetRepository`/`LoadingSheetService`/ +`LoadingSheetController` `GetAll`/`GetCount` gained `routeId`/`regionId` query params +(`s.Orders.Any(o => o.ShopPO.Store.FK_RouteId == routeId)`, same pattern for region). Frontend added +matching Region/Route filter dropdowns (Route list narrows to the selected Region), consistent with +the `LoadingSheetItemsDialog` filter UX added above. + +## Open questions (not resolved by this pass) + +- No endpoint creates an `Invoice` for the delivery-later path (`ConfirmAsync`, not + `ConfirmAndCreateInvoiceAsync`) once fulfillment completes. `IShopPORepository.GetConfirmedWithoutInvoiceAsync` + already exists and looks purpose-built for this — likely intended for a future + "generate invoices for confirmed-but-unbilled POs" job or endpoint, not built in this pass. +- Multi-invoice-per-PO, partial invoicing, and Invoice/OrderStatus axis merging remain unresolved, + consistent with `order_invoice_lifecycle.md`. + +## Known environment issue (unrelated to this pipeline's code) + +While verifying this pipeline live, every non-GET request (`POST`/`OPTIONS`, with or without a body, +including to nonexistent routes and endpoints untouched by this work like `/api/auth/login`) hung +indefinitely against the local backend, while every `GET` responded instantly. Since even a `POST` to +a route that doesn't exist hangs before reaching routing/controller code, this rules out an +application bug and points to something intercepting/filtering non-GET loopback HTTP traffic on the +machine (antivirus/EDR web/HTTP inspection, or a proxy/VPN). Restarting the backend process did not +resolve it. Not a defect in the code documented above — flagged here so it isn't mistaken for one +during future verification. + +## Verification + +- `dotnet build Distribution/Distribution.csproj` clean after each service/controller addition. +- Manually exercise the flow above via the existing frontend screens end-to-end against the local + backend (fresh `ShopPO` through to `Payment`), confirming each previously-404ing action (`create`, + `updateItems`, `confirm`, `confirmAndCreateInvoice`, `cancel`, `print`, `finalize`, `generate`) now + succeeds and the UI's existing `visible` guards match the state machine. +- `npx tsc --noEmit -p tsconfig.json` in `distribution_fe` after the frontend addition (generate + trigger + api client method). From 106ed5789786f8e083429a279d91a64d9dc03660 Mon Sep 17 00:00:00 2001 From: DeepnaPooja Date: Thu, 8 Oct 2026 12:55:45 +0530 Subject: [PATCH 7/9] Implement GRN tax settings UI and calculations for VAT and NBT --- .../_components/grn-tax-card.tsx | 171 ++++++++++++++++++ .../_components/product-capabilities-card.tsx | 47 +++-- .../grn/_components/grn-costing.tsx | 53 ++++-- .../grn/_components/grn-tracking.tsx | 48 ++++- .../grn/_components/grn-view-sheet.tsx | 5 + 5 files changed, 279 insertions(+), 45 deletions(-) create mode 100644 distribution_fe/app/(dashboard)/configuration/_components/grn-tax-card.tsx diff --git a/distribution_fe/app/(dashboard)/configuration/_components/grn-tax-card.tsx b/distribution_fe/app/(dashboard)/configuration/_components/grn-tax-card.tsx new file mode 100644 index 0000000..c4b104e --- /dev/null +++ b/distribution_fe/app/(dashboard)/configuration/_components/grn-tax-card.tsx @@ -0,0 +1,171 @@ +'use client' + +import { useEffect, useState } from 'react' +import { HugeiconsIcon } from '@hugeicons/react' +import { Discount01Icon } from '@hugeicons/core-free-icons' +import { Input } from '@/components/ui/input' +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select' +import { Card, CardContent, CardHeader, CardTitle, CardDescription } from '@/components/ui/card' +import { FieldLabel, FieldDescription } from '@/components/ui/field' +import { toast } from '@/components/ui/toast' +import { + sysConfig, + grnTaxSettings, + GRN_TAX_KEYS, + DEFAULT_GRN_TAX_SETTINGS, + getApiError, +} from '@/lib/api/index' +import type { GrnTaxSettings, TaxMode } from '@/lib/api/sys-config' +import { useAuth } from '@/contexts/auth-context' + +const MODE_LABELS: Record = { + Disabled: 'Not applied', + Optional: 'Optional', + Mandatory: 'Mandatory', +} + +const MODE_HINTS: Record = { + Disabled: 'Hidden on GRN', + Optional: 'Shown, can be 0', + Mandatory: 'Shown, must be filled', +} + +interface TaxRowProps { + id: string + title: string + description: string + mode: TaxMode + rate: number + pending: boolean + onModeChange: (mode: TaxMode) => void + onRateCommit: (rate: number) => void +} + +function TaxRow({ id, title, description, mode, rate, pending, onModeChange, onRateCommit }: TaxRowProps) { + // Edit the rate as text and only save when it is valid and the field loses focus. + const [text, setText] = useState(String(rate)) + useEffect(() => setText(String(rate)), [rate]) + + function commit() { + const n = Number(text) + if (text.trim() === '' || !Number.isFinite(n) || n < 0 || n > 100) { + setText(String(rate)) + return + } + if (n !== rate) onRateCommit(n) + } + + return ( +
+
+ + {title} · {description} + + {MODE_HINTS[mode]} +
+ +
+ Applies on GRN + +
+ +
+ Default rate +
+ setText(e.target.value)} + onBlur={commit} + className="pr-8" + /> + % +
+
+
+ ) +} + +/** GRN taxes: whether VAT and/or NBT apply to goods receipts, whether each is mandatory, and its default rate. */ +export function GrnTaxCard() { + const { user } = useAuth() + const [loading, setLoading] = useState(true) + const [settings, setSettings] = useState(DEFAULT_GRN_TAX_SETTINGS) + const [pending, setPending] = useState>({}) + + useEffect(() => { + grnTaxSettings.get() + .then(setSettings) + .catch(err => toast.add({ title: 'Failed to load GRN tax settings', description: getApiError(err), type: 'error' })) + .finally(() => setLoading(false)) + }, []) + + // Saves one key immediately and rolls the field back if the server refuses it. + async function save(field: keyof GrnTaxSettings, configKey: string, value: TaxMode | number) { + const previous = settings[field] + setSettings(s => ({ ...s, [field]: value })) + setPending(p => ({ ...p, [configKey]: true })) + try { + await sysConfig.set(configKey, String(value), user.id) + } catch (err) { + setSettings(s => ({ ...s, [field]: previous })) + toast.add({ title: 'Failed to update configuration', description: getApiError(err), type: 'error' }) + } finally { + setPending(p => ({ ...p, [configKey]: false })) + } + } + + if (loading) return

Loading...

+ + return ( + + +
+
+ +
+
+ GRN Taxes + VAT and NBT on goods receipts +
+
+
+ + save('vatMode', GRN_TAX_KEYS.vatMode, m)} + onRateCommit={r => save('vatDefaultRate', GRN_TAX_KEYS.vatRate, r)} + /> + save('nbtMode', GRN_TAX_KEYS.nbtMode, m)} + onRateCommit={r => save('nbtDefaultRate', GRN_TAX_KEYS.nbtRate, r)} + /> + +
+ ) +} diff --git a/distribution_fe/app/(dashboard)/configuration/_components/product-capabilities-card.tsx b/distribution_fe/app/(dashboard)/configuration/_components/product-capabilities-card.tsx index 85e4a3c..42136d7 100644 --- a/distribution_fe/app/(dashboard)/configuration/_components/product-capabilities-card.tsx +++ b/distribution_fe/app/(dashboard)/configuration/_components/product-capabilities-card.tsx @@ -45,7 +45,7 @@ function CapabilityToggle({ id, label, description, checked, disabled, onChecked return (
@@ -119,25 +119,24 @@ export function ProductCapabilitiesCard() { return loading ? (

Loading...

) : ( - - -
-
- + + +
+
+ +
+
+ Products + Item tracking options
- Products
- - Serial number, batch, expiry and warranty tracking for catalogue items. Each switch applies immediately; - items then opt into the enabled capabilities from the item's edit form. -
- -
+ +
updateToggle('serialNumberTracking', ITEM_TRACKING_KEYS.serialNumberTracking, v)} @@ -146,7 +145,7 @@ export function ProductCapabilitiesCard() { updateToggle('batchTracking', ITEM_TRACKING_KEYS.batchTracking, v)} @@ -155,7 +154,7 @@ export function ProductCapabilitiesCard() { updateToggle('expiryDateTracking', ITEM_TRACKING_KEYS.expiryDateTracking, v)} @@ -164,7 +163,7 @@ export function ProductCapabilitiesCard() { updateToggle('warrantyTracking', ITEM_TRACKING_KEYS.warrantyTracking, v)} @@ -173,24 +172,24 @@ export function ProductCapabilitiesCard() { {form.warrantyTracking && ( <> - + -
+

Warranty defaults

-
+
updateToggle('customWarrantyOverride', ITEM_TRACKING_KEYS.customWarrantyOverride, v)} /> -
+
Default warranty period @@ -206,9 +205,7 @@ export function ProductCapabilitiesCard() { /> days
- - Applied to items that don't set their own warranty period. - + Used when an item sets none
diff --git a/distribution_fe/app/(dashboard)/procurement/grn/_components/grn-costing.tsx b/distribution_fe/app/(dashboard)/procurement/grn/_components/grn-costing.tsx index dcd3563..92ab378 100644 --- a/distribution_fe/app/(dashboard)/procurement/grn/_components/grn-costing.tsx +++ b/distribution_fe/app/(dashboard)/procurement/grn/_components/grn-costing.tsx @@ -13,6 +13,7 @@ import type { BatchRow } from './grn-tracking' /** Preset GRN-wide percentages. Any line can still be typed to a different value. */ export const DISCOUNT_OPTIONS = [0, 2.5, 5, 10, 15, 20, 25] export const VAT_OPTIONS = [0, 5, 8, 12, 15, 18] +export const NBT_OPTIONS = [0, 2] // Same rule as the server (GrnPricing): 2 dp, halves round away from zero, rounded per line. const round2 = (n: number) => Math.round(Number((n * 100).toPrecision(12))) / 100 @@ -21,23 +22,30 @@ export interface CostedLine { acceptedQuantity: number unitCost: number discountPercent: number + nbtPercent: number vatPercent: number } -/** gross = qty x cost; discount on gross; VAT on (gross - discount); total = gross - discount + VAT. */ +/** + * gross = qty x cost; discount on gross; NBT on (gross - discount); VAT on (gross - discount + NBT); + * total = gross - discount + NBT + VAT. Mirrors the server (GrnPricing.Line). + */ export function lineAmounts(l: CostedLine) { const gross = round2((l.acceptedQuantity || 0) * (l.unitCost || 0)) const discount = round2((gross * (l.discountPercent || 0)) / 100) - const vat = round2(((gross - discount) * (l.vatPercent || 0)) / 100) - return { gross, discount, vat, total: gross - discount + vat } + const net = gross - discount + const nbt = round2((net * (l.nbtPercent || 0)) / 100) + const vat = round2(((net + nbt) * (l.vatPercent || 0)) / 100) + return { gross, discount, nbt, vat, total: net + nbt + vat } } export function grnTotals(lines: CostedLine[]) { const amounts = lines.map(lineAmounts) const subTotal = amounts.reduce((s, a) => s + a.gross, 0) const discount = amounts.reduce((s, a) => s + a.discount, 0) + const nbt = amounts.reduce((s, a) => s + a.nbt, 0) const vat = amounts.reduce((s, a) => s + a.vat, 0) - return { subTotal, discount, taxable: subTotal - discount, vat, total: subTotal - discount + vat } + return { subTotal, discount, taxable: subTotal - discount, nbt, vat, total: subTotal - discount + nbt + vat } } export const money = (n: number) => @@ -69,6 +77,10 @@ interface PercentSelectProps { value: number options: number[] onChange: (value: number) => void + /** A required percentage can't be left blank (0 is fine); optional ones fall back to the last value. */ + required?: boolean + /** Reports whether a required field is currently blank, so the form can block saving. */ + onBlankChange?: (blank: boolean) => void } /** @@ -76,30 +88,44 @@ interface PercentSelectProps { * locally while typing so "2." or "" don't get rewritten under the cursor; the number is only pushed * up when it is valid. */ -export function PercentSelect({ label, value, options, onChange }: PercentSelectProps) { +export function PercentSelect({ label, value, options, onChange, required, onBlankChange }: PercentSelectProps) { const listId = useId() const [text, setText] = useState(String(value)) // Follow outside changes (e.g. a reset) without fighting what the user is typing. useEffect(() => { - setText(prev => (Number(prev) === value && prev !== '' ? prev : String(value))) + setText(prev => (prev.trim() !== '' && Number(prev) === value ? prev : String(value))) + onBlankChange?.(false) + // eslint-disable-next-line react-hooks/exhaustive-deps }, [value]) + const blank = text.trim() === '' + function handleChange(raw: string) { setText(raw) const n = Number(raw) - if (raw.trim() !== '' && Number.isFinite(n) && n >= 0 && n <= 100) onChange(n) + if (raw.trim() === '') { + onBlankChange?.(!!required) + } else if (Number.isFinite(n) && n >= 0 && n <= 100) { + onChange(n) + onBlankChange?.(false) + } } - // An empty or out-of-range entry falls back to the last valid value on blur. + // An out-of-range entry falls back to the last valid value on blur; a blank one does too, unless + // the field is required (then it stays blank so the "Required" message and blocked save are visible). function handleBlur() { const n = Number(text) - if (text.trim() === '' || !Number.isFinite(n) || n < 0 || n > 100) setText(String(value)) + if (blank && required) return + if (blank || !Number.isFinite(n) || n < 0 || n > 100) setText(String(value)) } return ( - {label} + + {label} + {required && *} +
handleChange(e.target.value)} onBlur={handleBlur} + aria-invalid={required && blank ? true : undefined} className="pr-8" /> @@ -123,6 +150,7 @@ export function PercentSelect({ label, value, options, onChange }: PercentSelect ))}
+ {required && blank &&

{label} is required

}
) } @@ -157,13 +185,14 @@ export function NumberCell({ control, name, step = 1, max } /** Totals block under the items table. */ -export function GrnTotals({ lines }: { lines: CostedLine[] }) { +export function GrnTotals({ lines, showNbt, showVat }: { lines: CostedLine[]; showNbt: boolean; showVat: boolean }) { const t = grnTotals(lines) const rows: [string, string][] = [ ['Subtotal', money(t.subTotal)], ['Discount', `- ${money(t.discount)}`], ['Taxable amount', money(t.taxable)], - ['VAT', money(t.vat)], + ...(showNbt ? ([['NBT', money(t.nbt)]] as [string, string][]) : []), + ...(showVat ? ([['VAT', money(t.vat)]] as [string, string][]) : []), ] return (
diff --git a/distribution_fe/app/(dashboard)/procurement/grn/_components/grn-tracking.tsx b/distribution_fe/app/(dashboard)/procurement/grn/_components/grn-tracking.tsx index 0922e12..48691e2 100644 --- a/distribution_fe/app/(dashboard)/procurement/grn/_components/grn-tracking.tsx +++ b/distribution_fe/app/(dashboard)/procurement/grn/_components/grn-tracking.tsx @@ -12,6 +12,7 @@ import { DialogTitle, } from '@/components/ui/dialog' import { toast } from '@/components/ui/toast' +import { stock as stockApi } from '@/lib/api/stock' import type { GrnBatchLineBody } from '@/lib/api/procurement' import type { ItemSummaryResponse } from '@/types/api' @@ -71,6 +72,8 @@ interface LineTrackingProps { batches: BatchRow[] onSerialText: (v: string) => void onBatches: (v: BatchRow[]) => void + /** Serials already typed on other lines of this GRN for the same item, so generation never repeats them. */ + otherSerials?: string[] } /** @@ -78,11 +81,21 @@ interface LineTrackingProps { * entry, so long serial lists don't stretch the form. Serial tracking takes precedence over * batch/expiry (same as the server). Warranty is not entered here — its clock starts at sale. */ -export function LineTracking({ info, name, accepted, serialText, batches, onSerialText, onBatches }: LineTrackingProps) { +export function LineTracking({ + info, + name, + accepted, + serialText, + batches, + onSerialText, + onBatches, + otherSerials = [], +}: LineTrackingProps) { const [panelOpen, setPanelOpen] = useState(false) const [scanValue, setScanValue] = useState('') const [genPrefix, setGenPrefix] = useState(`${info.sku}-`) const [genStart, setGenStart] = useState(1) + const [generating, setGenerating] = useState(false) const progress = trackingProgress(info, accepted, serialText, batches) if (!progress) return null @@ -98,19 +111,38 @@ export function LineTracking({ info, name, accepted, serialText, batches, onSeri setScanValue('') } - // Appends generated serials (prefix + zero-padded sequence) for the units still missing, - // skipping any value already in the list. They stay editable in the list below. - const generateSerials = () => { + // Appends generated serials (prefix + zero-padded sequence) for the units still missing. Numbering + // continues after the highest number already saved for this item (asked from the server), so a + // second receipt of the same item gets 0006–0010 instead of repeating 0001–0005. Values already in + // this list, or on other lines of this GRN, are skipped. They stay editable in the list below. + const generateSerials = async () => { const existing = parseSerials(serialText) - const taken = new Set(existing) + const taken = new Set([...existing, ...otherSerials]) const missing = Math.max(0, accepted - existing.length) + + setGenerating(true) + let start = genStart + try { + start = Math.max(genStart, await stockApi.nextSerialSequence(info.id, genPrefix)) + } catch { + toast.add({ + title: 'Could not check existing serial numbers', + description: 'Numbering starts from the value shown; the server will reject any serial that already exists.', + type: 'error', + }) + } finally { + setGenerating(false) + } + const generated: string[] = [] - for (let n = genStart; generated.length < missing; n++) { + let n = start + while (generated.length < missing) { const sn = `${genPrefix}${String(n).padStart(4, '0')}` if (!taken.has(sn)) generated.push(sn) + n++ } onSerialText([...existing, ...generated].join('\n')) - setGenStart(genStart + missing) + setGenStart(n) } const isSerial = progress.kind === 'serial' @@ -180,7 +212,7 @@ export function LineTracking({ info, name, accepted, serialText, batches, onSeri
From 25b21d6fa846f2102b9202e0a0e49f5fcee20926 Mon Sep 17 00:00:00 2001 From: DeepnaPooja Date: Thu, 8 Oct 2026 12:57:48 +0530 Subject: [PATCH 8/9] Refactor GRN configuration page layout --- .../app/(dashboard)/configuration/page.tsx | 11 +- .../(dashboard)/procurement/grn/new/page.tsx | 124 ++++++++++++++---- distribution_fe/lib/api/index.ts | 2 +- distribution_fe/lib/api/procurement.ts | 3 + distribution_fe/lib/api/stock.ts | 6 + distribution_fe/lib/api/sys-config.ts | 50 +++++++ distribution_fe/types/api.ts | 6 +- 7 files changed, 176 insertions(+), 26 deletions(-) diff --git a/distribution_fe/app/(dashboard)/configuration/page.tsx b/distribution_fe/app/(dashboard)/configuration/page.tsx index b787920..fb8c58d 100644 --- a/distribution_fe/app/(dashboard)/configuration/page.tsx +++ b/distribution_fe/app/(dashboard)/configuration/page.tsx @@ -1,6 +1,7 @@ 'use client' import { ProductCapabilitiesCard } from './_components/product-capabilities-card' +import { GrnTaxCard } from './_components/grn-tax-card' export default function ConfigurationPage() { return ( @@ -10,7 +11,15 @@ export default function ConfigurationPage() {

System-wide settings, grouped by area

- + {/* 12-column grid: each card takes 6 columns so the two sit side by side; they stack on narrow screens. */} +
+
+ +
+
+ +
+
) } diff --git a/distribution_fe/app/(dashboard)/procurement/grn/new/page.tsx b/distribution_fe/app/(dashboard)/procurement/grn/new/page.tsx index 0ee05bd..c1ec526 100644 --- a/distribution_fe/app/(dashboard)/procurement/grn/new/page.tsx +++ b/distribution_fe/app/(dashboard)/procurement/grn/new/page.tsx @@ -20,6 +20,7 @@ import { Field, FieldGroup, FieldLabel } from '@/components/ui/field' import { Separator } from '@/components/ui/separator' import { toast } from '@/components/ui/toast' import type { GrnItemBody } from '@/lib/api/procurement' +import type { GrnTaxSettings } from '@/lib/api/sys-config' import { grn as grnApi, purchaseOrders as purchaseOrdersApi, @@ -27,6 +28,8 @@ import { users as usersApi, suppliers as suppliersApi, items as itemsApi, + grnTaxSettings, + DEFAULT_GRN_TAX_SETTINGS, getApiError, getPaginatedItems, } from '@/lib/api/index' @@ -37,10 +40,11 @@ import type { SupplierResponse, ItemSummaryResponse, } from '@/types/api' -import { LineTracking, trackingPayload, trackingProgress } from '../_components/grn-tracking' +import { LineTracking, parseSerials, trackingPayload, trackingProgress } from '../_components/grn-tracking' import { DISCOUNT_OPTIONS, VAT_OPTIONS, + NBT_OPTIONS, GrnTotals, NumberCell, PercentSelect, @@ -64,6 +68,7 @@ const lineSchema = z.object({ freeQuantity: z.number().min(0, 'Must be 0 or more'), unitCost: z.number().min(0, 'Must be 0 or more'), discountPercent: percent, + nbtPercent: percent, vatPercent: percent, // One serial number per line (newline or comma separated) — used for serial-tracked items. serialText: z.string(), @@ -77,6 +82,7 @@ const schema = z.object({ receivedAt: z.string().min(1, 'Received date is required'), receivedByUserId: z.string().min(1, 'Received by is required'), discountPercent: percent, + nbtPercent: percent, vatPercent: percent, items: z.array(lineSchema).min(1, 'Add at least one item'), }) @@ -91,6 +97,7 @@ const emptyLine = { freeQuantity: 0, unitCost: 0, discountPercent: 0, + nbtPercent: 0, vatPercent: 0, serialText: '', batches: [], @@ -116,6 +123,9 @@ export default function NewGrnPage() { const [poId, setPoId] = useState('') const [selectedPO, setSelectedPO] = useState(null) const [loadingPO, setLoadingPO] = useState(false) + // Which taxes apply to a GRN, and whether each is mandatory (Configuration → GRN Taxes). + const [tax, setTax] = useState(DEFAULT_GRN_TAX_SETTINGS) + const [taxBlank, setTaxBlank] = useState({ vat: false, nbt: false }) // Tracking flags for items loaded from a PO, keyed by itemId (the PO line response doesn't carry them). const [poItemInfo, setPoItemInfo] = useState>({}) @@ -127,6 +137,7 @@ export default function NewGrnPage() { receivedAt: todayIso(), receivedByUserId: '', discountPercent: 0, + nbtPercent: 0, vatPercent: 0, items: [{ ...emptyLine }], }, @@ -154,9 +165,25 @@ export default function NewGrnPage() { .catch(err => toast.add({ title: 'Failed to load reference data', description: getApiError(err), type: 'error' }) ) + + // New GRNs start from the configured default rates; a disabled tax stays at 0. + grnTaxSettings + .get() + .then(t => { + setTax(t) + applyToAllLines('vatPercent', t.vatMode === 'Disabled' ? 0 : t.vatDefaultRate) + applyToAllLines('nbtPercent', t.nbtMode === 'Disabled' ? 0 : t.nbtDefaultRate) + }) + .catch(err => + toast.add({ title: 'Failed to load GRN tax settings', description: getApiError(err), type: 'error' }) + ) + // eslint-disable-next-line react-hooks/exhaustive-deps }, []) const isPoMode = mode === 'po' + const vatOn = tax.vatMode !== 'Disabled' + const nbtOn = tax.nbtMode !== 'Disabled' + const colCount = 9 + (vatOn ? 1 : 0) + (nbtOn ? 1 : 0) const infoFor = (itemId: string) => poItemInfo[itemId] ?? itemsList.find(i => i.id === itemId) const poLines = selectedPO?.items?.filter(i => !i.isCancelled) ?? [] const poLine = (poItemId: string) => poLines.find(i => i.id === poItemId) @@ -164,6 +191,7 @@ export default function NewGrnPage() { const watchedItems = form.watch('items') const headerDiscount = form.watch('discountPercent') const headerVat = form.watch('vatPercent') + const headerNbt = form.watch('nbtPercent') // Block Save until every tracked line has its serials/batches fully entered (accepted + free units). const trackingIncomplete = watchedItems.some(v => { const p = trackingProgress(infoFor(v.itemId), v.acceptedQuantity + v.freeQuantity, v.serialText, v.batches) @@ -171,9 +199,9 @@ export default function NewGrnPage() { }) // The GRN-wide percentage is a default: choosing one fills every line, and a line can still be edited. - function applyToAllLines(field: 'discountPercent' | 'vatPercent', value: number) { + function applyToAllLines(field: 'discountPercent' | 'vatPercent' | 'nbtPercent', value: number) { form.setValue(field, value, { shouldDirty: true }) - fields.forEach((_, i) => form.setValue(`items.${i}.${field}`, value, { shouldDirty: true })) + form.getValues('items').forEach((_, i) => form.setValue(`items.${i}.${field}`, value, { shouldDirty: true })) } function changeMode(next: GrnMode) { @@ -183,9 +211,10 @@ export default function NewGrnPage() { setSelectedPO(null) const discountPercent = form.getValues('discountPercent') const vatPercent = form.getValues('vatPercent') + const nbtPercent = form.getValues('nbtPercent') // Direct starts with one blank line; Against PO stays empty until an order is picked. form.setValue('supplierId', NO_SUPPLIER) - replace(next === 'direct' ? [{ ...emptyLine, discountPercent, vatPercent }] : []) + replace(next === 'direct' ? [{ ...emptyLine, discountPercent, vatPercent, nbtPercent }] : []) } // A line in PO mode points at one of the order's lines; re-picking resets the cost and tracking. @@ -203,6 +232,7 @@ export default function NewGrnPage() { setPoId(nextId) const discountPercent = form.getValues('discountPercent') const vatPercent = form.getValues('vatPercent') + const nbtPercent = form.getValues('nbtPercent') setLoadingPO(true) try { @@ -225,6 +255,7 @@ export default function NewGrnPage() { unitCost: item.unitCost, discountPercent, vatPercent, + nbtPercent, })), ) } catch (err) { @@ -266,7 +297,8 @@ export default function NewGrnPage() { freeQuantity: v.freeQuantity, unitCost: v.unitCost, discountPercent: v.discountPercent, - vatPercent: v.vatPercent, + ...(vatOn ? { vatPercent: v.vatPercent } : {}), + ...(nbtOn ? { nbtPercent: v.nbtPercent } : {}), ...payload, }) } @@ -279,7 +311,8 @@ export default function NewGrnPage() { receivedAt: `${values.receivedAt}T00:00:00.000Z`, items, discountPercent: values.discountPercent, - vatPercent: values.vatPercent, + ...(vatOn ? { vatPercent: values.vatPercent } : {}), + ...(nbtOn ? { nbtPercent: values.nbtPercent } : {}), receivedByUserId: values.receivedByUserId, }) } else { @@ -288,7 +321,8 @@ export default function NewGrnPage() { supplierId: values.supplierId === NO_SUPPLIER ? undefined : values.supplierId, receivedByUserId: values.receivedByUserId, discountPercent: values.discountPercent, - vatPercent: values.vatPercent, + ...(vatOn ? { vatPercent: values.vatPercent } : {}), + ...(nbtOn ? { nbtPercent: values.nbtPercent } : {}), items, }) } @@ -429,12 +463,27 @@ export default function NewGrnPage() { onChange={v => applyToAllLines('discountPercent', v)} /> - applyToAllLines('vatPercent', v)} - /> + {nbtOn && ( + setTaxBlank(b => ({ ...b, nbt: blank }))} + onChange={v => applyToAllLines('nbtPercent', v)} + /> + )} + + {vatOn && ( + setTaxBlank(b => ({ ...b, vat: blank }))} + onChange={v => applyToAllLines('vatPercent', v)} + /> + )}
@@ -477,7 +526,9 @@ export default function NewGrnPage() { variant="outline" size="sm" disabled={isPoMode && !selectedPO} - onClick={() => append({ ...emptyLine, discountPercent: headerDiscount, vatPercent: headerVat })} + onClick={() => + append({ ...emptyLine, discountPercent: headerDiscount, vatPercent: headerVat, nbtPercent: headerNbt }) + } > Add Item @@ -505,7 +556,8 @@ export default function NewGrnPage() { Free Qty Unit Cost Disc % - VAT % + {nbtOn && NBT %} + {vatOn && VAT %} Line Total @@ -519,7 +571,8 @@ export default function NewGrnPage() { acceptedQuantity: v?.acceptedQuantity ?? 0, unitCost: v?.unitCost ?? 0, discountPercent: v?.discountPercent ?? 0, - vatPercent: v?.vatPercent ?? 0, + nbtPercent: nbtOn ? (v?.nbtPercent ?? 0) : 0, + vatPercent: vatOn ? (v?.vatPercent ?? 0) : 0, }) return ( @@ -596,9 +649,16 @@ export default function NewGrnPage() { - - - + {nbtOn && ( + + + + )} + {vatOn && ( + + + + )} {money(amounts.total)} @@ -619,11 +679,14 @@ export default function NewGrnPage() { {info && v && ( - + + j !== index && o.itemId === v.itemId ? parseSerials(o.serialText) : [], + )} serialText={v.serialText} batches={v.batches} onSerialText={val => form.setValue(`items.${index}.serialText`, val)} @@ -639,7 +702,15 @@ export default function NewGrnPage() {
- + ({ + ...v, + nbtPercent: nbtOn ? v.nbtPercent : 0, + vatPercent: vatOn ? v.vatPercent : 0, + }))} + showNbt={nbtOn} + showVat={vatOn} + /> )}
@@ -648,7 +719,14 @@ export default function NewGrnPage() { diff --git a/distribution_fe/lib/api/index.ts b/distribution_fe/lib/api/index.ts index ed4c5f6..08ff4ca 100644 --- a/distribution_fe/lib/api/index.ts +++ b/distribution_fe/lib/api/index.ts @@ -11,7 +11,7 @@ export { stock } from './stock' export { productReports } from './reports' export { shopPos, invoices, payments } from './sales' export { saleReturns, claims, supplierClaims } from './returns-claims' -export { sysConfig, ITEM_TRACKING_KEYS } from './sys-config' +export { sysConfig, ITEM_TRACKING_KEYS, GRN_TAX_KEYS, grnTaxSettings, DEFAULT_GRN_TAX_SETTINGS } from './sys-config' export { getPaginatedItems } from './paginated' export type { PageParams } from './master' diff --git a/distribution_fe/lib/api/procurement.ts b/distribution_fe/lib/api/procurement.ts index 560ff38..b1efefe 100644 --- a/distribution_fe/lib/api/procurement.ts +++ b/distribution_fe/lib/api/procurement.ts @@ -82,6 +82,7 @@ export interface GrnItemBody { /** Omitted falls back to the GRN-wide percentage. */ discountPercent?: number vatPercent?: number + nbtPercent?: number /** Free-issue units: stocked but not costed. Serials/batches must cover accepted + free. */ freeQuantity?: number } @@ -94,6 +95,7 @@ export interface GrnBody { /** GRN-wide defaults for lines that don't set their own. */ discountPercent?: number vatPercent?: number + nbtPercent?: number // TEMPORARY: required while auth doesn't resolve a logged-in user server-side — see testbug.md #28. receivedByUserId: string } @@ -104,6 +106,7 @@ export interface DirectGrnBody { items: GrnItemBody[] discountPercent?: number vatPercent?: number + nbtPercent?: number // TEMPORARY: required while auth doesn't resolve a logged-in user server-side — see testbug.md #28. receivedByUserId: string } diff --git a/distribution_fe/lib/api/stock.ts b/distribution_fe/lib/api/stock.ts index bdfbebe..b663843 100644 --- a/distribution_fe/lib/api/stock.ts +++ b/distribution_fe/lib/api/stock.ts @@ -47,6 +47,12 @@ export const stock = { adjust: (body: AdjustmentBody) => api.post('/stock/adjustment', body).then(r => r.data), + /** Next free number for auto-generated serials: one past the highest `` already saved for the item. */ + nextSerialSequence: (itemId: string, prefix: string) => + api + .get<{ next: number }>('/stock/serials/next-sequence', { params: { itemId, prefix } }) + .then(r => r.data.next), + updateMovementReason: (id: string, reasonCode: string) => api.patch(`/stock/movements/${id}/reason`, { reasonCode }).then(r => r.data), } diff --git a/distribution_fe/lib/api/sys-config.ts b/distribution_fe/lib/api/sys-config.ts index d68b953..6699096 100644 --- a/distribution_fe/lib/api/sys-config.ts +++ b/distribution_fe/lib/api/sys-config.ts @@ -22,3 +22,53 @@ export const sysConfig = { set: (key: string, value: string, userId?: string | null) => api.put(`/sys-config/${key}`, { value, userId }).then(r => r.data), } + +// ─── GRN tax settings (must match Distribution.Features.SysConfig.SysConfigService) ────────── + +export type TaxMode = 'Disabled' | 'Optional' | 'Mandatory' + +export const GRN_TAX_KEYS = { + vatMode: 'Grn.Tax.Vat.Mode', + vatRate: 'Grn.Tax.Vat.DefaultRate', + nbtMode: 'Grn.Tax.Nbt.Mode', + nbtRate: 'Grn.Tax.Nbt.DefaultRate', +} as const + +export interface GrnTaxSettings { + vatMode: TaxMode + vatDefaultRate: number + nbtMode: TaxMode + nbtDefaultRate: number +} + +const TAX_MODES: TaxMode[] = ['Disabled', 'Optional', 'Mandatory'] + +/** Unconfigured, VAT is Optional (as GRNs behaved before the setting existed) and NBT is off. */ +export const DEFAULT_GRN_TAX_SETTINGS: GrnTaxSettings = { + vatMode: 'Optional', + vatDefaultRate: 0, + nbtMode: 'Disabled', + nbtDefaultRate: 0, +} + +export function parseGrnTaxSettings(byKey: Record): GrnTaxSettings { + const mode = (raw: string | undefined, fallback: TaxMode): TaxMode => + TAX_MODES.includes(raw as TaxMode) ? (raw as TaxMode) : fallback + const rate = (raw: string | undefined) => { + const n = Number(raw) + return raw !== undefined && Number.isFinite(n) && n >= 0 && n <= 100 ? n : 0 + } + return { + vatMode: mode(byKey[GRN_TAX_KEYS.vatMode], DEFAULT_GRN_TAX_SETTINGS.vatMode), + vatDefaultRate: rate(byKey[GRN_TAX_KEYS.vatRate]), + nbtMode: mode(byKey[GRN_TAX_KEYS.nbtMode], DEFAULT_GRN_TAX_SETTINGS.nbtMode), + nbtDefaultRate: rate(byKey[GRN_TAX_KEYS.nbtRate]), + } +} + +export const grnTaxSettings = { + get: () => + sysConfig + .getAll() + .then(all => parseGrnTaxSettings(Object.fromEntries(all.map(c => [c.key, c.value])))), +} diff --git a/distribution_fe/types/api.ts b/distribution_fe/types/api.ts index 90c0766..2e6a440 100644 --- a/distribution_fe/types/api.ts +++ b/distribution_fe/types/api.ts @@ -429,6 +429,8 @@ export interface GrnItemResponse { lineTotal: number /** Free-issue units received at no charge (in stock, not costed). */ freeQuantity: number + nbtPercent: number + nbtAmount: number } export interface GrnResponse { @@ -447,10 +449,12 @@ export interface GrnResponse { vatPercent: number subTotal: number discountAmount: number - /** Subtotal - discount; the base VAT is charged on. */ + /** Subtotal - discount; the base NBT is charged on (VAT is charged on this plus NBT). */ taxableAmount: number vatAmount: number totalAmount: number + nbtPercent: number + nbtAmount: number } export interface PurchaseReturnItemResponse { From dd8f50f54cfe0006081b590ad683ed6fc02fbe31 Mon Sep 17 00:00:00 2001 From: DeepnaPooja Date: Thu, 8 Oct 2026 12:59:08 +0530 Subject: [PATCH 9/9] Update docs for procurement and shop PO processes, including GRN rework functionality --- Distribution/docs/15_procurement.md | 45 ++++++-- .../docs/shop_po_invoice_operations_flow.md | 105 ++++++++++++++++++ Distribution/docs/testbug.md | 2 + 3 files changed, 143 insertions(+), 9 deletions(-) create mode 100644 Distribution/docs/shop_po_invoice_operations_flow.md diff --git a/Distribution/docs/15_procurement.md b/Distribution/docs/15_procurement.md index 274d8b9..cb9b576 100644 --- a/Distribution/docs/15_procurement.md +++ b/Distribution/docs/15_procurement.md @@ -97,11 +97,18 @@ PurchaseOrderItem ├── IsCancelled (bool) — true once this line is individually cancelled, default false ``` +> **Update (GRN rework):** a goods receipt no longer requires a PO — see "Direct goods receipt" and +> the costing / free-issue / tax additions in +> [refactor_docs/grn_costing_and_taxes.md](./refactor_docs/grn_costing_and_taxes.md). The tree +> below shows the original design plus those changes, marked *(later)*. + ### GoodsReceipt ``` GoodsReceipt ├── Id (Guid) — PK, from Template -├── FK_PurchaseOrderId (Guid) — required +├── FK_PurchaseOrderId (Guid?) — nullable *(later)*: null = direct receipt (no PO) +├── FK_SupplierId (Guid?) — *(later)* set on a direct receipt; a PO receipt reaches the supplier via the PO +├── DiscountPercent, VatPercent, NbtPercent (decimal 5,2) — *(later)* GRN-wide defaults for lines ├── FK_ReceivedByUserId (Guid) — required ├── FK_WarehouseId (Guid) — required; the warehouse the accepted quantities are received into. │ Not anticipated in the original design — added because @@ -116,9 +123,13 @@ GoodsReceipt GoodsReceiptItem ├── Id (Guid) — PK, from Template ├── FK_GoodsReceiptId (Guid) — required -├── FK_PurchaseOrderItemId (Guid) — required, ties the receipt line back to its ordered line +├── FK_PurchaseOrderItemId (Guid?) — ties the receipt line back to its ordered line; null on a direct receipt +├── FK_ItemId (Guid?) — *(later)* the item, on a direct receipt line ├── AcceptedQuantity (int) — added to warehouse stock ├── RejectedQuantity (int) — damaged/short at receipt, default 0, not added to stock +├── FreeQuantity (int) — *(later)* free-issue units: added to stock, not costed, not counted against the PO +├── UnitCost (decimal 18,2) — *(later)* cost per unit; PO receipts default to the PO line's UnitCost +├── DiscountPercent, NbtPercent, VatPercent (decimal 5,2) — *(later)* per-line rates ``` ### PurchaseReturn @@ -154,11 +165,16 @@ gives one receiving warehouse per PO in practice; see Open Questions for the mul ## Migrations -One migration, applied: 1. **AddProcurement** (`20260824110925_AddProcurement`) — created `PurchaseOrders`, `PurchaseOrderItems`, `GoodsReceipts`, `GoodsReceiptItems`, `PurchaseReturns`, `PurchaseReturnItems`, with FKs to `Suppliers`/`Items`/`Warehouses` and indexes on every FK column. Applied via `dotnet ef database update`. +2. **AddDirectGoodsReceipt** (`20261006070042`) — made the PO links nullable and added the supplier / + item links for direct receipts. +3. **AddGrnPricing**, **AddGrnFreeQuantity**, **AddGrnNbt** (`20261007…`, `20261008…`) — GRN costing, + free issues and NBT. Pricing and free quantity are applied; **AddGrnNbt was still pending** when + last checked, and GRN reads/writes fail until it is applied — see + [refactor_docs/grn_costing_and_taxes.md](./refactor_docs/grn_costing_and_taxes.md) §7. --- @@ -182,11 +198,17 @@ One migration, applied: | `resolveInvoiceVariance(poId, variance, resolution)` | `POST /api/purchase-orders/{id}/resolve-variance` | ### Goods Receipt (`Features/Grn`) +> A PO is **not mandatory** for a goods receipt: `POST /api/goods-receipts/direct` receives stock +> with no PO (no ordered-quantity limit, no PO status, no purchase returns / invoice matching for +> it). See [refactor_docs/grn_costing_and_taxes.md](./refactor_docs/grn_costing_and_taxes.md) §1 for +> when to use which. | Function reference | Endpoint | |---|---| -| `createGoodsReceipt(poId, warehouseId, items[])` | `POST /api/purchase-orders/{id}/goods-receipts` | +| `createGoodsReceipt(poId, warehouseId, items[])` | `POST /api/purchase-orders/{id}/goods-receipts` (also `POST /api/goods-receipts` with `purchaseOrderId` in the body) | +| `createDirectGoodsReceipt(warehouseId, supplierId?, items[])` | `POST /api/goods-receipts/direct` | | `getGoodsReceipt(grnId)` | `GET /api/goods-receipts/{id}` | | `listGoodsReceipts(poId)` | `GET /api/purchase-orders/{id}/goods-receipts` | +| `listGoodsReceipts(page)` | `GET /api/goods-receipts` | ### Purchase Returns (`Features/PurchaseReturn`) | Function reference | Endpoint | @@ -215,8 +237,12 @@ One migration, applied: { "purchaseOrderItemId": "...", "acceptedQuantity": 95, "rejectedQuantity": 5 } ] } ``` -`rejectedQuantity` defaults to 0 if omitted; only `acceptedQuantity` is credited to -`StockBalance`. +`rejectedQuantity` defaults to 0 if omitted. `acceptedQuantity` **plus `freeQuantity`** (free-issue +units, default 0) is credited to `StockBalance`; only accepted units are costed and only accepted +units advance the PO line's `ReceivedQuantity`. Optional pricing fields (`unitCost`, +`discountPercent`, `vatPercent`, `nbtPercent` per line; `discountPercent`, `vatPercent`, +`nbtPercent` for the whole GRN) are described in +[refactor_docs/grn_costing_and_taxes.md](./refactor_docs/grn_costing_and_taxes.md). ### POST `/api/purchase-orders/{id}/returns` ```json @@ -297,9 +323,10 @@ above) per 14's existing target design. - Where should cost-vs-sale-price margin reporting (`PurchaseOrderItem.UnitCost` vs `PriceListItem.Price`/`Item.SalePrice`) live — a read-only endpoint/report in this step, or a later analytics/reporting step? Not a blocker for the core PO→GRN flow. -- Landed cost (freight, duty, taxes allocated onto received stock) is out of scope — - `PurchaseOrderItem.UnitCost` is the only cost basis captured. Defer unless accurate per-unit cost - is needed sooner. +- Landed cost (freight, duty allocated onto received stock) is out of scope. The GRN now records + unit cost, discount, NBT and VAT per line (see + [refactor_docs/grn_costing_and_taxes.md](./refactor_docs/grn_costing_and_taxes.md)), but these do + not feed stock valuation or `Item.CostPrice`, and the PO itself carries no discount or tax. - Reorder triggers (`setReorderPoint`/`sendLowStockAlert`) are not wired to procurement — restocking stays fully manual until Notifications & Alerts exists. - `createPurchaseReturn` assumes a single receiving warehouse per PO (uses the most recent diff --git a/Distribution/docs/shop_po_invoice_operations_flow.md b/Distribution/docs/shop_po_invoice_operations_flow.md new file mode 100644 index 0000000..5b7c6bb --- /dev/null +++ b/Distribution/docs/shop_po_invoice_operations_flow.md @@ -0,0 +1,105 @@ +# Shop PO → Invoice → Operations — Actual Flow (as implemented) + +**Status**: Reference — describes current code behavior, not a plan +**Related docs**: 03_ride_lifecycle.md, 04_loading_event.md, 05_delivery_ride.md, 06_sale_ride.md (original specs — some details below diverge from those specs; this doc reflects what the code actually does today) + +--- + +## Goal + +Trace the end-to-end lifecycle of a customer order from the moment a Shop PO is raised through +billing (Invoice) and physical fulfillment (Ride → Loading Sheet → Delivery Task), including every +status transition and stock movement along the way. + +--- + +## 1. Shop PO (raised by a sales agent, on behalf of a store) + +**Pages**: `/sales` (list), `/sales/new` (create), `/sales/[id]` (detail) +**Backend**: `Features/ShopPO` + +- **Create** (`ShopPOService.CreateAsync`): agent picks a store + items. Unit price resolves from + the active `PriceList` for that store's region, falling back to `Item.SalePrice` if none is + active. Status starts `Pending`. +- **Confirm** (`ConfirmAsync`): sets `ConfirmedAt`/`ConfirmedBy`. Status **stays `Pending`** — + confirming does not change `Status`, it only marks the PO as ready to act on (load/invoice). +- **Cancel**: only allowed while `Status == Pending` (not `PartiallyFulfilled`/`Fulfilled`). +- A confirmed PO with no invoice yet (`ConfirmedAt != null && InvoiceId == null`) is the exact set + `GetConfirmedWithoutInvoiceAsync` and the Loading Sheet picker (`GET /shop-pos?confirmed=true`) + query for. + +## 2. Invoice (billing document, one per Shop PO) + +**Page**: `/sales/orders` (labeled "Orders" in the nav, but it is actually the Invoices list) +**Backend**: `Features/Invoice` + +- Created via `ShopPOService.ConfirmAndCreateInvoiceAsync` — confirms the PO **and** creates an + `Invoice` (`Status: Draft`, `PaymentStatus: Pending`) in one step, linking `ShopPO.InvoiceId`. + (No frontend UI currently calls this endpoint — only the plain `Confirm` action is wired up.) +- Lifecycle: `Draft → Printed → Finalized`. + - Finalizing can adjust the billed quantity per line (capped between `0` and the ordered + quantity) and recomputes `TotalAmount` from the adjusted lines. +- Payments (`payments` API) apply against the invoice, moving `PaymentStatus`: + `Pending → PartiallyPaid → Paid`. + +## 3. Operations: Ride → Loading Sheet → Confirm → Delivery Tasks + +**Pages**: `/operations/rides`, `/operations/loading`, `/operations/loading/new` +**Backend**: `Features/Ride`, `Features/LoadingEvent`, `Features/DeliveryRide` + +1. **Ride** created with `Status: Open`. A Ride is just a Vehicle + Driver pairing — it has **no** + Route field in the current data model (unlike the original 03/06 specs' "ride type" framing). +2. **Loading Sheet** created against an Open ride (`/operations/loading/new`): + - Pick a **Route** (required, first) → confirmed Shop POs for stores on that route load + (`ConfirmedAt != null`, status `Pending`/`PartiallyFulfilled`, remaining quantity > 0). + - Optionally narrow by **Shop** (store) within that route. + - Pick items/quantities to load, a **Warehouse** (single warehouse per sheet), and finally the + **Ride** to load onto. + - Sheet starts `Status: Planned`. +3. **Set items** (`PUT /loading-sheets/{id}/items`) — editable only while `Planned`. Behavior is + **additive/upsert**, keyed by `(FK_ItemId, FK_WarehouseId, FK_ShopPOItemId)`: a matching row's + `QuantityDispatched` is updated in place, a new combination is added, and rows for combinations + not present in the request are left untouched. This lets a shortfall in one warehouse be topped + up from a second warehouse in a later save without losing the first line. +4. **Confirm sheet** (`LoadingSheetService.ConfirmAsync`): for every line, deducts stock from its + Warehouse and credits it to the Ride's Vehicle (`StockMovement.Type = Load`, + `Warehouse → Vehicle`). Sheet → `Status: Confirmed`. This is the only point stock actually + leaves the warehouse. (Note: unlike the 04_loading_event.md spec, confirming a sheet does **not** + transition the Ride's own status — `RideStatus` only has `Open`/`Cancelled`/`Closed`, no + `Active`.) +5. **Generate delivery tasks** (`DeliveryTaskService.GenerateAsync`): once a ride has at least one + `Confirmed` loading sheet, one `DeliveryTask` (`Status: Pending`) is created per distinct Shop PO + that sheet's `LoadingSheetOrder` rows reference. +6. **Set quantities** on each task (`SetQuantitiesAsync`) — how much of each `ShopPOItem` was + actually delivered, capped at the item's ordered quantity. Only allowed while the task is + `Pending`. +7. **Confirm task** (`DeliveryTaskService.ConfirmAsync`) — closes the loop back to the Shop PO: + - Moves stock **off the vehicle** (`StockMovement.Type = Sale`, `Vehicle → External`) for + whatever was fulfilled, failing if the vehicle doesn't actually have enough stock. + - Task → `Status: Delivered` (all items fully fulfilled) or `PartiallyDelivered`. + - Writes back to the Shop PO: `Status = Fulfilled`/`PartiallyFulfilled`, `FulfilledAt` set, and + each `ShopPOItem.QuantityFulfilled` bumped. This is exactly the field the Loading Sheet + picker's "remaining quantity" (`quantity - quantityFulfilled`) depends on — a fully fulfilled + PO drops out of the picker entirely. +8. **Fail task**: marks `Status: Failed`. No stock movement, no Shop PO status change. + +--- + +## Stock ledger across the whole flow + +``` +Warehouse --(Loading Sheet confirm, Type=Load)--> Vehicle --(Delivery Task confirm, Type=Sale)--> External/Customer +``` + +Every step writes a `StockMovement` row, so `StockBalance` for each Warehouse/Vehicle stays +reconciled from Shop PO creation all the way through delivery. + +--- + +## Known gaps at time of writing + +- No frontend UI calls `ConfirmAndCreateInvoiceAsync` — only the plain `Confirm` action (no + invoice) is wired up on the Shop PO screens. +- `ShopPOController`/`LoadingSheetController` Create/Confirm endpoints, like most other + create/confirm endpoints in this codebase, rely on the TEMPORARY `currentUser.UserId ?? request.XxxUserId` + fallback pattern documented in `testbug.md` (#28) until the real auth middleware is fixed. diff --git a/Distribution/docs/testbug.md b/Distribution/docs/testbug.md index 987afb2..0c89138 100644 --- a/Distribution/docs/testbug.md +++ b/Distribution/docs/testbug.md @@ -54,6 +54,7 @@ Running log of backend API failures found during testing and the corresponding f | 12 | Pricing → Schemes (action icons) | Same recurring pattern as Price Lists (#45): `DeleteAsync`'s `repo.Remove` already soft-deleted (`IsActive = false`), but `GetAllAsync`/`GetByIdAsync`/`UpdateAsync` filtered/blocked on `IsActive`, hiding a deleted scheme from the list entirely and blocking viewing/editing/reactivating it. `EvaluateEligibilityAsync` relied on that same repo-level filter to only ever consider active schemes as redemption candidates — removing the filter without care would have silently let inactive/deleted schemes be evaluated as eligible. Also `GetAll` returns a plain `List` but the frontend was configured `serverSide: true` expecting a `PaginatedResponse`, so pagination silently did nothing | Added View (details modal — rule type, region, segment, date range, trigger/free-goods payout, bundle components) and Delete (soft-deactivate, reuses the existing "Active" checkbox in the Edit form to reactivate) icons | Removed the `IsActive` filter from `SchemeRepository.GetAllAsync`; moved the active-only filter into `SchemeService.EvaluateEligibilityAsync` itself (`.Where(s => s.IsActive)` on the fetched candidates) so eligibility evaluation is unaffected; removed the `IsActive` blocks from `GetByIdAsync`/`UpdateAsync`; added `IsActive` to `UpdateSchemeRequest`; switched the frontend to client-side pagination (same fix as Price Lists/Orders/Brands); added `schemes.remove()`; created `SchemeViewSheet` | 2026-09-09 | | 14 | Suppliers (action icons) | Exact same recurring pattern again: `SupplierRepository.GetAllAsync` filtered `.Where(s => s.IsActive)` (hiding a deleted supplier from the list instead of showing it Inactive), `GetByIdAsync`/`UpdateAsync` blocked viewing/editing an inactive one, and `UpdateSupplierRequest` had no `IsActive` field so the Edit form's existing "Active" checkbox couldn't reactivate one. Also `suppliers.list()` was typed as `PaginatedResponse` against a backend `GetAll` that returns a plain array, same pagination-shape bug as Price Lists/Schemes/Orders/Brands | Added View (details modal — name, contact, status, performance score, created date) and Delete (soft-deactivate, reuses the existing "Active" checkbox in Edit to reactivate) icons | Removed the `IsActive` filter from `SupplierRepository.GetAllAsync`; removed the `IsActive` blocks from `GetByIdAsync`/`UpdateAsync` (kept on `EvaluateAsync`, a real business guard — no point recomputing a performance score for a deleted supplier); added `IsActive` to `UpdateSupplierRequest`; switched the frontend to client-side pagination; added `suppliers.remove()`; created `SupplierViewSheet` | 2026-09-09 | | 48 | Pricing → Price Lists (new fields) | — | Added `RetailPrice` and `AgencyMarginPercent` to `PriceListItem`, alongside the existing `Price`/`MinPrice` band — see [14_pricing_and_suppliers.md](./14_pricing_and_suppliers.md) for the full business rules. `RetailPrice` is the MRP the store is expected to charge the end consumer (must be `>= Price`); `AgencyMarginPercent` is the regional agency's commission `%` of `Price` (must be `0-100`). Both are informational only — carried through `PriceResolution` for display/reporting, never enforced on order pricing | Added both columns to the Items table in the Price List Edit/View pages (add-row + inline edit + view-only table), and a "Retail price: Rs. X" hint under the unit price on Create Sale when the resolved price came from a real price list | Added `RetailPrice`/`AgencyMarginPercent` to `PriceListItem`, migration `AddRetailPriceAndAgencyMargin`; extended `UpsertPriceListItemRequest`/`PriceListItemResponse`/`PriceResolution` with validation (`RetailPrice >= Price`, `AgencyMarginPercent` in `[0,100]`); updated `PriceListItemsSection`, the View page's items table, and `priceLists.upsertItem()`; updated `docs/14_pricing_and_suppliers.md` | 2026-09-09 | +| 49 | Configuration → Products (Product Capabilities toggles) — TEMPORARY workaround | `PUT /api/sys-config/{key}` had the same unhandled `var userId = currentUser.UserId ?? throw new UnauthorizedAccessException();` crash as #10/#36/#40/#8, surfaced the moment a toggle tried to save. A first attempt added `Guid? UserId = null` as a record constructor default, which broke `System.Text.Json` deserialization entirely (`NotSupportedException` — a record constructor parameter with a default value reads to STJ as an ambiguous/parameterless-like constructor) | Rebuilt the Configuration area end to end: sidebar "Configuration" is now a standalone link (no sub-nav/collapsible) straight to `/configuration`, a card-grid landing page; its "Products" card opens `/configuration/products`, a redesigned "Product Capabilities" card (two-column toggle grid, ON/OFF `Switch`, no "Enable…" wording). Removed the page's "Save Changes" button entirely — each toggle (and the default-warranty-days input, on blur) now calls the API immediately on change, optimistically updating and reverting with a toast on failure. Also renamed "Items" → "Products" in the Catalogue nav label, page heading, and the Add/Edit/View dialogs (left every other module's "Item" line-item wording — Sales/Procurement/GRN/Loading Sheets/Returns — untouched, since those are document line items, not the catalogue) | TEMP: added `UserId` (no default value, to keep STJ happy) to `SetSysConfigRequest`; `SysConfigController.Set` now does `currentUser.UserId ?? request.UserId`, returning a clean `BadRequest` instead of throwing when both are null. Frontend `sysConfig.set()` takes an optional `userId` and the Product Configuration page passes `useAuth().user.id` (the mock-auth user) on every toggle/save call. Revert once auth (#28) is fixed | 2026-10-02 | | 8 | Stock Balances | No way to correct a balance — stock is architecturally never edited directly, only through movement transactions (load, sale, return, damage, adjustment); the `POST /stock/adjustment` endpoint existed but its controller action always returned `Unauthorized()` because `currentUser.UserId` is unset (#28) | Added a View icon (details modal) and an Edit icon that opens an "Adjust Stock" dialog (signed quantity delta + reason + Adjusted By) which posts to `/stock/adjustment` — this is the correct way to change a balance, not a direct field edit | TEMP: added `AdjustedByUserId` fallback to `AdjustmentRequest`, same pattern as #36/#40; wired `StockBalanceViewSheet` + `StockAdjustDialog` into Stock Balances table | 2026-09-08 | | 9 | Stock Movements | Some rows missing From/To locations — every movement should always have both | Added View and Edit icons. Movements are an immutable audit-trail ledger by design — quantity, locations, and type can never change once recorded (the running balance is the sum of all movements), so Edit only lets you correct the Reason/note via a new `PATCH /stock/movements/{id}/reason` endpoint; a wrong quantity/location must be corrected with a new Adjustment (see #8), not by editing history | Wired `StockMovementViewSheet` + `StockMovementEditDialog` into Stock Movements table | 2026-09-08 | | 41 | Stock Balances / Stock Movements | Balances and Movements tables/view-sheets showed raw location GUIDs instead of names; Movements also had a genuinely missing side — Sale/PurchaseReceipt/PurchaseReturn/CustomerReturn/Damage/Adjustment each only ever wrote one of From/To, since the other side (customer, supplier, write-off, adjustment) has no real Warehouse/Vehicle record. Existing seeded movement rows still showed blank From/To after the code fix since it only affects newly-created movements | — | Added `LocationName` to `StockBalanceResponse` and `FromLocationName`/`ToLocationName` to `StockMovementResponse`, resolved server-side (`StockService`) by looking up the real Warehouse/Vehicle. Added a new `LocationType.External` plus a fixed `Guid.Empty` sentinel location so every movement's missing side is now always populated and labeled contextually ("Customer", "Supplier", "Written Off", "Stock Adjustment") instead of being left null. Updated all 6 movement-creation call sites (StockService Damage/CustomerReturn/Adjustment, SaleService, DeliveryTaskService, GrnService, PurchaseReturnService, ReturnService), and added migration `FixSeedStockMovementExternalLocations` to backfill the 2 affected seeded rows | 2026-09-08 | @@ -75,6 +76,7 @@ Everything below is a stopgap that only exists because `Distribution/Middleware/ 11. **`CreateSaleReturnRequest.CreatedByUserId`** (`ReturnDTOs.cs`) and the "Created By" dropdown in the Record Order Return dialog (#23) — remove once `ReturnController.Create` can resolve `currentUser.UserId` on its own. 12. **`SettleClaimRequest.SettledByUserId`** (`ClaimDTOs.cs`) and the "Settled By" dropdown in the Settle Claim dialog (#23) — remove once `ClaimController.Settle` can resolve `currentUser.UserId` on its own. 13. **`CreateSupplierClaimRequest.RaisedByUserId`/`ApproveSupplierClaimRequest.ApprovedByUserId`/`RejectSupplierClaimRequest.RejectedByUserId`** (`SupplierClaimDTOs.cs`) and the corresponding user dropdowns in the Raise/Approve/Reject Supplier Claim dialogs (#23) — remove once `SupplierClaimController.Create`/`Approve`/`Reject` can resolve `currentUser.UserId` on their own. +14. **`SetSysConfigRequest.UserId`** (`SysConfigDTOs.cs`) and the `userId` argument threaded through `sysConfig.set()` from the Product Configuration toggles page (`app/(dashboard)/configuration/products/page.tsx`, #49) — remove once `SysConfigController.Set` can resolve `currentUser.UserId` on its own; drop the `?? request.UserId` fallback and the `BadRequest` guard around it. ## Implementation Guide