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:
@@ -0,0 +1,106 @@
|
||||
namespace Fitness_core_BE.DTOs.Inventory
|
||||
{
|
||||
public class ProductDto
|
||||
{
|
||||
public Guid ProductId { get; set; }
|
||||
public string Name { get; set; } = string.Empty;
|
||||
public string? Sku { get; set; }
|
||||
/// <summary>Base64 data URL, computed on read from ImageId -- null when no image is set.</summary>
|
||||
public string? ImageUrl { get; set; }
|
||||
public string UnitOfMeasure { get; set; } = string.Empty;
|
||||
public decimal CostPrice { get; set; }
|
||||
public decimal SellingPrice { get; set; }
|
||||
public decimal QuantityOnHand { get; set; }
|
||||
public bool IsActive { get; set; }
|
||||
public DateTime CreatedAt { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>One row in a Product's detail-page stock-movement history.</summary>
|
||||
public class ProductMovementDto
|
||||
{
|
||||
/// <summary>"GRN" | "Sale" | "Return".</summary>
|
||||
public string Type { get; set; } = string.Empty;
|
||||
/// <summary>The GRN/Sale number, or the return's Category.</summary>
|
||||
public string Reference { get; set; } = string.Empty;
|
||||
/// <summary>Signed: positive for a GRN (stock in), negative for a Sale or Return (stock out).</summary>
|
||||
public decimal QuantityChange { get; set; }
|
||||
public DateTime Date { get; set; }
|
||||
}
|
||||
|
||||
public class ProductDetailDto : ProductDto
|
||||
{
|
||||
public List<ProductMovementDto> Movements { get; set; } = [];
|
||||
}
|
||||
|
||||
public class SupplierDto
|
||||
{
|
||||
public Guid SupplierId { get; set; }
|
||||
public string Name { get; set; } = string.Empty;
|
||||
public string? Phone { get; set; }
|
||||
public string? Email { get; set; }
|
||||
public string? Address { get; set; }
|
||||
public bool IsActive { get; set; }
|
||||
}
|
||||
|
||||
public class GoodsReceivedNoteItemDto
|
||||
{
|
||||
public Guid ProductId { get; set; }
|
||||
public string ProductName { get; set; } = string.Empty;
|
||||
public decimal Quantity { get; set; }
|
||||
public decimal UnitCost { get; set; }
|
||||
public decimal LineTotal { get; set; }
|
||||
}
|
||||
|
||||
public class GoodsReceivedNoteDto
|
||||
{
|
||||
public Guid GrnId { get; set; }
|
||||
public string GrnNumber { get; set; } = string.Empty;
|
||||
/// <summary>Null when this GRN was recorded without a supplier.</summary>
|
||||
public Guid? SupplierId { get; set; }
|
||||
public string? SupplierName { get; set; }
|
||||
public DateTime ReceivedAt { get; set; }
|
||||
public string? Notes { get; set; }
|
||||
public decimal TotalAmount { get; set; }
|
||||
public List<GoodsReceivedNoteItemDto> Items { get; set; } = [];
|
||||
}
|
||||
|
||||
public class SaleItemDto
|
||||
{
|
||||
public Guid ProductId { get; set; }
|
||||
public string ProductName { get; set; } = string.Empty;
|
||||
public decimal Quantity { get; set; }
|
||||
public decimal UnitPrice { get; set; }
|
||||
public decimal LineTotal { get; set; }
|
||||
}
|
||||
|
||||
public class SaleDto
|
||||
{
|
||||
public Guid SaleId { get; set; }
|
||||
public string SaleNumber { get; set; } = string.Empty;
|
||||
public DateTime SoldAt { get; set; }
|
||||
public string PaymentMethod { get; set; } = string.Empty;
|
||||
public Guid? CustomerId { get; set; }
|
||||
public string? CustomerName { get; set; }
|
||||
public decimal TotalAmount { get; set; }
|
||||
public List<SaleItemDto> Items { get; set; } = [];
|
||||
}
|
||||
|
||||
public class StockReturnDto
|
||||
{
|
||||
public Guid StockReturnId { get; set; }
|
||||
public Guid ProductId { get; set; }
|
||||
public string ProductName { get; set; } = string.Empty;
|
||||
public decimal Quantity { get; set; }
|
||||
public string Category { get; set; } = string.Empty;
|
||||
/// <summary>Optional traceability link -- which sale this return came from, if any.</summary>
|
||||
public Guid? SaleId { get; set; }
|
||||
public string? SaleNumber { get; set; }
|
||||
/// <summary>Optional traceability link -- which GRN this return relates to, if any.</summary>
|
||||
public Guid? GrnId { get; set; }
|
||||
public string? GrnNumber { get; set; }
|
||||
public string? Notes { get; set; }
|
||||
public DateTime ReturnedAt { get; set; }
|
||||
/// <summary>Quantity * Product.CostPrice at the moment of the return -- auto-computed.</summary>
|
||||
public decimal Amount { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace Fitness_core_BE.DTOs.Inventory
|
||||
{
|
||||
public class CreateProductRequest
|
||||
{
|
||||
[Required]
|
||||
public string Name { get; set; } = string.Empty;
|
||||
public string UnitOfMeasure { get; set; } = "pcs";
|
||||
|
||||
[Range(typeof(decimal), "0", "79228162514264337593543950335", ErrorMessage = "SellingPrice cannot be negative")]
|
||||
public decimal SellingPrice { get; set; } = 0m;
|
||||
}
|
||||
|
||||
/// <summary>Sku is system-generated at creation and never included here -- it can't be edited.</summary>
|
||||
public class UpdateProductRequest
|
||||
{
|
||||
public string? Name { get; set; }
|
||||
public string? UnitOfMeasure { get; set; }
|
||||
public decimal? SellingPrice { get; set; }
|
||||
public bool? IsActive { get; set; }
|
||||
}
|
||||
|
||||
public class CreateSupplierRequest
|
||||
{
|
||||
[Required]
|
||||
public string Name { get; set; } = string.Empty;
|
||||
public string? Phone { get; set; }
|
||||
public string? Email { get; set; }
|
||||
public string? Address { get; set; }
|
||||
}
|
||||
|
||||
public class GrnItemRequest
|
||||
{
|
||||
[Required]
|
||||
public Guid ProductId { get; set; }
|
||||
|
||||
[Range(0.0001, double.MaxValue, ErrorMessage = "Quantity must be greater than 0")]
|
||||
public decimal Quantity { get; set; }
|
||||
|
||||
[Range(typeof(decimal), "0", "79228162514264337593543950335", ErrorMessage = "UnitCost cannot be negative")]
|
||||
public decimal UnitCost { get; set; }
|
||||
}
|
||||
|
||||
public class CreateGrnRequest
|
||||
{
|
||||
/// <summary>Optional -- some GRNs don't have a supplier on file.</summary>
|
||||
public Guid? SupplierId { get; set; }
|
||||
public DateTime? ReceivedAt { get; set; }
|
||||
public string? Notes { get; set; }
|
||||
|
||||
[Required, MinLength(1, ErrorMessage = "At least one item is required")]
|
||||
public List<GrnItemRequest> Items { get; set; } = [];
|
||||
}
|
||||
|
||||
public class SaleItemRequest
|
||||
{
|
||||
[Required]
|
||||
public Guid ProductId { get; set; }
|
||||
|
||||
[Range(0.0001, double.MaxValue, ErrorMessage = "Quantity must be greater than 0")]
|
||||
public decimal Quantity { get; set; }
|
||||
|
||||
[Range(typeof(decimal), "0", "79228162514264337593543950335", ErrorMessage = "UnitPrice cannot be negative")]
|
||||
public decimal UnitPrice { get; set; }
|
||||
}
|
||||
|
||||
public class CreateSaleRequest
|
||||
{
|
||||
public DateTime? SoldAt { get; set; }
|
||||
|
||||
/// <summary>"Cash" | "Bank".</summary>
|
||||
[Required]
|
||||
public string PaymentMethod { get; set; } = "Cash";
|
||||
|
||||
public Guid? CustomerId { get; set; }
|
||||
public string? CustomerName { get; set; }
|
||||
|
||||
[Required, MinLength(1, ErrorMessage = "At least one item is required")]
|
||||
public List<SaleItemRequest> Items { get; set; } = [];
|
||||
}
|
||||
|
||||
public class CreateStockReturnRequest
|
||||
{
|
||||
[Required]
|
||||
public Guid ProductId { get; set; }
|
||||
|
||||
[Range(0.0001, double.MaxValue, ErrorMessage = "Quantity must be greater than 0")]
|
||||
public decimal Quantity { get; set; }
|
||||
|
||||
/// <summary>"Expired" | "Damaged" | "Lost" | "SupplierReturn" | "CustomerReturn" | "Other".
|
||||
/// Only "CustomerReturn" adds back to stock; every other category removes from it.</summary>
|
||||
[Required]
|
||||
public string Category { get; set; } = "Other";
|
||||
|
||||
/// <summary>Optional traceability link -- e.g. a customer return that came from a
|
||||
/// specific sale. Many returns (damaged shelf stock, etc.) don't have one.</summary>
|
||||
public Guid? SaleId { get; set; }
|
||||
|
||||
/// <summary>Optional traceability link -- e.g. stock sent back to the supplier from a
|
||||
/// specific GRN. Many returns don't have one.</summary>
|
||||
public Guid? GrnId { get; set; }
|
||||
|
||||
public string? Notes { get; set; }
|
||||
public DateTime? ReturnedAt { get; set; }
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user