Payment summary/all moved off bookings:view onto payments:view, the
booking list now requires bookings:view for staff instead of accepting
any employee, and wagon reads require wagons:view or fleet:view rather
than bare authentication.
Neither was wired into the app, so every /facilities route and
POST /bookings/:id/allocate-containers returned 404 and their permission
keys granted nothing.
Nest runs class and method guards together, so a class-level view key
ANDs with every action key below it. Staff granted only an action were
denied before their key was ever checked: OCC could not fulfil wagon
transfers, dispatchers could not create a yard, and track staff could not
assign first/last-mile vehicles. Reads now carry the view key themselves,
and the warehouses baseline lists every key its routes use.
Gates the previously open support-agent, procurement, compliance,
facilities, list-users and trade-access controllers, separates customer
from staff routes across bookings, contracts, companies, billing,
warehouses, files and train scheduling, and moves billing, overview,
reports and the settings controllers onto their own keys instead of the
blanket admin key. Drops the demo-permissions module and the untested
notification test route.
Adds the support, procurement, compliance, facilities, trade-access,
overview, reports and staff-users keys, plus the split action keys for
bookings, contracts, train scheduling and settings. Retires eight seeded
keys that no feature ever enforced, revoking their grants first.
FreightPermissionGuard now rejects non-employee user types before the key
check, making every BookingStaff route staff-only in one place. Adds
PortalCustomer and MixedAudience for the routes both audiences share, and
stops ServiceAuthGuard failing open when SERVICE_AUTH_TOKEN is unset.
container mode: the Add Rate dialog offers the multi-tier editor in both
modes, each tier is created as its own rate row, and overlapping bulk
bands are rejected. Pricing picks the tier whose half-open band holds
the trip km, falling back to the legacy bandless bulk rate.
The rate matrix currency cell hardcoded USD, so a last-mile rate
priced in ETB still displayed as dollars. formatCell now takes the
row and reads its currency code, falling back to USD.
Last-mile currency select gets defaultValue ETB (new generic
FormFieldDef.defaultValue for create-time pre-selection) and lists
ETB (Birr) first; USD stays selectable. Backend already persisted
and validated the chosen currency.
Approval now snapshots the rate estimate and generates a last-mile
contract instead of invoicing immediately. The customer picks a delivery
date on the confirm form, then reviews and signs the contract in the
portal (saved signature or drawn); the signed PDF is stored as
LM_<CustomerName>.pdf and only then is the advance invoice issued.
Backoffice shows signature status and the contract download.
- tierList field type in rule-engine form dialog (add/remove rows,
overlap + open-ended validation, From km auto-continues)
- create submits one rate row per tier sequentially
- editing a band row keeps the single From/To/value form
feat(bookings): add event handlers for booking invoice payment processing
fix(bookings): include PAYMENT_VERIFICATION_IN_PROGRESS status in queries
fix(train-scheduling): update status checks to include PAYMENT_VERIFICATION_IN_PROGRESS
feat(notifier): notify customers when a train is cancelled
- Implemented a method in to permanently delete wagons without history.
- Added corresponding permissions for hard delete actions in .
- Updated the UI components to include purge actions, ensuring they are only available to users with the appropriate permissions.
- Created modals for confirming permanent deletions in and .
- Enhanced API services to handle purge requests for locomotives, wagons, and routes.
- Added tests for the purge functionality in both and services to ensure proper behavior and error handling.
- Updated ContractDocumentViewModelBuilder to include cargoTypeName, containerType, and cargoSummary in the schedule.
- Modified contract dynamic template tests to validate the new cargo fields.
- Enhanced contract renderer service tests to reflect changes in cargo data structure.
- Updated contract view model interface to include new cargo-related fields.
- Improved dynamic template rendering to display cargo type and container type.
- Refactored exchange settings controller and service to streamline error handling and feed status management.
- Introduced article HTML conversion functions to support Quill editor integration for structured article editing.
- Added tests for article HTML conversion to ensure correct round-trip processing of clauses and bullets.
- Updated ContractDocumentViewModelBuilder to include cargoTypeName, containerType, and cargoSummary in the schedule.
- Modified contract dynamic template tests to validate the new cargo fields.
- Enhanced contract renderer service tests to reflect changes in cargo data structure.
- Updated contract view model interface to include new cargo-related fields.
- Improved dynamic template rendering to display cargo type and container type.
- Refactored exchange settings controller and service to streamline error handling and feed status management.
- Introduced article HTML conversion functions to support Quill editor integration for structured article editing.
- Added tests for article HTML conversion to ensure correct round-trip processing of clauses and bullets.
The suite drove a containerized freight API, so every code change needed an
image rebuild before a test could see it, and there was no way to attach a
debugger. Files also ran strictly in sequence against one shared database,
which is the root of the warm-stack gotchas the README documents: stowaway
paid bookings climbing back aboard, a short consist on the fifth file.
The freight app now boots inside each vitest worker from dist/, and each
worker owns a whole shard of the topology - its own database, payment API,
gateway mock and broker vhost - so nothing mutable is shared and files run
in parallel. Full suite drops from roughly 20 minutes to 196s at 4 shards.
- main.ts exports createFreightApp() so the harness applies the same prefix,
pipes, filters and interceptors as production instead of replaying them by
hand; self-start is guarded by require.main so the Dockerfile CMD still boots
- booking-window tick cadence is env-driven (BOOKING_WINDOW_TICK_CRON), */1 in
the suite, */10 unchanged in production
- prepare-shards.mjs seeds a template database (boot seeders, then the SQL
fixtures that depend on them) and clones it per shard; it.mjs re-clones on
every run, so each run is hermetic
- gateway mock and payment API are generated per shard: the mock keeps modes
and orders process-global and 20 of 25 specs reset it in beforeAll, and the
inbound CBE bill query has to reach one specific shard's app
- poll() samples every 250ms instead of 2000ms, keeping the caller's deadline
- authz.it.ts seeds its own invoice; it previously read another spec's leftover
and returned early, which silently passed on a pristine database
Known: an unlocked MAX(sequence_no)+1 in train-scheduling.service.ts races
under concurrent allocation and leaves a short consist, so 1-3 specs fail
intermittently. Pre-existing and reproduces at the production tick cadence.
Namespaces the OAuth landing path in all three places it exists: the API's
ack controller, both web apps' routes, and the redirect_uri env values.
A bare /callback claimed a generic top-level path in every app for one
provider's redirect.
The API side needed care. The ack controller moves to @Controller
('fayda/callback'), and the global-prefix exclusion has to name that exact
path — setGlobalPrefix's exclude is an exact route match, not a subtree, so
excluding "fayda" would have left /fayda/callback served at
/api/fayda/callback and 404ing at the registered redirect_uri, while
reading as though it covered everything under /fayda. Naming the full path
also keeps /api/fayda/verification/* prefixed, which every client calls.
Also drops a stale comment on the portal's callback route describing the
popup that no longer exists, and records why the route is public: behind
RequireAuth the onboarding gate redirects to /portal before the code+state
exchange can run.
NOT verified at runtime — this changes route registration, so boot the API
and confirm GET /fayda/callback answers un-prefixed and
/api/fayda/verification/start still resolves before relying on it.
Deploying this requires registering the new redirect_uri with eSignet
first; FAYDA_WEB_REDIRECT_URI, FAYDA_PORTAL_REDIRECT_URI and any mobile
client must be updated in step or verification breaks with a redirect_uri
mismatch.