diff --git a/.portal-flows-assets/00-invoice-detail-pay.png b/.portal-flows-assets/00-invoice-detail-pay.png
new file mode 100644
index 000000000..c57f4b977
Binary files /dev/null and b/.portal-flows-assets/00-invoice-detail-pay.png differ
diff --git a/.portal-flows-assets/01-invoices-due-full.png b/.portal-flows-assets/01-invoices-due-full.png
new file mode 100644
index 000000000..bea5dc558
Binary files /dev/null and b/.portal-flows-assets/01-invoices-due-full.png differ
diff --git a/.portal-flows-assets/02-login-full.png b/.portal-flows-assets/02-login-full.png
new file mode 100644
index 000000000..f0820e0a8
Binary files /dev/null and b/.portal-flows-assets/02-login-full.png differ
diff --git a/.portal-flows-assets/03-hager-invoices-empty-full.png b/.portal-flows-assets/03-hager-invoices-empty-full.png
new file mode 100644
index 000000000..2b56a9fcd
Binary files /dev/null and b/.portal-flows-assets/03-hager-invoices-empty-full.png differ
diff --git a/.portal-flows-assets/04-contracts-list-full.png b/.portal-flows-assets/04-contracts-list-full.png
new file mode 100644
index 000000000..99763e1b2
Binary files /dev/null and b/.portal-flows-assets/04-contracts-list-full.png differ
diff --git a/.portal-flows-assets/05-home-full.png b/.portal-flows-assets/05-home-full.png
new file mode 100644
index 000000000..3eb3d0d6c
Binary files /dev/null and b/.portal-flows-assets/05-home-full.png differ
diff --git a/.portal-flows-assets/06-contract-detail-signed-full.png b/.portal-flows-assets/06-contract-detail-signed-full.png
new file mode 100644
index 000000000..801016e19
Binary files /dev/null and b/.portal-flows-assets/06-contract-detail-signed-full.png differ
diff --git a/.portal-flows-assets/07-contract-step1-full.png b/.portal-flows-assets/07-contract-step1-full.png
new file mode 100644
index 000000000..43b2fe8b0
Binary files /dev/null and b/.portal-flows-assets/07-contract-step1-full.png differ
diff --git a/.portal-flows-assets/08-contract-step1-filled-full.png b/.portal-flows-assets/08-contract-step1-filled-full.png
new file mode 100644
index 000000000..02cbf4d03
Binary files /dev/null and b/.portal-flows-assets/08-contract-step1-filled-full.png differ
diff --git a/.portal-flows-assets/09-contract-step2-full.png b/.portal-flows-assets/09-contract-step2-full.png
new file mode 100644
index 000000000..193fc5252
Binary files /dev/null and b/.portal-flows-assets/09-contract-step2-full.png differ
diff --git a/.portal-flows-assets/10-contract-step3-review-full.png b/.portal-flows-assets/10-contract-step3-review-full.png
new file mode 100644
index 000000000..5bbd72746
Binary files /dev/null and b/.portal-flows-assets/10-contract-step3-review-full.png differ
diff --git a/.portal-flows-assets/11-contract-duplicate-blocked-full.png b/.portal-flows-assets/11-contract-duplicate-blocked-full.png
new file mode 100644
index 000000000..c93a06db1
Binary files /dev/null and b/.portal-flows-assets/11-contract-duplicate-blocked-full.png differ
diff --git a/.portal-flows-assets/11b-quotation-approve-full.png b/.portal-flows-assets/11b-quotation-approve-full.png
new file mode 100644
index 000000000..f381f4988
Binary files /dev/null and b/.portal-flows-assets/11b-quotation-approve-full.png differ
diff --git a/.portal-flows-assets/13-bookings-list-full.png b/.portal-flows-assets/13-bookings-list-full.png
new file mode 100644
index 000000000..8477b2a76
Binary files /dev/null and b/.portal-flows-assets/13-bookings-list-full.png differ
diff --git a/.portal-flows-assets/14-booking-documents-modal-full.png b/.portal-flows-assets/14-booking-documents-modal-full.png
new file mode 100644
index 000000000..b58f9b919
Binary files /dev/null and b/.portal-flows-assets/14-booking-documents-modal-full.png differ
diff --git a/.portal-flows-assets/15-nati-contracts-list-full.png b/.portal-flows-assets/15-nati-contracts-list-full.png
new file mode 100644
index 000000000..191f75f81
Binary files /dev/null and b/.portal-flows-assets/15-nati-contracts-list-full.png differ
diff --git a/.portal-flows-assets/16-nati-contracts-list-actions.png b/.portal-flows-assets/16-nati-contracts-list-actions.png
new file mode 100644
index 000000000..d7fd73df7
Binary files /dev/null and b/.portal-flows-assets/16-nati-contracts-list-actions.png differ
diff --git a/.portal-flows-assets/17-initiate-booking-confirm.png b/.portal-flows-assets/17-initiate-booking-confirm.png
new file mode 100644
index 000000000..16b0b481d
Binary files /dev/null and b/.portal-flows-assets/17-initiate-booking-confirm.png differ
diff --git a/.portal-flows-assets/18-nati-bookings-list-actions.png b/.portal-flows-assets/18-nati-bookings-list-actions.png
new file mode 100644
index 000000000..f4446db24
Binary files /dev/null and b/.portal-flows-assets/18-nati-bookings-list-actions.png differ
diff --git a/.portal-flows-assets/19-complete-booking-cargo-full.png b/.portal-flows-assets/19-complete-booking-cargo-full.png
new file mode 100644
index 000000000..1cb7c216a
Binary files /dev/null and b/.portal-flows-assets/19-complete-booking-cargo-full.png differ
diff --git a/.portal-flows-assets/portal-flows.html b/.portal-flows-assets/portal-flows.html
new file mode 100644
index 000000000..2bbd40681
--- /dev/null
+++ b/.portal-flows-assets/portal-flows.html
@@ -0,0 +1,312 @@
+
+
+
+
+
EDR Freight — Priority Flows
+
Portal Side
Contract Signing · Booking · Payment
+
A step-by-step walkthrough of the same three flows, driven live as the real
+ customer — every screen, button, and validation message as the account actually saw it.
+
+
+
+
+ 1 · Contract creation & signing
+ Portal customer hager@gmail.com, logged into the freight portal (localhost:5273).
+
+
+
+
The portal login form. A customer signs in with their email and password — no OTP step for an already-onboarded account (OTP only applies during self-signup).
+

