The customer-truck freight order went through ContractPdfService, whose only
fallback is a crude text dump - so on servers without Chromium the document
rendered as unstyled text.
- render via the shared PdfRenderService with the styled tabular fallback
(same path as marshalling / release documents)
- rebuild the freight-order HTML in the standard document shape: brand header,
booking summary tiles, one trucks table (plate / driver / type / containers /
arrival), gate notice, signature lines - which is also what the fallback
parser reads
- keep the two watermarked copies (Port Operations, Gate Security & Carrier)
for the Chromium path; drop the now-unused ContractPdfService injection
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- remove Incoterms, HS codes, and Item code from the truck-entrance form:
nothing in the booking captures them, so they were always hand-typed noise
- remove the hand-typed "Warehouse code and location" input: the backend now
stamps it from the warehouse/yard/zone the operator actually selected
(WH / YARD / ZONE codes), so the GRN and notes always match reality
- Declaration number and Item description widen to full rows
Backend DTO keeps the optional fields for compatibility.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The receive form now arrives filled with everything the booking already knows,
instead of the operator re-typing it:
- container numbers come from the per-unit records (booking_container_units)
entered at booking time, falling back to the line-level aggregate
- customs seal number prefilled from the units' seal numbers
- net weight prefilled from the booking's declared cargo weight (tonnes)
- driver signatory defaults to the arriving driver's name
Existing prefills (owner, TIN, phone, booking ref, cargo, packaging, unit
count, first-mile / customer truck + driver) unchanged; all fields stay
editable where they were editable before.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Truck arrives (portal-assigned or walk-in) → handover generated → flag true → button appears on detail page and dashboard, + notification every 5 min.
Customer clicks → reviews the handover PDF → signs → signed_at stamped → sign-modal invalidates both the booking and the list queries → flag false → button gone, reminders stop.
Operator's Exit Paper signature-request on a booking with no handover also creates one → button appears the same way.
Wagon loading happens in the train flow and dispatch at the train level, which
already advances inventory - the per-row buttons duplicated that and confused
operators.
- WarehouseInventoryTable (import dispatch queue + warehouse pages): suppress
the 'load'/'dispatch' next-action buttons and drop the extra per-row Dispatch
on READY_FOR_PICKUP rows (Store stays)
- Export Dispatch Queue: drop the per-row Dispatch button and its Actions
column; the bulk Dispatch All / Dispatch Selected controls remain
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Approve delivery now tracks the handover lifecycle exactly: the button appears
(detail page + portal dashboard) the moment a handover is generated and
disappears when the customer signs. The bookings list attaches the
handoverAwaitingSignature flag via one batched query per page; status
heuristics (COMPLETED / TRUCK_ASSIGNED+arrived) are gone.
- generate the arrival handover for ANY self-haul truck: portal-assigned OR
walk-in registered at the gate (isSelfHaulBooking: assigned_at set, or no
EDR last-mile leg). Same rule now guards the exit paper.
- remind every 5 minutes (in-app + SMS + email) until the handover is signed
(@Cron in HandoverService; one reminder per booking per tick)
- Truck Leaving no longer opens blank: the import queue mapper now carries
inv.notes, so the saved arrival renders read-only with only gate-out time
and gross weight editable
- container bookings get "Weigh truck? Yes/No": No skips tare/gross and the
container weight match (weighingSkipped on ReleaseOrderDto, decision made at
arrival sticks for the exit via the Weighing: SKIPPED note). Bulk always
weighs, unchanged.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Blob downloads set responseType: 'blob', so axios delivers the JSON error body
as a Blob. extractErrorMessage() reads `.message` off it, finds nothing, and
falls back to "Request failed with status code 400" - hiding every real reason
("Handover must be signed...", "...must be fully paid before terminal release").
Only ContainerItemsModal used the async Blob decoder. Switch the remaining nine
blob-download catches to extractDownloadErrorMessage():
InventoryWorkbench release paper, handover
WarehouseInventoryTable GRN
ReceiveInventoryModal GRN (x2), handover, exit paper
FeePreviewModal release paper
TruckDispatchModal truck exit paper
Mutation-error catches are untouched - their bodies are already parsed JSON.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
READY_FOR_PICKUP allows STORED (park an import item back into storage), but
STORED allowed only RESERVED / READY_FOR_LOADING - so readyForPickup() hit
assertTransition(STORED, READY_FOR_PICKUP) and threw. The item could never
return to pickup, and getNextInventoryAction returned null for an import STORED
item, leaving the row with no action at all.
- allow STORED -> READY_FOR_PICKUP
- an inspected import STORED item now advances to ready-for-pickup
readyForPickup() still rejects non-IMPORT inventory, so the new edge cannot be
reached from the export flow.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
AddGrnNumberToWarehouseInventory1828000000000 is recorded in the migrations
table but the column is absent - it was added, then dropped out-of-band. Because
TypeORM has the original recorded it will never re-run, so every GRN read/write
fails with "column grn_number does not exist":
- bulkReceive() INSERT names grn_number (receive to warehouse)
- importQueueByStatuses() Unloaded + Dispatch queues
- exportInventoryByStatus() Received / Ready-To-Load / Loaded tabs
- grnDocument() GRN PDF
Re-adds the column, backfills from the "GRN Number:" receive note, recreates the
partial index. Idempotent, and down() is a deliberate no-op so reverting the
repair cannot re-introduce the outage.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Added default capacities for container and bulk wagons.
- Updated wagonsFor method to consider weight and length for wagon calculations.
- Improved handling of overweight bookings with appropriate warnings.
- Adjusted tests to reflect changes in wagon capacity and validation logic.