mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-28 20:40:55 +00:00
feat: ( payment ) specific CBE query descriptions + Payment_Reason
This commit is contained in:
@@ -55,6 +55,27 @@ const OPEN_STATUSES: Freight.InvoiceStatus[] = [
|
||||
Freight.InvoiceStatus.Overdue,
|
||||
];
|
||||
|
||||
/**
|
||||
* Why a non-open invoice can no longer be paid, in the vocabulary the payment service's CBE
|
||||
* bill-query mapper understands. Kept specific: CBE reads this back to the payer at the counter,
|
||||
* so "cancelled" must not stand in for "already paid" or "refunded".
|
||||
*/
|
||||
function closedInvoiceReason(status: Freight.InvoiceStatus): string {
|
||||
switch (status) {
|
||||
case Freight.InvoiceStatus.Paid:
|
||||
return "ALREADY_PAID";
|
||||
case Freight.InvoiceStatus.Refunded:
|
||||
return "REFUNDED";
|
||||
case Freight.InvoiceStatus.Cancelled:
|
||||
return "CANCELLED";
|
||||
case Freight.InvoiceStatus.Expired:
|
||||
return "EXPIRED";
|
||||
// Draft — issued to nobody yet, so there is nothing honest to say beyond "not payable".
|
||||
default:
|
||||
return "NOT_PAYABLE";
|
||||
}
|
||||
}
|
||||
|
||||
/** A single line to bill on a generated invoice. */
|
||||
export interface InvoiceLineInput {
|
||||
chargeType: string;
|
||||
@@ -1097,6 +1118,7 @@ export class BillingService {
|
||||
currentAmountMinor?: number | null;
|
||||
currency?: string | null;
|
||||
reason?: string | null;
|
||||
paymentReason?: string | null;
|
||||
}> {
|
||||
const repo = this.dataSource.getRepository(Invoice);
|
||||
const open = await repo.findOne({
|
||||
@@ -1113,7 +1135,12 @@ export class BillingService {
|
||||
payerName: open.company?.name ?? null,
|
||||
currentAmountMinor: balance,
|
||||
currency: open.currency,
|
||||
reason: expired ? "EXPIRED" : balance > 0 ? null : "ALREADY_PAID",
|
||||
// CBE shows this beside the amount on the confirmation screen — the invoice number
|
||||
// the payer is holding, not our internal reference.
|
||||
paymentReason: `Freight invoice ${open.invoiceNumber}`,
|
||||
// Settled-in-full wins over past-due: an invoice with nothing left to pay is paid, not
|
||||
// expired, and that is what the payer at the CBE counter must be told.
|
||||
reason: balance > 0 ? (expired ? "EXPIRED" : null) : "ALREADY_PAID",
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1122,15 +1149,24 @@ export class BillingService {
|
||||
relations: { company: true },
|
||||
order: { createdAt: "DESC" },
|
||||
});
|
||||
// A bill reference whose invoice no longer exists at all — a data problem, not a
|
||||
// cancellation the payer did anything to cause.
|
||||
if (!latest) {
|
||||
return {
|
||||
stillPayable: false,
|
||||
payerName: null,
|
||||
currentAmountMinor: null,
|
||||
currency: null,
|
||||
reason: "NOT_FOUND",
|
||||
};
|
||||
}
|
||||
return {
|
||||
stillPayable: false,
|
||||
payerName: latest?.company?.name ?? null,
|
||||
currentAmountMinor: latest ? Math.round(Number(latest.totalAmount)) : null,
|
||||
currency: latest?.currency ?? null,
|
||||
reason:
|
||||
latest?.status === Freight.InvoiceStatus.Paid
|
||||
? "ALREADY_PAID"
|
||||
: "CANCELLED",
|
||||
payerName: latest.company?.name ?? null,
|
||||
currentAmountMinor: Math.round(Number(latest.totalAmount)),
|
||||
currency: latest.currency,
|
||||
paymentReason: `Freight invoice ${latest.invoiceNumber}`,
|
||||
reason: closedInvoiceReason(latest.status),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,9 +57,13 @@ export class MarkPaidResponseDto {
|
||||
* "is this invoice still payable, by whom, for how much" while a CBE channel is on the line.
|
||||
*/
|
||||
export class BillQueryRequestDto {
|
||||
// Typed `string`, not the enum: the @nestjs/swagger CLI plugin resolves an enum-typed
|
||||
// property to a relative require() into packages/types, which does not exist inside the
|
||||
// Docker image (only /app is copied) and crashes at boot with MODULE_NOT_FOUND. The
|
||||
// decorators below still give us enum docs + runtime validation.
|
||||
@ApiProperty({ enum: PaymentReferenceType })
|
||||
@IsEnum(PaymentReferenceType)
|
||||
referenceType!: PaymentReferenceType;
|
||||
referenceType!: string;
|
||||
|
||||
@ApiProperty() @IsString() referenceId!: string;
|
||||
}
|
||||
@@ -69,6 +73,8 @@ export class BillQueryResponseDto {
|
||||
@ApiPropertyOptional() payerName?: string | null;
|
||||
@ApiPropertyOptional() currentAmountMinor?: number | null;
|
||||
@ApiPropertyOptional() currency?: string | null;
|
||||
/** When stillPayable=false: "CANCELLED" | "ALREADY_PAID" | "EXPIRED". */
|
||||
/** When stillPayable=false: "ALREADY_PAID" | "CANCELLED" | "REFUNDED" | "EXPIRED" | "NOT_FOUND" | "NOT_PAYABLE". */
|
||||
@ApiPropertyOptional() reason?: string | null;
|
||||
/** What the payer is paying for — CBE renders it beside the amount (Payment_Reason). */
|
||||
@ApiPropertyOptional() paymentReason?: string | null;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user