+
+
+
+
+
Landing page after login: quick stats and shortcuts into Contracts, Bookings, and Invoices.
+

+
+
+
+
+
The customer's contracts: one Active (CTR-2026-00043, Fully Executed) and two In Progress (CTR-2026-00086, CTR-2026-00087 — both Submitted, awaiting EDR review). "New Contract" starts a fresh one.
+

+
+
+
+
4
New Contract — Step 1: Setup
+
Operation Type, Contract Kind (One-Time vs. Framework), New-vs-Renewal, and the Service Type cards — the entry point of the wizard.
+

+
+
+
+
5
Step 1 filled — Operation Type + Service
+
Operation Type set to Import and "Rail Transport with Customs" selected — this reveals the trucking/customs options below (first mile, last mile, and the included customs clearing service).
+

+
+
+
+
+
Cargo scope (containerised, both 20ft/40ft), and the origin/destination yard pair that defines the route.
+

+
+
+
+
+
Final review of the assembled contract terms — operation, service, route, cargo scope, and customs handling — before submitting.
+

+
+
+
+
+
Submitting immediately opens a per-unit rate quotation for the customer to approve — freight rates for 20ft/40ft containers plus the customs clearance service fee per container size. The dialog is explicit that these are per-unit rates, not a total: the payable amount is computed per booking from the quantities actually shipped. Approving submits the contract for EDR staff review.
+

+
+
+
+
9
Real validation: duplicate contract blocked
+
Re-submitting a route/service/cargo-scope combination that already has an active or in-review contract is rejected outright, naming the conflicting contract by number. This is a genuine business rule hit live, not staged.
+

