diff --git a/Backend/ERPCore/Domain/Entities/SalesInvoiceLine.cs b/Backend/ERPCore/Domain/Entities/SalesInvoiceLine.cs index 05310de..e7e4beb 100644 --- a/Backend/ERPCore/Domain/Entities/SalesInvoiceLine.cs +++ b/Backend/ERPCore/Domain/Entities/SalesInvoiceLine.cs @@ -1,3 +1,5 @@ +using ERPCore.Domain.Enums; + namespace ERPCore.Domain.Entities; public class SalesInvoiceLine @@ -22,6 +24,7 @@ public class SalesInvoiceLine public decimal UnitPrice { get; set; } public decimal BaseCost { get; set; } public string PriceSource { get; set; } = string.Empty; + public SalesDiscountMode DiscountMode { get; set; } = SalesDiscountMode.Percentage; public decimal DiscountPct { get; set; } public decimal DiscountAmount { get; set; } public decimal NetUnitPrice { get; set; } diff --git a/Backend/ERPCore/Domain/Entities/SalesSlipLine.cs b/Backend/ERPCore/Domain/Entities/SalesSlipLine.cs index c0079e3..0eb3de2 100644 --- a/Backend/ERPCore/Domain/Entities/SalesSlipLine.cs +++ b/Backend/ERPCore/Domain/Entities/SalesSlipLine.cs @@ -1,3 +1,5 @@ +using ERPCore.Domain.Enums; + namespace ERPCore.Domain.Entities; public class SalesSlipLine @@ -22,6 +24,7 @@ public class SalesSlipLine public decimal UnitPrice { get; set; } public decimal BaseCost { get; set; } public string PriceSource { get; set; } = string.Empty; + public SalesDiscountMode DiscountMode { get; set; } = SalesDiscountMode.Percentage; public decimal DiscountPct { get; set; } public decimal DiscountAmount { get; set; } public decimal NetUnitPrice { get; set; } diff --git a/Backend/ERPCore/Domain/Enums/SalesDiscountMode.cs b/Backend/ERPCore/Domain/Enums/SalesDiscountMode.cs new file mode 100644 index 0000000..c6d8f79 --- /dev/null +++ b/Backend/ERPCore/Domain/Enums/SalesDiscountMode.cs @@ -0,0 +1,7 @@ +namespace ERPCore.Domain.Enums; + +public enum SalesDiscountMode +{ + Percentage = 1, + FixedAmount = 2 +} diff --git a/Backend/ERPCore/Dtos/Sales/SalesInvoiceDtos.cs b/Backend/ERPCore/Dtos/Sales/SalesInvoiceDtos.cs index b90fd30..f8f8f5e 100644 --- a/Backend/ERPCore/Dtos/Sales/SalesInvoiceDtos.cs +++ b/Backend/ERPCore/Dtos/Sales/SalesInvoiceDtos.cs @@ -6,8 +6,8 @@ namespace ERPCore.Dtos.Sales; public sealed record SalesInvoiceLineDto( int SalesInvoiceLineId, int ItemId, string Description, decimal Qty, decimal FreeQty, int UomId, int WarehouseId, decimal UnitPrice, decimal BaseCost, string PriceSource, decimal DiscountPct, - decimal DiscountAmount, decimal NetUnitPrice, decimal LineTotal, decimal TaxPct, decimal TaxAmount, - bool IsFreeIssue, int? ParentLineId); + decimal DiscountAmount, SalesDiscountMode DiscountMode, decimal NetUnitPrice, decimal LineTotal, + decimal TaxPct, decimal TaxAmount, bool IsFreeIssue, int? ParentLineId); public sealed record SalesInvoiceTotalsDto( decimal Subtotal, decimal DiscountTotal, decimal TaxTotal, decimal GrandTotal, @@ -33,8 +33,10 @@ public sealed class CreateSalesInvoiceLineRequest [Range(0, double.MaxValue)] public decimal FreeQty { get; set; } [Range(0, double.MaxValue)] public decimal? UnitPrice { get; set; } public bool AllowManualPriceOverride { get; set; } + [Required, EnumDataType(typeof(SalesDiscountMode))] public SalesDiscountMode DiscountMode { get; set; } = SalesDiscountMode.Percentage; [Range(0, 100)] public decimal DiscountPct { get; set; } [Range(0, double.MaxValue)] public decimal DiscountAmount { get; set; } + [Range(0, double.MaxValue)] public decimal DiscountValue { get; set; } [Range(0, 100)] public decimal TaxPct { get; set; } public bool IsFreeIssue { get; set; } public int? ParentLineId { get; set; } diff --git a/Backend/ERPCore/Dtos/Sales/SalesSlipDtos.cs b/Backend/ERPCore/Dtos/Sales/SalesSlipDtos.cs index eab27c9..11a4597 100644 --- a/Backend/ERPCore/Dtos/Sales/SalesSlipDtos.cs +++ b/Backend/ERPCore/Dtos/Sales/SalesSlipDtos.cs @@ -6,8 +6,8 @@ namespace ERPCore.Dtos.Sales; public sealed record SalesSlipLineDto( int SalesSlipLineId, int ItemId, string Description, decimal Qty, decimal FreeQty, int UomId, int WarehouseId, decimal UnitPrice, decimal BaseCost, string PriceSource, decimal DiscountPct, - decimal DiscountAmount, decimal NetUnitPrice, decimal LineTotal, decimal TaxPct, decimal TaxAmount, - bool IsFreeIssue, int? ParentLineId); + decimal DiscountAmount, SalesDiscountMode DiscountMode, decimal NetUnitPrice, decimal LineTotal, + decimal TaxPct, decimal TaxAmount, bool IsFreeIssue, int? ParentLineId); public sealed record SalesSlipTotalsDto( decimal Subtotal, decimal DiscountTotal, decimal TaxTotal, decimal GrandTotal, @@ -32,8 +32,10 @@ public sealed class CreateSalesSlipLineRequest [Range(0, double.MaxValue)] public decimal FreeQty { get; set; } [Range(0, double.MaxValue)] public decimal? UnitPrice { get; set; } public bool AllowManualPriceOverride { get; set; } + [Required, EnumDataType(typeof(SalesDiscountMode))] public SalesDiscountMode DiscountMode { get; set; } = SalesDiscountMode.Percentage; [Range(0, 100)] public decimal DiscountPct { get; set; } [Range(0, double.MaxValue)] public decimal DiscountAmount { get; set; } + [Range(0, double.MaxValue)] public decimal DiscountValue { get; set; } [Range(0, 100)] public decimal TaxPct { get; set; } public bool IsFreeIssue { get; set; } public int? ParentLineId { get; set; } diff --git a/Backend/ERPCore/Services/SalesInvoiceService.cs b/Backend/ERPCore/Services/SalesInvoiceService.cs index d49252e..9736cbe 100644 --- a/Backend/ERPCore/Services/SalesInvoiceService.cs +++ b/Backend/ERPCore/Services/SalesInvoiceService.cs @@ -182,8 +182,7 @@ public sealed class SalesInvoiceService : ISalesInvoiceService var priceSource = resolved.PriceSource; var gross = r.Qty * unitPrice; - var discountAfterPct = gross * (r.DiscountPct / 100m); - var discountTotal = discountAfterPct + r.DiscountAmount; + var discountTotal = CalculateDiscount(gross, r.DiscountMode, r.DiscountPct, r.DiscountValue, r.DiscountAmount); var netUnit = r.Qty > 0 ? (gross - discountTotal) / r.Qty : 0m; var lineTotal = gross - discountTotal; var taxAmount = lineTotal * (r.TaxPct / 100m); @@ -201,6 +200,7 @@ public sealed class SalesInvoiceService : ISalesInvoiceService PriceSource = priceSource, DiscountPct = r.DiscountPct, DiscountAmount = discountTotal, + DiscountMode = r.DiscountMode, NetUnitPrice = netUnit, LineTotal = lineTotal, TaxPct = r.TaxPct, @@ -224,6 +224,18 @@ public sealed class SalesInvoiceService : ISalesInvoiceService invoice.BalanceAmount = invoice.NetPayable; } + private static decimal CalculateDiscount(decimal gross, SalesDiscountMode mode, decimal discountPct, decimal discountValue, decimal legacyDiscountAmount) + { + var computed = mode == SalesDiscountMode.FixedAmount + ? discountValue + : gross * (discountPct / 100m); + + if (computed <= 0m && legacyDiscountAmount > 0m) + computed = legacyDiscountAmount; + + return Math.Min(gross, Math.Max(0m, computed)); + } + private static SalesInvoiceSummaryDto MapSummary(SalesInvoice x) => new( x.SalesInvoiceId, x.InvoiceNo, x.InvoiceDate, x.CustomerId, x.CustomerSnapshotName, x.WarehouseId, x.InvoiceType, x.Status, new SalesInvoiceTotalsDto( @@ -233,5 +245,5 @@ public sealed class SalesInvoiceService : ISalesInvoiceService x.SalesInvoiceId, x.InvoiceNo, x.InvoiceDate, x.CustomerId, x.CustomerSnapshotName, x.CustomerSnapshotTaxNo, x.WarehouseId, x.InvoiceType, x.Status, x.CreatedBy, x.CreatedAt, x.UpdatedAt, new SalesInvoiceTotalsDto(x.Subtotal, x.DiscountTotal, x.TaxTotal, x.GrandTotal, x.RoundOff, x.NetPayable, x.PaidAmount, x.BalanceAmount), - x.Lines.Select(l => new SalesInvoiceLineDto(l.SalesInvoiceLineId, l.ItemId, l.Description, l.Qty, l.FreeQty, l.UomId, l.WarehouseId, l.UnitPrice, l.BaseCost, l.PriceSource, l.DiscountPct, l.DiscountAmount, l.NetUnitPrice, l.LineTotal, l.TaxPct, l.TaxAmount, l.IsFreeIssue, l.ParentLineId)).ToList()); + x.Lines.Select(l => new SalesInvoiceLineDto(l.SalesInvoiceLineId, l.ItemId, l.Description, l.Qty, l.FreeQty, l.UomId, l.WarehouseId, l.UnitPrice, l.BaseCost, l.PriceSource, l.DiscountPct, l.DiscountAmount, l.DiscountMode, l.NetUnitPrice, l.LineTotal, l.TaxPct, l.TaxAmount, l.IsFreeIssue, l.ParentLineId)).ToList()); } diff --git a/Backend/ERPCore/Services/SalesSlipService.cs b/Backend/ERPCore/Services/SalesSlipService.cs index 96c6a6b..a92a355 100644 --- a/Backend/ERPCore/Services/SalesSlipService.cs +++ b/Backend/ERPCore/Services/SalesSlipService.cs @@ -183,8 +183,7 @@ public sealed class SalesSlipService : ISalesSlipService var priceSource = resolved.PriceSource; var gross = r.Qty * unitPrice; - var discountAfterPct = gross * (r.DiscountPct / 100m); - var discountTotal = discountAfterPct + r.DiscountAmount; + var discountTotal = CalculateDiscount(gross, r.DiscountMode, r.DiscountPct, r.DiscountValue, r.DiscountAmount); var netUnit = r.Qty > 0 ? (gross - discountTotal) / r.Qty : 0m; var lineTotal = gross - discountTotal; var taxAmount = lineTotal * (r.TaxPct / 100m); @@ -200,6 +199,7 @@ public sealed class SalesSlipService : ISalesSlipService UnitPrice = unitPrice, BaseCost = unitPrice, PriceSource = priceSource, + DiscountMode = r.DiscountMode, DiscountPct = r.DiscountPct, DiscountAmount = discountTotal, NetUnitPrice = netUnit, @@ -223,6 +223,18 @@ public sealed class SalesSlipService : ISalesSlipService slip.BalanceAmount = slip.GrandTotal - slip.PaidAmount; } + private static decimal CalculateDiscount(decimal gross, SalesDiscountMode mode, decimal discountPct, decimal discountValue, decimal legacyDiscountAmount) + { + var computed = mode == SalesDiscountMode.FixedAmount + ? discountValue + : gross * (discountPct / 100m); + + if (computed <= 0m && legacyDiscountAmount > 0m) + computed = legacyDiscountAmount; + + return Math.Min(gross, Math.Max(0m, computed)); + } + private static SalesSlipSummaryDto MapSummary(SalesSlip x) => new( x.SalesSlipId, x.SlipNo, x.SlipDate, x.CustomerId, x.CustomerSnapshotName, x.WarehouseId, x.Status, new SalesSlipTotalsDto(x.Subtotal, x.DiscountTotal, x.TaxTotal, x.GrandTotal, x.PaidAmount, x.BalanceAmount), x.CreatedAt); @@ -230,5 +242,5 @@ public sealed class SalesSlipService : ISalesSlipService private static SalesSlipDto Map(SalesSlip x) => new( x.SalesSlipId, x.SlipNo, x.SlipDate, x.CustomerId, x.CustomerSnapshotName, x.WarehouseId, x.CashierUserId, x.Status, x.CreatedAt, x.UpdatedAt, new SalesSlipTotalsDto(x.Subtotal, x.DiscountTotal, x.TaxTotal, x.GrandTotal, x.PaidAmount, x.BalanceAmount), - x.Lines.Select(l => new SalesSlipLineDto(l.SalesSlipLineId, l.ItemId, l.Description, l.Qty, l.FreeQty, l.UomId, l.WarehouseId, l.UnitPrice, l.BaseCost, l.PriceSource, l.DiscountPct, l.DiscountAmount, l.NetUnitPrice, l.LineTotal, l.TaxPct, l.TaxAmount, l.IsFreeIssue, l.ParentLineId)).ToList()); + x.Lines.Select(l => new SalesSlipLineDto(l.SalesSlipLineId, l.ItemId, l.Description, l.Qty, l.FreeQty, l.UomId, l.WarehouseId, l.UnitPrice, l.BaseCost, l.PriceSource, l.DiscountPct, l.DiscountAmount, l.DiscountMode, l.NetUnitPrice, l.LineTotal, l.TaxPct, l.TaxAmount, l.IsFreeIssue, l.ParentLineId)).ToList()); }