- Added StationWorkControls to manage loading/unloading phases in TrainScheduleV2DetailPage.
- Implemented API endpoints for recording station work and managing wagon detach requests.
- Updated contract templates to include Ethiopian customs handling options.
- Enhanced shipment forms to collect customs clearing agent details for without-customs bookings.
- Introduced NUMBER_OF_WAGONS as a unit of measure for bulk cargo, allowing customers to specify wagon counts.
- Improved validation for customs clearing agent information in shipment forms.
- Updated various components and services to accommodate new features and ensure data integrity.
- Implemented read-only locking for customer-requested container sizes and billing currency in the GlCreateBookingForm component.
- Added functionality to lock partner quantities based on shipment requests in the ConsolidationPartnerPanel.
- Introduced a new Leave action in the LogPassYardWorkModal to unassign bookings from trains.
- Enhanced the AuditLogsPage to support filtering by action and added a Go button for direct navigation to entity detail pages.
- Updated WagonCancellationsPage to handle odd-20ft credits requiring partner selection during rebooking.
- Improved TrainScheduleV2DetailPage to allow manual loading of cargo and display warnings for unassigned bookings.
- Added a new reference field to the audit logs for better searchability and tracking of actions.
- Created a migration to add the reference column to the audit logs table and established an index for efficient querying.
- Defined a registry for audit reference sources to streamline the retrieval of human identifiers for various entities.
- planned couples: loose wagons join the train at a route stop, added
from the schedule yards tab; capacity credits them per corridor edge
and coupling validates locomotive weight/length caps per leg
- real-cut toggle: a cut wagon permanently leaves the train build at
its cut yard (soft cut still sits out one trip only)
- fix heaviest-leg display counting a shared slot's full cargo on
every spanned edge (phantom pull-weight overload on S-2026-00045)
- confirmation dialogs for workspace add/load/unload/remove actions
- train-builder History and Detached-wagons tabs, backed by paginated
endpoints; builder detaches now always write adjustment-log rows
Migrations 3660 (planned_wagon_couples, planned_wagon_real_cuts) and
3670 (adjustment log train_schedule_id nullable) — both applied to the
dev DB by hand; watch mode does not run migrations.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Empty container returns now generate a downloadable Equipment Interchange Receipt: container number & size, exact return timestamp, depot/location, condition, and the carrier/booking reference. Customers can download it from their booking's Documents tab (own-booking returns only); staff can pull any via the same endpoint.
Backoffice: new tab on the booking detail page (?tab=additional-charges,
already linked from the row menu) with an Add Charge modal — Save draft or
Send to customer.
Portal: charges panel on the booking detail page, Pay now per charge via
the existing OTP/CBE-bill payment flow.
Also fixes: GET /bookings/:id/additional-charges was returning DRAFT
charges to the customer (hidden client-side only) — now filtered
server-side per caller.
- Added support for viewing and managing consolidated bookings in BookingRequestDetailPage.
- Enhanced BookingRequestsPage to display paired bookings in a single row.
- Introduced pairedDecision method in bookings service to handle decisions for both halves of a consolidated pair.
- Updated contracts service to include methods for manual consolidation of odd-20ft bookings.
- Created new components for selecting and editing consolidation partners.
- Added tests for paired decision logic and manual consolidation scenarios.
- Updated UI to reflect changes in booking handling and provide user feedback for odd container counts.
A resumed onboarding asked for the per-role business licence again and refused
to submit until it was uploaded a second time, however many were already on
file. `getInfo` never populates `companyProfiles[].licenseFiles`, and the
wizard validates against exactly that — so every profile looked empty, while
`markOnboardingComplete` would have accepted the application as it stood.
Validation now reads `licenseProfiles[].uploaded` from the onboarding
requirements: the server's own verdict, already fetched by the step, and the
same source the API enforces on submit.
Reachable today by anyone who resumes onboarding after a licence upload, and by
every company that switches back to eTrade registration — which is where the
e2e suite hit it.
The Company, Owner, Contact and Power of Attorney tabs had drifted from the
onboarding wizard while the manual-registration routes were added, and each
divergence locked somebody out of their own account:
- VAT was validated as 10-or-11 digits here and by onboarding's rule there. A
foreign company's VAT is its own tax authority's and a co-operative's follows
neither, so the stricter copy failed every save on the tab, not just the VAT
field. The schema now reuses onboarding's, with a test pinning both rules.
- Fayda's phone claims are normalized on read, as the wizard does — the
registry holds local numbers that neither the form nor the API's
@IsValidPhone() accepts.
- The eTrade manager is shown read-only, because that record is exactly what
the backoffice checks the company against; leaving it editable let a customer
overwrite the value under review.
- A co-operative's per-role licence cards are hidden on the Documents tab. The
API lifts the requirement, so the slots could never be filled or cleared.
Both manual-registration routes exist for one reason — eTrade holds no record
to fetch — so leaving one is the same act whichever it is. A co-operative that
has since taken out a trade licence had no way out at all: the flag is chosen
in the wizard, and an onboarded company can no longer reach it.
revert-to-etrade now accepts either flag and strips both, and a co-operative's
ACTIVE roles go back to Pending with it: those approvals were granted without a
per-role business licence, because a co-op owes none, and the moment it stops
being one that licence is due. Rejected, suspended and blacklisted roles are
left alone — promoting a blocked role to "awaiting approval" would launder a
staff decision away. An investor's roles are untouched; their licences were
always due and already reviewed.
Switching INTO a route stays with the wizard's nationality/role step, which
owns the mutually-exclusive rules; a second entry point would restate all three.
The Region select called setValue without shouldDirty. `region` is an
eTrade-bundle key, and stepPayload sends those only when the customer changed
them this session — so for the two routes that type their address by hand (a
co-operative, a foreign investor) the region was dropped on every save while
zone, woreda and kebele went through, because those are registered inputs and
are dirty by construction.
Found by the new onboarding e2e suite: both manual-route companies finished
onboarding with zone/woreda/kebele on file and region empty.
A company that ticked the investment-licence box by mistake, or that has since
been registered with the trade registry, had no way back — the flag is chosen
once, on a step onboarding never returns to.
The Company tab now carries a Registration source card for those companies.
It is a re-application rather than a settings edit, so the confirmation says
so outright: the typed registration is cleared, the company returns to pending
and the wizard reopens on the company step, while documents, owner and contact
details stay. Hidden for everyone else, and disabled while a profile change
request is under review — switching then would strand it.
A foreign company can now say it operates on an investment licence, on the
same step as its nationality and roles. The box only appears for a foreign
company, and moving the nationality answer back to Ethiopian drops it — the
API refuses both pairings.
The company step's eTrade gate now reads `manualRegistration`
(co-operative OR investment licence): the TIN lookup still runs, but finding
nothing is an expected outcome rather than a blocker, and the registration
section is typed instead. What stays keyed to `cooperative` alone is the
per-role business licence — an investor holds one, a co-operative does not —
so the licence cards and their validation are unchanged for investors.
Also carries the client plumbing for the revert endpoint the settings card
uses next.