mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-26 18:42:49 +00:00
eims cancellation
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { BadRequestException } from "@nestjs/common";
|
||||
import { BadRequestException, ConflictException } from "@nestjs/common";
|
||||
import { DataSource } from "typeorm";
|
||||
|
||||
import { Invoice } from "../billing/entities/invoice.entity";
|
||||
@@ -96,14 +96,16 @@ describe("EimsCancellationService.cancelInvoiceWithEims", () => {
|
||||
expect(postBearer).toHaveBeenCalledWith("/v1/cancel", { Irn: IRN, ReasonCode: "1", Remark: "" });
|
||||
});
|
||||
|
||||
it("is idempotent — an already-cancelled invoice returns unchanged, no HTTP call", async () => {
|
||||
const db = new FakeDb([invoiceRow({ eimsStatus: EimsInvoiceStatus.Cancelled })]);
|
||||
it("refuses re-cancelling an already-cancelled invoice, per IRC-N010 — no silent no-op", async () => {
|
||||
const db = new FakeDb([
|
||||
invoiceRow({ eimsStatus: EimsInvoiceStatus.Cancelled, eimsCancellationDate: "Sun Dec 22 2024" }),
|
||||
]);
|
||||
const postBearer = jest.fn();
|
||||
|
||||
const view = await build(db, postBearer).cancelInvoiceWithEims(INVOICE_ID, "1");
|
||||
|
||||
await expect(build(db, postBearer).cancelInvoiceWithEims(INVOICE_ID, "1")).rejects.toBeInstanceOf(
|
||||
ConflictException,
|
||||
);
|
||||
expect(postBearer).not.toHaveBeenCalled();
|
||||
expect(view.eimsStatus).toBe(EimsInvoiceStatus.Cancelled);
|
||||
});
|
||||
|
||||
it("refuses to cancel an invoice that was never registered", async () => {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { BadRequestException, Injectable, Logger, NotFoundException } from "@nestjs/common";
|
||||
import { BadRequestException, ConflictException, Injectable, Logger, NotFoundException } from "@nestjs/common";
|
||||
import { InjectDataSource } from "@nestjs/typeorm";
|
||||
import { DataSource, EntityManager } from "typeorm";
|
||||
|
||||
@@ -38,8 +38,11 @@ export class EimsCancellationService {
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Idempotent: an already-cancelled invoice returns unchanged, no HTTP call. Refuses an invoice
|
||||
* that was never registered — there is no IRN to cancel.
|
||||
* Refuses an already-cancelled invoice with a 409, rather than a silent no-op — IRC-N010 in
|
||||
* MoR's Master Compliance Checklist requires "an appropriate error or rejection message" for a
|
||||
* repeat cancellation, not a quiet success. No HTTP call either way: this is a local check, not
|
||||
* a retry against MoR. Also refuses an invoice that was never registered — there is no IRN to
|
||||
* cancel.
|
||||
*/
|
||||
async cancelInvoiceWithEims(
|
||||
invoiceId: string,
|
||||
@@ -48,7 +51,12 @@ export class EimsCancellationService {
|
||||
): Promise<EimsInvoiceStatusView> {
|
||||
const eligible = await this.dataSource.transaction(async (manager) => {
|
||||
const invoice = await this.lockInvoice(manager, invoiceId);
|
||||
if (invoice.eimsStatus === EimsInvoiceStatus.Cancelled) return null;
|
||||
if (invoice.eimsStatus === EimsInvoiceStatus.Cancelled) {
|
||||
throw new ConflictException({
|
||||
code: "EIMS_ALREADY_CANCELLED",
|
||||
message: `Invoice ${invoice.invoiceNumber} was already cancelled with EIMS${invoice.eimsCancellationDate ? ` (${invoice.eimsCancellationDate})` : ""}.`,
|
||||
});
|
||||
}
|
||||
if (!invoice.eimsIrn) {
|
||||
throw new BadRequestException({
|
||||
code: "EIMS_NOT_REGISTERED",
|
||||
@@ -57,7 +65,6 @@ export class EimsCancellationService {
|
||||
}
|
||||
return invoice;
|
||||
});
|
||||
if (!eligible) return this.getEimsCancellationStatus(invoiceId);
|
||||
|
||||
const request: EimsCancelRequest = { Irn: eligible.eimsIrn!, ReasonCode: reasonCode, Remark: remark ?? "" };
|
||||
// Outside any transaction — no DB lock is held across the wire.
|
||||
|
||||
@@ -81,7 +81,7 @@ export class EimsInvoiceController {
|
||||
@BookingStaff(FREIGHT_PERMS.invoices.eimsCancel)
|
||||
@ApiOperation({
|
||||
summary:
|
||||
"Cancel the invoice's registered EIMS document. Idempotent — an already-cancelled invoice is returned unchanged.",
|
||||
"Cancel the invoice's registered EIMS document. Refuses (409) an already-cancelled invoice rather than a silent no-op — see IRC-N010.",
|
||||
})
|
||||
cancel(@Param("id", ParseUUIDPipe) id: string, @Body() dto: CancelEimsRegistrationDto) {
|
||||
return this.cancellation.cancelInvoiceWithEims(id, dto.reasonCode, dto.remark);
|
||||
|
||||
Reference in New Issue
Block a user