5707bd4c31
Introduced StoreCreditMovement entity and migration to track store credit usage and settlements. Updated payment flow to enforce credit limits and record credit movements. Modified outstanding balance calculation to use credit movements. Enhanced frontend to display real-time credit info and prevent over-credit payments. Updated API types, seed data, and DI registrations accordingly.
102 lines
4.0 KiB
C#
102 lines
4.0 KiB
C#
using Distribution.Features.Stock;
|
|
using Distribution.Infra.Authorization;
|
|
using Distribution.Infra.UoW;
|
|
using Distribution.Models;
|
|
|
|
namespace Distribution.Features.Invoice;
|
|
|
|
public class InvoiceService(
|
|
IInvoiceRepository repo,
|
|
IUnitOfWork uow,
|
|
ICurrentUserService currentUser)
|
|
{
|
|
private static InvoiceResponse ToResponse(Models.Invoice i) =>
|
|
new(i.Id, i.FK_ShopPOId,
|
|
i.ShopPO?.FK_StoreId ?? Guid.Empty,
|
|
i.ShopPO?.Store?.StoreName ?? string.Empty,
|
|
i.Status, i.PaymentStatus, i.CreatedBy,
|
|
i.PrintedAt, i.PrintedBy,
|
|
i.FinalizedAt, i.FinalizedBy,
|
|
i.TotalAmount,
|
|
i.Payments?.Where(p => p.IsActive).Sum(p => p.Amount) ?? 0m,
|
|
i.IsActive, i.CreatedAt);
|
|
|
|
public async Task<InvoiceResponse?> GetByIdAsync(Guid id)
|
|
{
|
|
var invoice = await repo.GetByIdAsync(id);
|
|
return invoice is null ? null : ToResponse(invoice);
|
|
}
|
|
|
|
public async Task<PagedResponse<InvoiceResponse>> GetAllAsync(Guid? agentId, Guid? routeId, Guid? shopId, InvoiceStatus? status, PaymentStatus? paymentStatus, int page, int pageSize)
|
|
{
|
|
var total = await repo.GetCountAsync(agentId, routeId, shopId, status, paymentStatus);
|
|
var items = await repo.GetAllAsync(agentId, routeId, shopId, status, paymentStatus, page, pageSize);
|
|
return new PagedResponse<InvoiceResponse>(page, pageSize, total, items.Select(ToResponse).ToList());
|
|
}
|
|
|
|
public async Task<OutstandingPaymentsResponse> GetOutstandingSummaryAsync()
|
|
{
|
|
var invoices = await repo.GetOutstandingAsync();
|
|
var count = invoices.Count;
|
|
var outstanding = invoices.Sum(i => i.TotalAmount - i.Payments.Sum(p => p.Amount));
|
|
return new OutstandingPaymentsResponse(count, outstanding);
|
|
}
|
|
|
|
public async Task<InvoiceResponse> PrintAsync(Guid id, Guid printedBy, CancellationToken ct = default)
|
|
{
|
|
var invoice = await repo.GetByIdAsync(id)
|
|
?? throw new KeyNotFoundException("Invoice not found");
|
|
|
|
if (invoice.Status != InvoiceStatus.Draft)
|
|
throw new InvalidOperationException($"Cannot print an invoice in {invoice.Status} status");
|
|
|
|
invoice.Status = InvoiceStatus.Printed;
|
|
invoice.PrintedAt = DateTime.Now;
|
|
invoice.PrintedBy = printedBy;
|
|
|
|
await uow.BeginAsync(ct);
|
|
try { repo.Update(invoice); await uow.CommitAsync(ct); }
|
|
catch { await uow.RollbackAsync(ct); throw; }
|
|
|
|
return ToResponse(invoice);
|
|
}
|
|
|
|
public async Task<InvoiceResponse> FinalizeAsync(Guid id, FinalizeInvoiceRequest request, Guid finalizedBy, CancellationToken ct = default)
|
|
{
|
|
var invoice = await repo.GetByIdAsync(id)
|
|
?? throw new KeyNotFoundException("Invoice not found");
|
|
|
|
if (invoice.Status != InvoiceStatus.Printed)
|
|
throw new InvalidOperationException($"Cannot finalize an invoice in {invoice.Status} status");
|
|
|
|
var adjustments = request.Adjustments ?? [];
|
|
if (adjustments.Count > 0)
|
|
{
|
|
var poItems = invoice.ShopPO.Items.Where(i => i.IsActive).ToList();
|
|
decimal total = 0;
|
|
foreach (var item in poItems)
|
|
{
|
|
var adjustment = adjustments.FirstOrDefault(a => a.ShopPOItemId == item.Id);
|
|
var billedQuantity = adjustment is not null ? adjustment.AdjustedQuantity : item.Quantity;
|
|
|
|
if (billedQuantity < 0 || billedQuantity > item.Quantity)
|
|
throw new InvalidOperationException(
|
|
$"Adjusted quantity for item {item.Id} must be between 0 and {item.Quantity}");
|
|
|
|
total += billedQuantity * item.UnitPrice;
|
|
}
|
|
invoice.TotalAmount = total;
|
|
}
|
|
|
|
invoice.Status = InvoiceStatus.Finalized;
|
|
invoice.FinalizedAt = DateTime.Now;
|
|
invoice.FinalizedBy = finalizedBy;
|
|
|
|
await uow.BeginAsync(ct);
|
|
try { repo.Update(invoice); await uow.CommitAsync(ct); }
|
|
catch { await uow.RollbackAsync(ct); throw; }
|
|
|
|
return ToResponse(invoice);
|
|
}
|
|
}
|