+
+
+
+
Once EDR approves the contract, the customer signs it
+ A submitted contract moves through EDR review as:
Submitted → Pending Approval → Approved →
+ Approved Pending Signature → Contract Ready. The "View & sign contract" button only
+ appears once the contract reaches
Contract Ready — it shows on the Contracts list row and
+ on the contract's own detail page, and opens a dedicated preview-and-sign page
+ (
/contracts/:id/view), not a plain button on the detail page.
+
+ - EDR staff approve the contract and price it.
+ - The customer is notified the moment it's ready to sign — SMS, email, and an in-app
+ notification all fire together (per the customer's description of the real notification
+ flow — not re-verified against the notification-sending code for this document).
+ - The customer opens the notification or the Contracts list, clicks "View & sign
+ contract", scrolls the full contract text, and ticks the consent checkbox.
+ - They draw or reuse a saved signature, upload a company stamp, then verify by OTP
+ (sent to their registered phone/email) to finalize the signature.
+ - Status moves to Signed — Awaiting Staff, then Fully Executed once EDR counter-signs.
+
+ CTR-2026-00086 and CTR-2026-00087 above are still at Submitted — EDR hasn't approved them yet,
+ so no sign button is showing for either.
+
+
+
+
11
Where "View & sign" actually shows (a different customer's contract list)
+
A second real account (nati@gmail.com, 17 contracts) confirms the button live: its Action column carries "View", "Request shipment", "Book shipment", and "Initiate booking" depending on each row's status — the same column shows "View & sign" the moment a row reaches Contract Ready.
+

+
+
+
+ The signable window is real and it closes
+ This account's own CTR-2026-00089 was captured with a live "View & sign" button two days ago
+ (per the customer's own screenshot) — by the time this document was rebuilt, that same contract
+ had already moved to Expired with no button left to click. Contract Ready is a real,
+ time-boxed state, not a permanent one. No currently-open contract exists on either test account
+ at time of writing, so the sign page itself (scroll-to-consent → draw signature → stamp → OTP)
+ could not be re-captured live in this pass; it was documented from the customer's own screenshots
+ and code inspection instead.
+
+
+
+
10
Fully executed contract — signatures
+
CTR-2026-00043's detail page: both STAFF and CUSTOMER signatures are complete and the contract is Fully Executed — the end state of the flow described above.
+

+
+
+
+
+ 2 · Booking
+ A portal customer does not have a bare "New booking" button on the Bookings page
+ itself — booking creation is triggered from a signed contract's row, once EDR has cleared it for
+ shipment (contract status Active). What that entry point actually is depends on the contract's
+ state: "Initiate booking" opens a brand-new booking; "Book shipment" / "Book" continues one EDR
+ has already pre-cleared.
+
+
+
1
Initiate booking — confirmation
+
Clicking "Initiate booking" on an Active contract (CTR-2026-00071) doesn't create the booking immediately — it confirms first: "This creates a new shipment booking under contract CTR-2026-00071. You'll upload the import documents next, and the shipment quantity is drawn down from your contract's reserved capacity." Cancelled here rather than completed, since creating a live booking is a real mutation.
+

+
+
+
+
2
Complete Your Booking — cargo details
+
Following an already-cleared booking's "Book" action (BK-2026-000209, Bulk cargo under CTR-2026-00093) lands on this form: the contract's fixed route, a cargo-details block (Total weight for Bulk cargo — a Container-scoped contract shows an Excel-import table instead), and a Schedule block picking the binding shipment day. The billing currency here is fixed to ETB, paid through the payment gateway. The calendar is real and reactive: it read "Enter your cargo details first — available shipment days depend on the wagons your cargo needs," and no day was actually open on this route at the time of capture (0 available days) — the same live batch-window behaviour documented in Section 1.
+

+
+
+
+
3
Bookings list — one contract's view
+
This account's one existing booking, BK-2026-000068 on CTR-2026-00043 — status "In review," amount still ETB 0.00 because pricing is finalised after document review, not at booking creation. Every row carries a "Review documents" action.
+

+
+
+
+
+
Clicking "Review documents" opens this in place, no page navigation. All four required import documents — Bill of Lading/Waybill, Packing List, Import declaration, and Ethiopia T1 & Djibouti T1 — show status "Under review," each with View/Download and a "Replace file" drop zone. The banner is explicit: "Only re-upload the documents flagged with a query below — approved documents stay as they are." "Submit documents" stays disabled while nothing is flagged.
+

+
+
+
+
5
Bookings list — the fuller picture (nati@gmail.com, 38 bookings)
+
Across a larger real history the Action column carries every state at once: "Book" (cleared, awaiting cargo details), "View" (nothing to do), "Rebook wagons" (a cancelled reservation), "Pay [amount]" (booked, payment outstanding), "Review documents" / "Documents", and "Track" once a train is assigned. BK-2026-000188 shows "Pay 16,323.25 ETB" while its own Status column reads Cancelled — a live inconsistency (the booking lapsed after its payment window closed, but the stale Pay action hadn't been cleared from the row).
+

+
+
+
+
+ 3 · Payment
+ The customer-facing payment surface is the Invoices page.
+
+
+
1
Invoices — hager@gmail.com (nothing to pay)
+
Outstanding, Overdue, and Total Invoices all read 0. This lines up with the Booking flow above: BK-2026-000068 is still in document review and hasn't been priced, so nothing has been invoiced to this customer yet. The customer supplied a second real login (nati@gmail.com) to reach the actual payment screen.
+

+
+
+
+
2
Invoices — nati@gmail.com, filtered to Due
+
This account has a long invoice history (35 invoices) and 5 currently Outstanding. The status filter's real options are Due / Overdue / Paid / Draft / Cancelled / Refunded; filtering to "Due" narrows the list to what's actually payable right now, each row with a "Pay" action.
+

+
+
+
+
3
Invoice detail — the Pay screen
+
Opening INV-20260820-00017 (Br 200.00, a port-charges line item off booking BK-2026-000112) shows the full detail a customer sees before paying: billed-to company, source, issue/due dates, the line-item breakdown, a "Download invoice" action, and a "Pay Br 200.00" button sized to the exact amount due.
+

+
+
+
+ What "Pay" opens (from the customer's own screenshot)
+ Clicking "Pay" opens a "Complete your payment" dialog: the amount due, a red warning —
+ "Pay only from a bank account registered under your company name — DE BE KE. A payment sent
+ from an account under any other name will not be recognized as paid" — a payment-method
+ picker (the option shown was CBE Bill Payment — "Pay at any CBE branch, app or USSD · ETB"),
+ a "Secured — you'll be redirected to your provider to pay" note, then "Continue to payment".
+ This was captured by the customer directly, not by this document — every attempt here to click a
+ live "Pay" button (on this invoice and on two different bookings) was blocked by the harness's own
+ financial-mutation guard before the dialog could be reached.
+
+
+
+
Paying by bank transfer — step by step (customer-provided process)
+
+ - Open your internet banking app or account.
+ - Copy the PNR code EDR sent you by SMS and email.
+ - In your bank's payment menu, choose Travel, then Land Transport, then
+ EDR Freight as the biller.
+ - Paste the PNR code into the reference/biller-code field.
+ - Confirm the amount shown matches the invoice total, then pay.
+
+ This is the settlement path behind the "Pay" button above — the bank confirms the PNR against
+ EDR's own records, so the code must match exactly what was sent. These steps were supplied
+ directly by the customer describing the real banking flow; they are not something this
+ document observed inside the portal UI itself, which only exposes the "Pay" button shown above.
+
+
+
+ Stopped short of submitting the payment — three separate attempts, all blocked
+ Clicking "Pay Br 200.00" on the invoice above, and separately clicking "Pay" on two different
+ bookings (BK-2026-000195 and BK-2026-000188) while rebuilding this document, were all blocked by
+ the harness's own auto-mode permission classifier as a real financial mutation against the shared
+ dev database — the same guard that blocked "Confirm paid" on the backoffice side. No workaround
+ was attempted, per the classifier's own instruction. The invoice screen above is the actual entry
+ point a customer uses; the backoffice-side companion document (EDR-Freight-Priority-Flows.pdf)
+ covers staff-side settlement (Transactions → Manual Payments).
+
+
+
+
+
diff --git a/.portal-flows-assets/render.mjs b/.portal-flows-assets/render.mjs
new file mode 100644
index 000000000..4b0780043
--- /dev/null
+++ b/.portal-flows-assets/render.mjs
@@ -0,0 +1,25 @@
+import { chromium } from '/home/tria/projects/hagernesh/edr-platform/node_modules/.pnpm/playwright-core@1.61.1/node_modules/playwright-core/index.mjs';
+import { readFileSync } from 'node:fs';
+import path from 'node:path';
+
+const dir = path.dirname(new URL(import.meta.url).pathname);
+let html = readFileSync(path.join(dir, 'portal-flows.html'), 'utf8');
+
+html = html.replace(/\{\{([\w.-]+\.png)\}\}/g, (_, file) => {
+ const buf = readFileSync(path.join(dir, file));
+ return `data:image/png;base64,${buf.toString('base64')}`;
+});
+
+const browser = await chromium.launch({
+ executablePath: '/home/tria/.cache/ms-playwright/chromium-1237/chrome-linux64/chrome',
+});
+const page = await browser.newPage();
+await page.setContent(html, { waitUntil: 'load' });
+await page.pdf({
+ path: '/home/tria/projects/hagernesh/edr-platform/EDR-Freight-Priority-Flows-Portal.pdf',
+ format: 'A4',
+ printBackground: true,
+ margin: { top: '14mm', bottom: '16mm', left: '14mm', right: '14mm' },
+});
+await browser.close();
+console.log('done');
diff --git a/EDR-Freight-Priority-Flows-Portal.pdf b/EDR-Freight-Priority-Flows-Portal.pdf
index fe37cc9e8..8d8115757 100644
Binary files a/EDR-Freight-Priority-Flows-Portal.pdf and b/EDR-Freight-Priority-Flows-Portal.pdf differ
diff --git a/apps/edr-freight-api/src/modules/train-scheduling/services/train-scheduling.service.spec.ts b/apps/edr-freight-api/src/modules/train-scheduling/services/train-scheduling.service.spec.ts
index 57bbb2c58..35eed0f80 100644
--- a/apps/edr-freight-api/src/modules/train-scheduling/services/train-scheduling.service.spec.ts
+++ b/apps/edr-freight-api/src/modules/train-scheduling/services/train-scheduling.service.spec.ts
@@ -1134,6 +1134,81 @@ describe('TrainSchedulingService', () => {
expect(html).toContain('2 (1 empty)');
});
+ it('marks a leg slot on the import document as TO BE LOADED and keeps it out of the loaded tallies', () => {
+ const loadList = {
+ generatedAt: '2026-07-17T08:00:00.000Z',
+ trainScheduleId: 'schedule-1',
+ trainNumber: '7002',
+ route: 'DCT/SGTD → GMP',
+ origin: 'DCT/SGTD',
+ destination: 'GMP',
+ totalBookings: 2,
+ wagons: [
+ {
+ sequenceNo: 1,
+ wagonNumber: 'W-ICY',
+ boardYard: 'Dire Dawa Port',
+ alightYard: null,
+ allocations: [
+ {
+ ...loadedAllocation,
+ containerItems: [{ containerNumber: 'ICY-001' }],
+ },
+ ],
+ },
+ {
+ sequenceNo: 2,
+ wagonNumber: 'W-IMP',
+ boardYard: null,
+ alightYard: null,
+ allocations: [
+ {
+ ...loadedAllocation,
+ containerItems: [{ containerNumber: 'CONT-001', containerType: { sizeFt: 20 } }],
+ },
+ ],
+ },
+ ],
+ operation: { status: {} },
+ };
+
+ const html = (service as never as {
+ buildImportLoadListHtml: (l: unknown) => string;
+ }).buildImportLoadListHtml(loadList);
+
+ expect(html).toContain('TO BE LOADED AT DIRE DAWA PORT');
+ // Departure station of the leg slot is its board yard, not the origin.
+ expect(html).toContain('