fix: the invoice status syncing with booking payment window

This commit is contained in:
ghost2023
2026-07-01 15:46:13 +03:00
parent 7e96fa65b9
commit aef868bc40
5 changed files with 762 additions and 578 deletions

View File

@@ -151,16 +151,22 @@ export class BillingService {
/** Sealed PDF invoice for any source, rendered by the shared document service. */
async document(id: string): Promise<{ filename: string; buffer: Buffer }> {
const invoice = await this.findById(id);
return this.invoiceDocuments.render(this.toDocumentModel(invoice, "INVOICE"));
return this.invoiceDocuments.render(
this.toDocumentModel(invoice, "INVOICE"),
);
}
/** Sealed PDF receipt; available once any payment has been recorded. */
async receipt(id: string): Promise<{ filename: string; buffer: Buffer }> {
const invoice = await this.findById(id);
if (Number(invoice.paidAmount) <= 0) {
throw new BadRequestException("A receipt is available only after payment is recorded.");
throw new BadRequestException(
"A receipt is available only after payment is recorded.",
);
}
return this.invoiceDocuments.render(this.toDocumentModel(invoice, "RECEIPT"));
return this.invoiceDocuments.render(
this.toDocumentModel(invoice, "RECEIPT"),
);
}
/** Map a global invoice (+ lines) onto the source-agnostic document model. */
@@ -177,7 +183,11 @@ export class BillingService {
if (Number(invoice.taxAmount) > 0) {
totals.push({ label: "Tax", amount: Number(invoice.taxAmount) });
}
totals.push({ label: "Total", amount: Number(invoice.totalAmount), grand: true });
totals.push({
label: "Total",
amount: Number(invoice.totalAmount),
grand: true,
});
totals.push({ label: "Paid", amount: Number(invoice.paidAmount) });
totals.push({ label: "Balance", amount: Number(invoice.balanceAmount) });
@@ -193,8 +203,18 @@ export class BillingService {
{ label: "Type", value: invoice.type },
{ label: "Reference", value: invoice.sourceId },
{ label: "Currency", value: invoice.currency },
{ label: "Issued", value: invoice.issuedAt ? new Date(invoice.issuedAt).toLocaleDateString("en-GB") : null },
{ label: "Due", value: invoice.dueAt ? new Date(invoice.dueAt).toLocaleDateString("en-GB") : null },
{
label: "Issued",
value: invoice.issuedAt
? new Date(invoice.issuedAt).toLocaleDateString("en-GB")
: null,
},
{
label: "Due",
value: invoice.dueAt
? new Date(invoice.dueAt).toLocaleDateString("en-GB")
: null,
},
],
categoryHeader: "Charge type",
lines: invoice.lines.map((l) => ({
@@ -303,7 +323,10 @@ export class BillingService {
/** `<CODE>-YYYYMMDD-00001` — sequential per day & prefix, within the active transaction. */
private nextInvoiceNumber(mg: EntityManager): Promise<string> {
return nextDailyInvoiceNumber(mg, { table: "freight.invoices", code:"INV" });
return nextDailyInvoiceNumber(mg, {
table: "freight.invoices",
code: "INV",
});
}
/**
@@ -351,8 +374,7 @@ export class BillingService {
input.subtotalAmount ??
lines.reduce((sum, l) => sum + Number(l.amount), 0);
const taxAmount = input.taxAmount ?? 0;
const totalAmount =
input.totalAmount ?? round2(subtotalAmount + taxAmount);
const totalAmount = input.totalAmount ?? round2(subtotalAmount + taxAmount);
const dueAt =
input.dueAt ??
@@ -438,7 +460,9 @@ export class BillingService {
manager?: EntityManager,
): Promise<Invoice> {
if (!(input.amount > 0)) {
throw new BadRequestException("Payment amount must be greater than zero.");
throw new BadRequestException(
"Payment amount must be greater than zero.",
);
}
const mg = manager ?? this.dataSource.manager;
@@ -473,17 +497,13 @@ export class BillingService {
};
const payments = [...(invoice.payments ?? []), entry];
await mg.update(
Invoice,
{ id: invoice.id },
{
paidAmount,
balanceAmount,
status,
payments,
paidAt: fullyPaid ? at : invoice.paidAt ?? null,
} as never,
);
await mg.update(Invoice, { id: invoice.id }, {
paidAmount,
balanceAmount,
status,
payments,
paidAt: fullyPaid ? at : (invoice.paidAt ?? null),
} as never);
const updated = {
...invoice,
@@ -491,7 +511,7 @@ export class BillingService {
balanceAmount,
status,
payments,
paidAt: fullyPaid ? at : invoice.paidAt ?? null,
paidAt: fullyPaid ? at : (invoice.paidAt ?? null),
} as Invoice;
if (fullyPaid) this.emitInvoiceEvent("paid", updated);
@@ -712,6 +732,20 @@ export class BillingService {
await mg.update(Invoice, { id: invoice.id }, { dueAt });
}
async updateStatus(
invoiceId: string,
status: Freight.InvoiceStatus,
manager?: EntityManager,
): Promise<void> {
const mg = manager ?? this.dataSource.manager;
const invoice = await mg.findOne(Invoice, {
where: { id: invoiceId, status: In(OPEN_STATUSES) },
order: { issuedAt: "DESC" },
});
if (!invoice) return;
await mg.update(Invoice, { id: invoice.id }, { status });
}
// ── Payment initiation & settlement (the gateway boundary) ───────────────────
/**
@@ -738,7 +772,9 @@ export class BillingService {
): Promise<InitiateResponseDto> {
const invoice = await this.findPayable(source, sourceId);
if (!invoice) {
throw new NotFoundException(`No open invoice to charge for ${source}:${sourceId}`);
throw new NotFoundException(
`No open invoice to charge for ${source}:${sourceId}`,
);
}
const result = await this.payment.initiate({