diff --git a/Distribution/Features/Invoice/IInvoiceRepository.cs b/Distribution/Features/Invoice/IInvoiceRepository.cs index f8fb3b4..b4e7f21 100644 --- a/Distribution/Features/Invoice/IInvoiceRepository.cs +++ b/Distribution/Features/Invoice/IInvoiceRepository.cs @@ -9,6 +9,7 @@ public interface IInvoiceRepository Task> GetAllAsync(Guid? agentId, Guid? routeId, Guid? shopId, InvoiceStatus? status, PaymentStatus? paymentStatus, int page, int pageSize); Task GetCountAsync(Guid? agentId, Guid? routeId, Guid? shopId, InvoiceStatus? status, PaymentStatus? paymentStatus); Task> GetOutstandingAsync(); + Task GetSchemeDiscountAsync(Guid shopPOId); Task AddAsync(Models.Invoice invoice); Task GetItemAsync(Guid itemId); Task AddItemsAsync(IEnumerable items); diff --git a/Distribution/Features/Invoice/InvoiceDTOs.cs b/Distribution/Features/Invoice/InvoiceDTOs.cs index 0966786..70673de 100644 --- a/Distribution/Features/Invoice/InvoiceDTOs.cs +++ b/Distribution/Features/Invoice/InvoiceDTOs.cs @@ -45,6 +45,7 @@ public record InvoiceResponse( DateTime? FinalizedAt, Guid? FinalizedBy, decimal TotalAmount, + decimal DiscountAmount, decimal PaidAmount, bool IsActive, DateTime CreatedAt, diff --git a/Distribution/Features/Invoice/InvoiceRepository.cs b/Distribution/Features/Invoice/InvoiceRepository.cs index 482f4ed..77eb0d4 100644 --- a/Distribution/Features/Invoice/InvoiceRepository.cs +++ b/Distribution/Features/Invoice/InvoiceRepository.cs @@ -51,6 +51,11 @@ public class InvoiceRepository(AppDBContext context) : IInvoiceRepository .Where(i => i.IsActive && i.PaymentStatus != PaymentStatus.Paid) .ToListAsync(); + public async Task GetSchemeDiscountAsync(Guid shopPOId) => + await context.SchemeRedemptions + .Where(r => r.FK_ShopPOId == shopPOId) + .SumAsync(r => r.DiscountAmount) ?? 0m; + public async Task AddAsync(Models.Invoice invoice) => await context.Invoices.AddAsync(invoice); diff --git a/Distribution/Features/Invoice/InvoiceService.cs b/Distribution/Features/Invoice/InvoiceService.cs index bed5e68..a8ce1a8 100644 --- a/Distribution/Features/Invoice/InvoiceService.cs +++ b/Distribution/Features/Invoice/InvoiceService.cs @@ -19,7 +19,7 @@ public class InvoiceService( i.Status, i.PaymentStatus, i.CreatedBy, i.PrintedAt, i.PrintedBy, i.FinalizedAt, i.FinalizedBy, - i.TotalAmount, + i.TotalAmount, i.DiscountAmount, i.Payments?.Where(p => p.IsActive).Sum(p => p.Amount) ?? 0m, i.IsActive, i.CreatedAt, i.Items.Where(x => x.IsActive).Select(x => @@ -30,6 +30,15 @@ public class InvoiceService( private static decimal BillableTotal(IEnumerable items) => items.Where(i => i.IsActive && !i.IsRemoved).Sum(i => i.Quantity * i.UnitPrice); + // TotalAmount = billable lines - scheme discount redeemed on the PO. Re-derived whenever lines change + // and at finalize, so a scheme applied after the invoice was drafted is still picked up before it locks. + private async Task ApplyTotalsAsync(Models.Invoice invoice, IEnumerable items) + { + var billable = BillableTotal(items); + invoice.DiscountAmount = Math.Min(await repo.GetSchemeDiscountAsync(invoice.FK_ShopPOId), billable); + invoice.TotalAmount = billable - invoice.DiscountAmount; + } + // Immutable copy of the invoice lines as they stand right now. public static InvoiceVersion BuildVersion(Models.Invoice invoice, IEnumerable items, int versionNumber, InvoiceVersionStage stage, Guid userId) => new() @@ -115,15 +124,15 @@ public class InvoiceService( FK_ShopPOId = po.Id, Status = InvoiceStatus.Draft, PaymentStatus = PaymentStatus.Pending, - CreatedBy = createdBy, - TotalAmount = po.TotalAmount + CreatedBy = createdBy }; + var lines = BuildItemsFromShopPO(po, invoice.Id); + await ApplyTotalsAsync(invoice, lines); await uow.BeginAsync(ct); try { await repo.AddAsync(invoice); - var lines = BuildItemsFromShopPO(po, invoice.Id); await repo.AddItemsAsync(lines); await repo.AddVersionAsync(BuildVersion(invoice, lines, 1, InvoiceVersionStage.Created, createdBy)); po.InvoiceId = invoice.Id; @@ -192,7 +201,7 @@ public class InvoiceService( throw new InvalidOperationException("Each edit needs an invoiceItemId (existing line) or an itemId (new line)"); } - invoice.TotalAmount = BillableTotal(invoice.Items.Concat(newLines)); + await ApplyTotalsAsync(invoice, invoice.Items.Concat(newLines)); await uow.BeginAsync(ct); try @@ -223,6 +232,10 @@ public class InvoiceService( if (invoice.Status != InvoiceStatus.Draft) throw new InvalidOperationException($"Cannot print an invoice in {invoice.Status} status"); + // Printing fixes the amount the customer sees, so pick up any scheme discount applied since the last edit. + var lines = invoice.Items.Where(i => i.IsActive).ToList(); + if (lines.Count > 0) await ApplyTotalsAsync(invoice, lines); + invoice.Status = InvoiceStatus.Printed; invoice.PrintedAt = DateTime.Now; invoice.PrintedBy = printedBy; @@ -265,7 +278,7 @@ public class InvoiceService( } if (items.Count > 0) - invoice.TotalAmount = BillableTotal(items); + await ApplyTotalsAsync(invoice, items); invoice.Status = InvoiceStatus.Finalized; invoice.FinalizedAt = DateTime.Now;