From 1c18fdbd529b7b080fd2abdb8dcd8a7a6937dd66 Mon Sep 17 00:00:00 2001 From: Marshal Date: Tue, 7 Jul 2026 09:49:12 +0000 Subject: [PATCH] Enhance contract management by adding booking windows section and updating invoice logic for offloaded cargo --- .../contracts/gl-operations.service.ts | 12 +- .../contracts/ExportClearanceStepper.tsx | 6 +- .../contracts/GlUpcomingWindowsSection.tsx | 75 +++- .../contracts/ContractClearanceDetailPage.tsx | 5 + .../backoffice/src/types/trainScheduling.ts | 2 + .../ContractBookingWindowsSection.tsx | 325 ++++++++++++++++++ .../pages/contracts/ContractDetailPage.tsx | 13 +- .../src/pages/contracts/NewContractPage.tsx | 6 +- 8 files changed, 422 insertions(+), 22 deletions(-) create mode 100644 apps/edr-freight-web/portal/src/pages/contracts/ContractBookingWindowsSection.tsx diff --git a/apps/edr-freight-api/src/modules/contracts/gl-operations.service.ts b/apps/edr-freight-api/src/modules/contracts/gl-operations.service.ts index 181d8b688..28a31c331 100644 --- a/apps/edr-freight-api/src/modules/contracts/gl-operations.service.ts +++ b/apps/edr-freight-api/src/modules/contracts/gl-operations.service.ts @@ -395,14 +395,20 @@ export class GlOperationsService { } if (!file) throw new BadRequestException('Attach the invoice document.'); + // Invoiceable once cargo is offloaded, or — for export, where OFFLOADED is a + // DJ doc milestone that may never be recorded — once the Djibouti gate pass + // is secured. The invoice itself stays optional; nothing forces GL DJ to send one. const milestones = await this.milestoneService.listForBooking(bookingId); const offloaded = milestones.find( (m) => m.milestoneCode === 'OFFLOADED' && m.status === 'COMPLETED', ); if (!offloaded) { - throw new BadRequestException( - 'Cargo must be offloaded before the final invoice can be raised.', - ); + const gatepass = await this.gatepassForBooking(bookingId); + if (!gatepass.granted) { + throw new BadRequestException( + 'Cargo must be offloaded (or the gate pass secured) before the final invoice can be raised.', + ); + } } const existing = await this.billingService.findInvoice( diff --git a/apps/edr-freight-web/backoffice/src/components/contracts/ExportClearanceStepper.tsx b/apps/edr-freight-web/backoffice/src/components/contracts/ExportClearanceStepper.tsx index db6e79430..dc59c4dfc 100644 --- a/apps/edr-freight-web/backoffice/src/components/contracts/ExportClearanceStepper.tsx +++ b/apps/edr-freight-web/backoffice/src/components/contracts/ExportClearanceStepper.tsx @@ -646,7 +646,9 @@ function FinalInvoiceStep({ const invoice = clearance.finalInvoice ?? null; const paid = invoice?.status === "PAID"; - if (!clearance.offloaded && !invoice) { + // Export: OFFLOADED is a DJ doc milestone that may never be recorded, so the + // secured gate pass is enough to open invoicing. Sending an invoice is optional. + if (!clearance.offloaded && !clearance.gatepassGranted && !invoice) { return ( - Cargo offloaded — send the final invoice to the customer. + Send the final invoice to the customer if post-arrival charges apply (optional).