Steps are now company -> owner -> representation -> contact -> documents;
PersonnelStep and PoaStep are gone.
- OwnerStep shows whoever the eTrade licence names, read-only with a
provenance badge (SourcedField), and renders an input for every gap eTrade
and Fayda left. It warns when the stored owner does not match eTrade.
- RepresentationStep asks outright whether anyone holds power of attorney,
then branches: "no" verifies the owner, "yes" collects the representative
and the DARS delegation letter. Freight forwarders cannot answer "no".
- Passport input appears only for a foreign company whose subject has not
verified with Fayda.
- Drop every same-as-owner copy-across and the auth-user fallbacks for the
owner's email and phone. Re-picking a business licence now clears the
eTrade owner prefill, since licences under one TIN can name different
managers.
- Settings: TabGeneralManager replaced by TabOwner; TabPowerOfAttorney
reworked around poaDeclared.
fayda-identity covers the two subjects, the passport alternative and the
eTrade owner comparison in place of ~40 general-manager assertions.
poa-delegation drops the poaSameAsOwner waiver case and asserts the opposite:
a forwarder is refused without a delegation letter however it represents
itself.
Data rescue, not just schema tidying — the order matters:
1. Backfill companies.email from generalManagerEmail before the key is
stripped, or companies whose only address lived there stop receiving mail.
2. Backfill ownerName/Email/Phone (now required) from owner, GM, contact
person, then the columns; seed etradeManagerName so existing rows read as
matching rather than as a false mismatch.
3. Convert poaSameAsOwner: non-forwarders become poaDeclared='no' with their
poa* details cleared; forwarders become 'yes' and keep them, so they now
owe a DARS letter they were previously waived.
4. Derive poaDeclared for everyone else.
5. Move drafts off the deleted 'personnel' wizard step.
6. Drop the general_manager_* columns and strip gm*/poaSameAsOwner.
down() restores the column shape only; the stripped values are gone by design.
Replaces the owner/general-manager/PoA trio with a single identity whose
subject is the PoA when the company declares one and the owner otherwise.
- Drop the general manager everywhere: entity columns, DTOs, required-field
list, self-service attributes, gm* identity handling.
- New explicit poaDeclared answer ("yes"/"no") replaces poaSameAsOwner. A
DARS delegation letter is required iff it is "yes"; a freight forwarder is
forced to "yes" server-side and gets no waiver.
- Owner name/email/phone become typeable and required, prefilled from the
eTrade lookup, never falling back to the authenticated account.
- Store eTrade's manager separately (etradeManagerName/Phone) and expose
ownerMatchesEtrade so backoffice compares the asserted owner against the
licence instead of against itself.
- Foreign companies satisfy the identity with Fayda or a passport number, on
whichever subject is verifying (poaPassportNumber added).
- Write companies.email/phone from the owner unconditionally, so a company
without a Fayda-verified owner still has a notification address.
LAST_MILE_REQUESTS URL constants were missing the /api prefix every other
endpoint in URLS.ts carries — all five calls (get, submit, contract
view/document/sign) 404'd against the deployed API, so the departure
notification's confirm link never loaded for the customer.
Also widen GET /last-mile-requests/:id from @BookingStaff to @MixedAudience
with the same ownership check submit()/sign() already use — the confirm
page calls this as its first request, before the customer has done
anything else, so it can't be staff-only.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
End-to-end testing of the direct truck-to-train flow surfaced two defects
in the carriage acceptance sheet's no-wagon fallback. Container numbers
were read from freight.containers, which only gains rows at allocation,
so a direct booking's declared containers never appeared; they are read
from booking_container_units now, seal numbers included. The weight used
bulkTotalWeightTons alone, which is null for CONTAINER and PER_TON bulk
bookings, printing 0.00 — cargoTotalWeightVgm is the real weight there
and only holds an item count for PER_ITEM break-bulk.
Separately, warehouse cargo can be loaded from the Load-to-Train queue or
from the schedule, but loading from the schedule never advanced
warehouse_inventory. The booking went IN_TRANSIT while its cargo still
read as sitting in the warehouse — the same inconsistency that produced
the spurious dispatch block. loadBooking now moves the inventory to
LOADED in the same transaction, and no-ops for direct bookings.