Add manual invoice creation mode with new API & UI flow
CI/CD - ERP Distribution / Build & Deploy Frontend (pull_request) Failing after 13m13s
CI/CD - ERP Distribution / Build & Deploy Backend (pull_request) Failing after 14m28s

Introduced "ModeB" for manual invoice creation in system config. Added backend endpoints and service logic for creating invoices from confirmed Shop POs, with validation and error handling. Updated DTOs to support optional CreatedByUserId. On the frontend, added configuration card to toggle modes, a dialog for manual invoice creation, and UI updates to support the new workflow. Enables admins to choose between automatic and manual invoice creation.
This commit is contained in:
Kalana B. Thilakarathna
2026-10-20 13:38:43 +05:30
parent 5707bd4c31
commit 2de9e5e4ad
12 changed files with 344 additions and 5 deletions
@@ -1,4 +1,5 @@
using Distribution.Features.Stock;
using Distribution.Features.ShopPO;
using Distribution.Features.Stock;
using Distribution.Infra.Authorization;
using Distribution.Infra.UoW;
using Distribution.Models;
@@ -7,6 +8,7 @@ namespace Distribution.Features.Invoice;
public class InvoiceService(
IInvoiceRepository repo,
IShopPORepository shopPoRepo,
IUnitOfWork uow,
ICurrentUserService currentUser)
{
@@ -42,6 +44,41 @@ public class InvoiceService(
return new OutstandingPaymentsResponse(count, outstanding);
}
public async Task<InvoiceResponse> CreateAsync(CreateInvoiceRequest request, Guid createdBy, CancellationToken ct = default)
{
var po = await shopPoRepo.GetByIdAsync(request.ShopPOId)
?? throw new KeyNotFoundException("Shop PO not found");
if (po.ConfirmedAt is null)
throw new InvalidOperationException("Shop PO must be confirmed before an invoice can be created");
if (po.Status == ShopPOStatus.Cancelled)
throw new InvalidOperationException("Cannot create an invoice for a cancelled Shop PO");
if (po.InvoiceId is not null)
throw new InvalidOperationException("An invoice already exists for this Shop PO");
var invoice = new Models.Invoice
{
FK_ShopPOId = po.Id,
Status = InvoiceStatus.Draft,
PaymentStatus = PaymentStatus.Pending,
CreatedBy = createdBy,
TotalAmount = po.TotalAmount
};
await uow.BeginAsync(ct);
try
{
await repo.AddAsync(invoice);
po.InvoiceId = invoice.Id;
shopPoRepo.Update(po);
await uow.CommitAsync(ct);
}
catch { await uow.RollbackAsync(ct); throw; }
var saved = await repo.GetByIdAsync(invoice.Id);
return ToResponse(saved!);
}
public async Task<InvoiceResponse> PrintAsync(Guid id, Guid printedBy, CancellationToken ct = default)
{
var invoice = await repo.GetByIdAsync(id)