Add inventory management features including Sales, Stock Returns, and Suppliers

- Implement Sale and SaleItem models for tracking sales transactions.
- Create StockReturn model to manage stock adjustments for various reasons.
- Introduce Supplier model to represent suppliers of products.
- Develop InventoryRepository for CRUD operations on products, suppliers, sales, and stock returns.
- Implement InventoryService to handle business logic for inventory management.
- Update TenantDbContext to include new models and relationships.
- Enhance ProfitLossRepository to account for sales, purchases, and stock returns in profit and loss calculations.
- Modify Program.cs to register new inventory-related services.
This commit is contained in:
2026-10-07 15:43:20 +05:30
parent f96db48ec2
commit 2eb88b058e
16 changed files with 1678 additions and 0 deletions
+54
View File
@@ -82,6 +82,16 @@ namespace Fitness_core_BE
// Models/BookingInvoice.cs. Deliberately separate from CustomerInvoice (membership-only).
public DbSet<BookingInvoice> BookingInvoices => Set<BookingInvoice>();
// Simple inventory: Products, Suppliers, GRN (stock in), Sales (stock out), Returns
// (stock write-offs) -- see each model's own doc comment.
public DbSet<Product> Products => Set<Product>();
public DbSet<Supplier> Suppliers => Set<Supplier>();
public DbSet<GoodsReceivedNote> GoodsReceivedNotes => Set<GoodsReceivedNote>();
public DbSet<GoodsReceivedNoteItem> GoodsReceivedNoteItems => Set<GoodsReceivedNoteItem>();
public DbSet<Sale> Sales => Set<Sale>();
public DbSet<SaleItem> SaleItems => Set<SaleItem>();
public DbSet<StockReturn> StockReturns => Set<StockReturn>();
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
@@ -191,6 +201,11 @@ namespace Fitness_core_BE
modelBuilder.Entity<BookingInvoice>().Ignore(i => i.Client);
modelBuilder.Entity<TimeBankCard>().Ignore(t => t.Client);
modelBuilder.Entity<TimeBankCardUsageLog>().Ignore(l => l.Client);
modelBuilder.Entity<Product>().Ignore(p => p.Client);
modelBuilder.Entity<Supplier>().Ignore(s => s.Client);
modelBuilder.Entity<GoodsReceivedNote>().Ignore(g => g.Client);
modelBuilder.Entity<Sale>().Ignore(s => s.Client);
modelBuilder.Entity<StockReturn>().Ignore(r => r.Client);
// A session's capacity is enforced by counting non-cancelled bookings against it at
// booking time, not by a unique index -- multiple guests/members can share one slot
@@ -286,6 +301,45 @@ namespace Fitness_core_BE
modelBuilder.Entity<BookingInvoice>()
.HasIndex(i => new { i.FKClientId, i.IssuedAt });
// Inventory: GRN/Sale items cascade with their header; Return/GRN/Sale restrict
// deleting a Product/Supplier that already has history (there's no delete endpoint
// for either, but Restrict keeps the data model honest either way).
modelBuilder.Entity<Product>()
.HasIndex(p => new { p.FKClientId, p.Name });
modelBuilder.Entity<Supplier>()
.HasIndex(s => new { s.FKClientId, s.Name });
modelBuilder.Entity<GoodsReceivedNote>()
.HasIndex(g => new { g.FKClientId, g.ReceivedAt });
modelBuilder.Entity<GoodsReceivedNote>()
.HasOne(g => g.Supplier).WithMany()
.HasForeignKey(g => g.FKSupplierId).OnDelete(DeleteBehavior.Restrict);
modelBuilder.Entity<GoodsReceivedNoteItem>()
.HasOne(i => i.Grn).WithMany(g => g.Items)
.HasForeignKey(i => i.FKGrnId).OnDelete(DeleteBehavior.Cascade);
modelBuilder.Entity<GoodsReceivedNoteItem>()
.HasOne(i => i.Product).WithMany()
.HasForeignKey(i => i.FKProductId).OnDelete(DeleteBehavior.Restrict);
modelBuilder.Entity<Sale>()
.HasIndex(s => new { s.FKClientId, s.SoldAt });
modelBuilder.Entity<SaleItem>()
.HasOne(i => i.Sale).WithMany(s => s.Items)
.HasForeignKey(i => i.FKSaleId).OnDelete(DeleteBehavior.Cascade);
modelBuilder.Entity<SaleItem>()
.HasOne(i => i.Product).WithMany()
.HasForeignKey(i => i.FKProductId).OnDelete(DeleteBehavior.Restrict);
modelBuilder.Entity<StockReturn>()
.HasIndex(r => new { r.FKClientId, r.ReturnedAt });
modelBuilder.Entity<StockReturn>()
.HasOne(r => r.Product).WithMany()
.HasForeignKey(r => r.FKProductId).OnDelete(DeleteBehavior.Restrict);
// Optional traceability links -- most returns have neither.
modelBuilder.Entity<StockReturn>()
.HasOne(r => r.Sale).WithMany()
.HasForeignKey(r => r.FKSaleId).OnDelete(DeleteBehavior.Restrict);
modelBuilder.Entity<StockReturn>()
.HasOne(r => r.Grn).WithMany()
.HasForeignKey(r => r.FKGrnId).OnDelete(DeleteBehavior.Restrict);
modelBuilder.Entity<UnlinkedDeviceEnrollment>()
.HasIndex(e => new { e.FKDeviceId, e.DeviceEmployeeNo, e.LinkedAt });