serial numbers

This commit is contained in:
2026-10-07 14:00:32 +05:30
committed by ImanThiyanga
parent 3abcfa6e6f
commit a788349138
89 changed files with 15320 additions and 216 deletions
@@ -34,7 +34,9 @@ public sealed class SerialConfiguration : IEntityTypeConfiguration<Serial>
builder.HasKey(s => s.SerialId);
builder.Property(s => s.SerialNo).IsRequired().HasMaxLength(100);
builder.Property(s => s.Status).IsRequired().HasMaxLength(20);
// String-stored enum: rows written as "InStock"/"Sold" before SerialStatus existed
// read back unchanged.
builder.Property(s => s.Status).HasConversion<string>().IsRequired().HasMaxLength(20);
builder.Property(s => s.Barcode).HasMaxLength(100);
builder.HasIndex(s => s.Barcode).IsUnique();
@@ -43,7 +45,12 @@ public sealed class SerialConfiguration : IEntityTypeConfiguration<Serial>
.WithMany()
.HasForeignKey(s => s.ItemId)
.OnDelete(DeleteBehavior.Restrict);
builder.HasOne(s => s.Warehouse).WithMany().HasForeignKey(s => s.WarehouseId).OnDelete(DeleteBehavior.Restrict);
builder.HasOne(s => s.Bin).WithMany().HasForeignKey(s => s.BinId).OnDelete(DeleteBehavior.Restrict);
builder.HasOne(s => s.GrnLine).WithMany().HasForeignKey(s => s.GrnLineId).OnDelete(DeleteBehavior.Restrict);
builder.HasIndex(s => new { s.ItemId, s.SerialNo }).IsUnique();
builder.HasIndex(s => new { s.ItemId, s.Status });
builder.HasIndex(s => new { s.WarehouseId, s.ItemId, s.Status });
}
}
@@ -74,3 +74,23 @@ public sealed class GrnLineWarrantyNumberConfiguration : IEntityTypeConfiguratio
builder.HasIndex(w => new { w.GrnLineId, w.WarrantyNo }).IsUnique();
}
}
public sealed class GrnLineSerialConfiguration : IEntityTypeConfiguration<GrnLineSerial>
{
public void Configure(EntityTypeBuilder<GrnLineSerial> builder)
{
builder.ToTable("grn_line_serials");
builder.HasKey(s => s.GrnLineSerialId);
builder.Property(s => s.SerialNo).IsRequired().HasMaxLength(100);
builder.HasOne(s => s.GrnLine).WithMany(l => l.Serials)
.HasForeignKey(s => s.GrnLineId).OnDelete(DeleteBehavior.Cascade);
builder.HasOne(s => s.Serial).WithMany()
.HasForeignKey(s => s.SerialId).OnDelete(DeleteBehavior.Restrict);
// Global uniqueness lives on serials (ItemId, SerialNo); this only stops a number
// being entered twice on one line.
builder.HasIndex(s => new { s.GrnLineId, s.SerialNo }).IsUnique();
}
}
@@ -0,0 +1,118 @@
using ERPCore.Domain.Entities;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
namespace ERPCore.Infra.Persistence.Configurations;
// Sales-side serial/batch tracking and warranty (docs/ITEM_TRACKING_PLAN.md Phases 4–5).
public sealed class SalesInvoiceLineSerialConfiguration : IEntityTypeConfiguration<SalesInvoiceLineSerial>
{
public void Configure(EntityTypeBuilder<SalesInvoiceLineSerial> builder)
{
builder.ToTable("sales_invoice_line_serials");
builder.HasKey(x => x.SalesInvoiceLineSerialId);
builder.HasOne(x => x.SalesInvoiceLine).WithMany(l => l.Serials)
.HasForeignKey(x => x.SalesInvoiceLineId).OnDelete(DeleteBehavior.Cascade);
builder.HasOne(x => x.Serial).WithMany()
.HasForeignKey(x => x.SerialId).OnDelete(DeleteBehavior.Restrict);
builder.HasIndex(x => new { x.SalesInvoiceLineId, x.SerialId }).IsUnique();
}
}
public sealed class SalesSlipLineSerialConfiguration : IEntityTypeConfiguration<SalesSlipLineSerial>
{
public void Configure(EntityTypeBuilder<SalesSlipLineSerial> builder)
{
builder.ToTable("sales_slip_line_serials");
builder.HasKey(x => x.SalesSlipLineSerialId);
builder.HasOne(x => x.SalesSlipLine).WithMany(l => l.Serials)
.HasForeignKey(x => x.SalesSlipLineId).OnDelete(DeleteBehavior.Cascade);
builder.HasOne(x => x.Serial).WithMany()
.HasForeignKey(x => x.SerialId).OnDelete(DeleteBehavior.Restrict);
builder.HasIndex(x => new { x.SalesSlipLineId, x.SerialId }).IsUnique();
}
}
public sealed class SalesLineAllocationConfiguration : IEntityTypeConfiguration<SalesLineAllocation>
{
public void Configure(EntityTypeBuilder<SalesLineAllocation> builder)
{
builder.ToTable("sales_line_allocations");
builder.HasKey(x => x.SalesLineAllocationId);
builder.Property(x => x.SourceDocType).IsRequired().HasMaxLength(50);
builder.Property(x => x.Qty).HasPrecision(18, 4);
builder.Property(x => x.QtyReturned).HasPrecision(18, 4);
builder.Property(x => x.UnitCost).HasPrecision(18, 6);
builder.HasOne(x => x.Batch).WithMany().HasForeignKey(x => x.BatchId).OnDelete(DeleteBehavior.Restrict);
builder.HasOne(x => x.Serial).WithMany().HasForeignKey(x => x.SerialId).OnDelete(DeleteBehavior.Restrict);
builder.HasIndex(x => new { x.SourceDocType, x.SourceDocId, x.SourceLineId });
builder.HasIndex(x => x.SerialId);
builder.HasIndex(x => x.BatchId);
}
}
public sealed class StockTransferLineSerialConfiguration : IEntityTypeConfiguration<StockTransferLineSerial>
{
public void Configure(EntityTypeBuilder<StockTransferLineSerial> builder)
{
builder.ToTable("stock_transfer_line_serials");
builder.HasKey(x => x.StockTransferLineSerialId);
builder.Property(x => x.UnitCost).HasPrecision(18, 6);
builder.HasOne(x => x.TransferLine).WithMany(l => l.Serials)
.HasForeignKey(x => x.TransferLineId).OnDelete(DeleteBehavior.Cascade);
builder.HasOne(x => x.Serial).WithMany()
.HasForeignKey(x => x.SerialId).OnDelete(DeleteBehavior.Restrict);
builder.HasOne<Batch>().WithMany().HasForeignKey(x => x.BatchId).OnDelete(DeleteBehavior.Restrict);
builder.HasIndex(x => new { x.TransferLineId, x.SerialId }).IsUnique();
}
}
public sealed class SerialMovementConfiguration : IEntityTypeConfiguration<SerialMovement>
{
public void Configure(EntityTypeBuilder<SerialMovement> builder)
{
builder.ToTable("serial_movements");
builder.HasKey(x => x.SerialMovementId);
builder.Property(x => x.SourceDocType).IsRequired().HasMaxLength(50);
builder.Property(x => x.Status).HasConversion<string>().IsRequired().HasMaxLength(20);
builder.HasOne(x => x.Serial).WithMany().HasForeignKey(x => x.SerialId).OnDelete(DeleteBehavior.Cascade);
builder.HasIndex(x => new { x.SerialId, x.SerialMovementId });
builder.HasIndex(x => new { x.SourceDocType, x.SourceDocId });
}
}
public sealed class WarrantyRegistrationConfiguration : IEntityTypeConfiguration<WarrantyRegistration>
{
public void Configure(EntityTypeBuilder<WarrantyRegistration> builder)
{
builder.ToTable("warranty_registrations");
builder.HasKey(x => x.WarrantyRegistrationId);
builder.Property(x => x.SourceDocType).IsRequired().HasMaxLength(50);
builder.Property(x => x.Qty).HasPrecision(18, 4);
builder.Property(x => x.Status).HasConversion<string>().IsRequired().HasMaxLength(20);
builder.HasOne(x => x.Item).WithMany().HasForeignKey(x => x.ItemId).OnDelete(DeleteBehavior.Restrict);
builder.HasOne(x => x.Serial).WithMany().HasForeignKey(x => x.SerialId).OnDelete(DeleteBehavior.Restrict);
builder.HasOne(x => x.Customer).WithMany().HasForeignKey(x => x.CustomerId).OnDelete(DeleteBehavior.Restrict);
builder.HasIndex(x => x.SerialId);
builder.HasIndex(x => x.CustomerId);
builder.HasIndex(x => new { x.SourceDocType, x.SourceDocId });
builder.HasIndex(x => new { x.Status, x.EndDate });
}
}
@@ -111,6 +111,11 @@ public sealed class SalesInvoiceLineConfiguration : IEntityTypeConfiguration<Sal
.HasForeignKey(x => x.WarehouseId)
.OnDelete(DeleteBehavior.Restrict);
builder.HasOne(x => x.Batch)
.WithMany()
.HasForeignKey(x => x.BatchId)
.OnDelete(DeleteBehavior.Restrict);
builder.Property(x => x.PriceSource).HasMaxLength(50);
}
}
@@ -37,5 +37,20 @@ public sealed class SalesReturnLineConfiguration : IEntityTypeConfiguration<Sale
builder.HasOne(l => l.Return).WithMany(r => r.Lines).HasForeignKey(l => l.ReturnId).OnDelete(DeleteBehavior.Cascade);
builder.HasOne(l => l.SalesInvoiceLine).WithMany().HasForeignKey(l => l.SalesInvoiceLineId).OnDelete(DeleteBehavior.Restrict);
builder.HasOne(l => l.Item).WithMany().HasForeignKey(l => l.ItemId).OnDelete(DeleteBehavior.Restrict);
builder.HasOne(l => l.Batch).WithMany().HasForeignKey(l => l.BatchId).OnDelete(DeleteBehavior.Restrict);
}
}
public sealed class SalesReturnLineSerialConfiguration : IEntityTypeConfiguration<SalesReturnLineSerial>
{
public void Configure(EntityTypeBuilder<SalesReturnLineSerial> builder)
{
builder.ToTable("sales_return_line_serials");
builder.HasKey(s => s.SalesReturnLineSerialId);
builder.HasOne(s => s.ReturnLine).WithMany(l => l.Serials).HasForeignKey(s => s.ReturnLineId).OnDelete(DeleteBehavior.Cascade);
builder.HasOne(s => s.Serial).WithMany().HasForeignKey(s => s.SerialId).OnDelete(DeleteBehavior.Restrict);
builder.HasIndex(s => new { s.ReturnLineId, s.SerialId }).IsUnique();
}
}
@@ -40,6 +40,6 @@ public sealed class StockAdjustmentLineConfiguration : IEntityTypeConfiguration<
builder.HasOne(l => l.Item).WithMany().HasForeignKey(l => l.ItemId).OnDelete(DeleteBehavior.Restrict);
builder.HasOne<Bin>().WithMany().HasForeignKey(l => l.BinId).OnDelete(DeleteBehavior.Restrict);
builder.HasOne<Batch>().WithMany().HasForeignKey(l => l.BatchId).OnDelete(DeleteBehavior.Restrict);
builder.HasOne<Serial>().WithMany().HasForeignKey(l => l.SerialId).OnDelete(DeleteBehavior.Restrict);
builder.HasOne(l => l.Serial).WithMany().HasForeignKey(l => l.SerialId).OnDelete(DeleteBehavior.Restrict);
}
}
@@ -93,6 +93,14 @@ public class ErpDbContext : DbContext
// --- Batch / Serial (docs/10 Part C.4) ---
public DbSet<Batch> Batches => Set<Batch>();
public DbSet<Serial> Serials => Set<Serial>();
public DbSet<GrnLineSerial> GrnLineSerials => Set<GrnLineSerial>();
public DbSet<SalesInvoiceLineSerial> SalesInvoiceLineSerials => Set<SalesInvoiceLineSerial>();
public DbSet<SalesSlipLineSerial> SalesSlipLineSerials => Set<SalesSlipLineSerial>();
public DbSet<SalesLineAllocation> SalesLineAllocations => Set<SalesLineAllocation>();
public DbSet<WarrantyRegistration> WarrantyRegistrations => Set<WarrantyRegistration>();
public DbSet<SalesReturnLineSerial> SalesReturnLineSerials => Set<SalesReturnLineSerial>();
public DbSet<SerialMovement> SerialMovements => Set<SerialMovement>();
public DbSet<StockTransferLineSerial> StockTransferLineSerials => Set<StockTransferLineSerial>();
// --- Stock core: FIFO layers + immutable ledger (docs/10 Part C.5) ---
public DbSet<StockLayer> StockLayers => Set<StockLayer>();