Refactor code structure for improved readability and maintainability

This commit is contained in:
marshalyordanos
2026-08-11 19:36:00 +03:00
parent 07a120af5e
commit 35e5404b41
20 changed files with 1684 additions and 1933 deletions

View File

@@ -0,0 +1,922 @@
# Freight API — Mutating Endpoints (Audit Surface)
Every state-changing route in `apps/edr-freight-api``POST`, `PUT`, `PATCH`, `DELETE`.
This is the candidate surface for audit logging: each row is an action a user can take
that changes persisted state and therefore needs a who / what / when trail.
All paths include the global prefix `api` (`app.setGlobalPrefix("api")` in `src/main.ts`).
Titles come from each route's `@ApiOperation({ summary })`; where a route has none,
the title is derived from its handler name.
> Generated by reading the `@Post` / `@Put` / `@Patch` / `@Delete` decorators in
> `src/**/*.controller.ts`. Re-generate after adding routes so this stays complete.
## Summary
| Method | Count |
| --- | ---: |
| `POST` | 351 |
| `PUT` | 8 |
| `PATCH` | 72 |
| `DELETE` | 61 |
| **Total** | **492** |
Across **66** entities.
## Entity index
| Entity | Endpoints |
| --- | ---: |
| [Account](#account) | 3 |
| [AI Assist](#ai-assist) | 1 |
| [Approval Rule](#approval-rule) | 5 |
| [Booking](#booking) | 57 |
| [Cargo](#cargo) | 6 |
| [Cargo Type](#cargo-type) | 5 |
| [Company](#company) | 30 |
| [Compliance](#compliance) | 3 |
| [Consignment](#consignment) | 1 |
| [Container](#container) | 5 |
| [Container Type](#container-type) | 5 |
| [Contract](#contract) | 68 |
| [Contract Template](#contract-template) | 8 |
| [Driver](#driver) | 5 |
| [Dropdown Setting](#dropdown-setting) | 7 |
| [EIMS Invoice](#eims-invoice) | 3 |
| [Exchange Setting](#exchange-setting) | 1 |
| [Facility](#facility) | 3 |
| [Fayda Verification](#fayda-verification) | 1 |
| [File Upload Setting](#file-upload-setting) | 7 |
| [First Mile](#first-mile) | 7 |
| [Fuel](#fuel) | 1 |
| [GPS Tracking](#gps-tracking) | 3 |
| [Import Operation](#import-operation) | 9 |
| [Incident](#incident) | 3 |
| [Interchange Document](#interchange-document) | 3 |
| [Last Mile](#last-mile) | 10 |
| [Last Mile Request](#last-mile-request) | 4 |
| [Locomotive](#locomotive) | 4 |
| [Maintenance](#maintenance) | 13 |
| [Notification Inbox](#notification-inbox) | 2 |
| [Organization User](#organization-user) | 2 |
| [OTP](#otp) | 2 |
| [Password Reset](#password-reset) | 4 |
| [Payment](#payment) | 7 |
| [Priority Config](#priority-config) | 5 |
| [Priority Rule Change Request](#priority-rule-change-request) | 3 |
| [Procurement](#procurement) | 8 |
| [Rate](#rate) | 5 |
| [Rate Change Request](#rate-change-request) | 3 |
| [Route](#route) | 4 |
| [Schedule](#schedule) | 3 |
| [Service Type](#service-type) | 5 |
| [Shipping Line](#shipping-line) | 3 |
| [Signature](#signature) | 1 |
| [Support Chat](#support-chat) | 5 |
| [Support Content](#support-content) | 3 |
| [Train](#train) | 3 |
| [Train Build](#train-build) | 11 |
| [Train Schedule](#train-schedule) | 48 |
| [Transit Agent](#transit-agent) | 3 |
| [Truck Type](#truck-type) | 3 |
| [User Trade Access](#user-trade-access) | 1 |
| [Vehicle](#vehicle) | 3 |
| [Wagon](#wagon) | 8 |
| [Wagon Transfer Request](#wagon-transfer-request) | 5 |
| [Wagon Type](#wagon-type) | 3 |
| [Warehouse](#warehouse) | 12 |
| [Warehouse Fee Invoice](#warehouse-fee-invoice) | 5 |
| [Warehouse Inspection Report](#warehouse-inspection-report) | 3 |
| [Warehouse Inventory](#warehouse-inventory) | 24 |
| [Warehouse Yard](#warehouse-yard) | 2 |
| [Warehouse Zone](#warehouse-zone) | 1 |
| [Weight Limit Rule](#weight-limit-rule) | 3 |
| [Yard](#yard) | 5 |
| [Yard Distance](#yard-distance) | 3 |
---
## Endpoints by entity
### Account
| Title | Method | Endpoint | Source |
| --- | --- | --- | --- |
| Send a verification code to a new email/phone before changing it | `POST` | `/api/me/contact/otp` | `modules/auth/account.controller.ts:26` |
| Change the account's email or phone, gated by a verification code | `PATCH` | `/api/me/contact` | `modules/auth/account.controller.ts:40` |
| Change the account's display name | `PATCH` | `/api/me/name` | `modules/auth/account.controller.ts:54` |
### AI Assist
| Title | Method | Endpoint | Source |
| --- | --- | --- | --- |
| Mock AI: extract structured booking fields from free-text request | `POST` | `/api/ai/booking/extract` | `modules/ai/ai.controller.ts:16` |
### Approval Rule
| Title | Method | Endpoint | Source |
| --- | --- | --- | --- |
| Create an approval rule step | `POST` | `/api/approval-rules` | `modules/rule-engine/controllers/approval-rules.controller.ts:66` |
| Move an approval step up or down within its chain | `POST` | `/api/approval-rules/:id/move-order` | `modules/rule-engine/controllers/approval-rules.controller.ts:51` |
| Bulk reorder approval steps within a chain | `POST` | `/api/approval-rules/reorder` | `modules/rule-engine/controllers/approval-rules.controller.ts:43` |
| Update an approval rule | `PATCH` | `/api/approval-rules/:id` | `modules/rule-engine/controllers/approval-rules.controller.ts:73` |
| Soft-delete an approval rule | `DELETE` | `/api/approval-rules/:id` | `modules/rule-engine/controllers/approval-rules.controller.ts:80` |
### Booking
| Title | Method | Endpoint | Source |
| --- | --- | --- | --- |
| Create a new freight booking (DRAFT) | `POST` | `/api/bookings` | `modules/bookings/bookings.controller.ts:170` |
| Allocate containers to vehicles | `POST` | `/api/bookings/:bookingId/allocate-containers` | `modules/bookings/booking-allocation.controller.ts:13` |
| Cancel booking | `POST` | `/api/bookings/:id/cancel` | `modules/bookings/bookings.controller.ts:1544` |
| Customer cancels an unpaid hold (SELECTED_FOR_BATCH → CANCELLED); | `POST` | `/api/bookings/:id/cancel-hold` | `modules/bookings/bookings.controller.ts:1568` |
| GL ET uploads customs declaration on booking (GENERAL customs) | `POST` | `/api/bookings/:id/clearance/declaration` | `modules/bookings/bookings.controller.ts:1126` |
| Upload Booking Delivery Order | `POST` | `/api/bookings/:id/clearance/delivery-order` | `modules/bookings/bookings.controller.ts:1268` |
| Customer uploads clearance documents (fieldname = document key) | `POST` | `/api/bookings/:id/clearance/documents` | `modules/bookings/bookings.controller.ts:959` |
| GL ET sends a draft customs declaration (multi-file) with an estimated price for the customer to review | `POST` | `/api/bookings/:id/clearance/draft-declaration` | `modules/bookings/bookings.controller.ts:1175` |
| Customer accepts the draft customs declaration — unlocks the real customs declaration step for GL Ethiopia | `POST` | `/api/bookings/:id/clearance/draft-declaration/accept` | `modules/bookings/bookings.controller.ts:1200` |
| Customer requests a change to the draft customs declaration with a reason — GL Ethiopia sends a corrected draft (repeatable) | `POST` | `/api/bookings/:id/clearance/draft-declaration/change` | `modules/bookings/bookings.controller.ts:1211` |
| GL ET sets duty/tax on booking with notice attachment | `POST` | `/api/bookings/:id/clearance/duty` | `modules/bookings/bookings.controller.ts:1144` |
| Customer uploads duty/tax payment slip on booking | `POST` | `/api/bookings/:id/clearance/duty-slip` | `modules/bookings/bookings.controller.ts:1238` |
| Confirm Booking Export Release | `POST` | `/api/bookings/:id/clearance/export-release` | `modules/bookings/bookings.controller.ts:1326` |
| GL finalizes clearance (requires 100% approved) → CLEARANCE_READY | `POST` | `/api/bookings/:id/clearance/finalize` | `modules/bookings/bookings.controller.ts:1087` |
| GL ET finalizes import pre-clearance on booking | `POST` | `/api/bookings/:id/clearance/finalize-pre-clearance` | `modules/bookings/bookings.controller.ts:1230` |
| GL uploads customs output documents (IM4/EX3/…) | `POST` | `/api/bookings/:id/clearance/output-documents` | `modules/bookings/bookings.controller.ts:1071` |
| Customer requests operation with a schedule day | `POST` | `/api/bookings/:id/clearance/proceed` | `modules/bookings/bookings.controller.ts:979` |
| Upload Booking Release Order | `POST` | `/api/bookings/:id/clearance/release-order` | `modules/bookings/bookings.controller.ts:1288` |
| GL reviews a clearance document (Approve | Query) | `POST` | `/api/bookings/:id/clearance/review` | `modules/bookings/bookings.controller.ts:1051` |
| Request Booking RO Amendment | `POST` | `/api/bookings/:id/clearance/ro-amendment` | `modules/bookings/bookings.controller.ts:1311` |
| GL Djibouti picks the transit officer from the roster — unblocks the customs declaration; calling again reassigns | `POST` | `/api/bookings/:id/clearance/transit-assignee/assign` | `modules/bookings/bookings.controller.ts:1112` |
| GL ET asks GL Djibouti to name the transit officer — required before the import customs declaration | `POST` | `/api/bookings/:id/clearance/transit-assignee/request` | `modules/bookings/bookings.controller.ts:1098` |
| Upload Booking Transit Permit | `POST` | `/api/bookings/:id/clearance/transit-permit` | `modules/bookings/bookings.controller.ts:1251` |
| Confirm submit after price change | `POST` | `/api/bookings/:id/confirm-submit` | `modules/bookings/bookings.controller.ts:906` |
| Request freight consolidation | `POST` | `/api/bookings/:id/consolidation` | `modules/bookings/bookings.controller.ts:1583` |
| Generate contract PDF from template | `POST` | `/api/bookings/:id/contract/generate` | `modules/bookings/bookings.controller.ts:1406` |
| Apply digital signature (customer or staff) | `POST` | `/api/bookings/:id/contract/sign` | `modules/bookings/bookings.controller.ts:1452` |
| Customer cancels their own booking before payment — no cancellation fee | `POST` | `/api/bookings/:id/customer-cancel` | `modules/bookings/bookings.controller.ts:1555` |
| Customer assigns external truck and driver for terminal pickup | `POST` | `/api/bookings/:id/customer-truck-assignment` | `modules/bookings/bookings.controller.ts:460` |
| Add a customer self-haul truck carrying 12 of the booking containers | `POST` | `/api/bookings/:id/customer-trucks` | `modules/bookings/bookings.controller.ts:686` |
| Register an import truck leaving: containers loaded + weighed gross (staff) | `POST` | `/api/bookings/:id/customer-trucks/:assignmentId/depart` | `modules/bookings/bookings.controller.ts:788` |
| Truck_dispatch: load selected containers onto a truck (staff) | `POST` | `/api/bookings/:id/customer-trucks/:assignmentId/load` | `modules/bookings/bookings.controller.ts:773` |
| Bulk add customer trucks from array payload (Excel parsed) | `POST` | `/api/bookings/:id/customer-trucks/bulk` | `modules/bookings/bookings.controller.ts:701` |
| Customer digital signature (deprecated — use POST contract/sign) | `POST` | `/api/bookings/:id/customer/sign` | `modules/bookings/bookings.controller.ts:1487` |
| Upload documents for a booking (DRAFT only) | `POST` | `/api/bookings/:id/documents` | `modules/bookings/bookings.controller.ts:869` |
| Generate a GRN over the received containers (all received, or a subset) — one GRN per batch | `POST` | `/api/bookings/:id/generate-grn` | `modules/bookings/bookings.controller.ts:820` |
| Generate price preview (DRAFT or CHANGES_REQUESTED) | `POST` | `/api/bookings/:id/generate-price` | `modules/bookings/bookings.controller.ts:882` |
| Expedite government booking to PAID / ELIGIBLE for scheduling | `POST` | `/api/bookings/:id/government-expedite` | `modules/bookings/bookings.controller.ts:1390` |
| Staff contract signature and fully execute (use contract/sign STAFF preferred) | `POST` | `/api/bookings/:id/marketing/approve` | `modules/bookings/bookings.controller.ts:1505` |
| Operations reviews an operation request: ACCEPT (→ batch pool), | `POST` | `/api/bookings/:id/operation/review` | `modules/bookings/bookings.controller.ts:1030` |
| Mark completed | `POST` | `/api/bookings/:id/operations/complete` | `modules/bookings/bookings.controller.ts:1536` |
| Mark in transit | `POST` | `/api/bookings/:id/operations/start-transit` | `modules/bookings/bookings.controller.ts:1528` |
| Customer reject price estimate | `POST` | `/api/bookings/:id/reject` | `modules/bookings/bookings.controller.ts:918` |
| Staff accept intake → set contract validity window + start approval chain | `POST` | `/api/bookings/:id/staff/accept` | `modules/bookings/bookings.controller.ts:1355` |
| Staff final reject | `POST` | `/api/bookings/:id/staff/reject` | `modules/bookings/bookings.controller.ts:1374` |
| Staff return booking for customer updates | `POST` | `/api/bookings/:id/staff/request-changes` | `modules/bookings/bookings.controller.ts:1339` |
| Customer submit booking | `POST` | `/api/bookings/:id/submit` | `modules/bookings/bookings.controller.ts:894` |
| Request a partial wagon cancellation on a PAID booking — opens the cancellation-fee invoice; wagons are released only once the fee settles | `POST` | `/api/bookings/:id/wagon-cancellations` | `modules/bookings/bookings.controller.ts:558` |
| Preview the fee/credit of a partial wagon cancellation (no writes) | `POST` | `/api/bookings/:id/wagon-cancellations/preview` | `modules/bookings/bookings.controller.ts:544` |
| Rebook a wagon-cancellation credit: pick a shipment day only — the new booking is created under the contract and marked PAID (freight already paid; contract must still be valid) | `POST` | `/api/bookings/wagon-cancellations/:cancellationId/rebook` | `modules/bookings/bookings.controller.ts:638` |
| Withdraw a fee-pending wagon cancellation (owner, or staff with the void permission) | `POST` | `/api/bookings/wagon-cancellations/:cancellationId/withdraw` | `modules/bookings/bookings.controller.ts:624` |
| Update booking | `PATCH` | `/api/bookings/:id` | `modules/bookings/bookings.controller.ts:211` |
| Edit a not-yet-arrived customer truck (plate/driver/type + containers) | `PATCH` | `/api/bookings/:id/customer-trucks/:assignmentId` | `modules/bookings/bookings.controller.ts:716` |
| Export only: choose direct truck-to-train (no warehouse, no GRN) or warehouse first | `PATCH` | `/api/bookings/:id/export-handover-mode` | `modules/bookings/bookings.controller.ts:761` |
| Soft-delete DRAFT booking | `DELETE` | `/api/bookings/:id` | `modules/bookings/bookings.controller.ts:861` |
| Remove consolidation pairing | `DELETE` | `/api/bookings/:id/consolidation` | `modules/bookings/bookings.controller.ts:1590` |
| Remove a not-yet-arrived customer truck from a booking | `DELETE` | `/api/bookings/:id/customer-trucks/:assignmentId` | `modules/bookings/bookings.controller.ts:732` |
### Cargo
| Title | Method | Endpoint | Source |
| --- | --- | --- | --- |
| Create a new cargo | `POST` | `/api/cargoes` | `modules/cargoes/cargoes.controller.ts:34` |
| Mark cargo as delivered | `POST` | `/api/cargoes/:id/deliver` | `modules/cargoes/cargoes.controller.ts:81` |
| Load cargo into a container | `POST` | `/api/cargoes/:id/load` | `modules/cargoes/cargoes.controller.ts:67` |
| Unload cargo from container | `POST` | `/api/cargoes/:id/unload` | `modules/cargoes/cargoes.controller.ts:74` |
| Update a cargo | `PATCH` | `/api/cargoes/:id` | `modules/cargoes/cargoes.controller.ts:53` |
| Delete a cargo | `DELETE` | `/api/cargoes/:id` | `modules/cargoes/cargoes.controller.ts:60` |
### Cargo Type
| Title | Method | Endpoint | Source |
| --- | --- | --- | --- |
| Create a cargo type | `POST` | `/api/cargo-types` | `modules/rule-engine/controllers/cargo-types.controller.ts:51` |
| Move a cargo type up or down in display order | `POST` | `/api/cargo-types/:id/move-order` | `modules/rule-engine/controllers/cargo-types.controller.ts:36` |
| Bulk reorder cargo types by ID list | `POST` | `/api/cargo-types/reorder` | `modules/rule-engine/controllers/cargo-types.controller.ts:28` |
| Update a cargo type | `PATCH` | `/api/cargo-types/:id` | `modules/rule-engine/controllers/cargo-types.controller.ts:58` |
| Soft-delete a cargo type | `DELETE` | `/api/cargo-types/:id` | `modules/rule-engine/controllers/cargo-types.controller.ts:65` |
### Company
| Title | Method | Endpoint | Source |
| --- | --- | --- | --- |
| Create a new company (customer, freight_forwarder, dj_freight_forwarder, transporter) | `POST` | `/api/companies` | `modules/companies/companies.controller.ts:565` |
| Upload documents for a company (onboarding) | `POST` | `/api/companies/:companyId/documents` | `modules/companies/companies.controller.ts:728` |
| Add a profile (employee) to a company | `POST` | `/api/companies/:companyId/profiles` | `modules/companies/companies.controller.ts:843` |
| Approve a pending profile change request (applies the changes) | `POST` | `/api/companies/change-requests/:id/approve` | `modules/companies/companies.controller.ts:790` |
| Reject a pending profile change request with a note | `POST` | `/api/companies/change-requests/:id/reject` | `modules/companies/companies.controller.ts:806` |
| Ask for specific changes on a pending request without rejecting it (row stays open, next edit appends to it) | `POST` | `/api/companies/change-requests/:id/request-changes` | `modules/companies/companies.controller.ts:824` |
| Create a single operational profile for the current user's company. The role starts pending and does not become the active mode | `POST` | `/api/companies/company-profile` | `modules/companies/companies.controller.ts:272` |
| Add operational profile(s) (importer/exporter/forwarder) to the current user's company | `POST` | `/api/companies/company-profiles` | `modules/companies/companies.controller.ts:229` |
| Add business-license document(s) to a profile. For an approved company | `POST` | `/api/companies/company-profiles/:profileId/license` | `modules/companies/companies.controller.ts:290` |
| Replace a business-license file with a newly uploaded one (staged for | `POST` | `/api/companies/company-profiles/:profileId/license/:fileId/replace` | `modules/companies/companies.controller.ts:311` |
| Resubmit a rejected operational role for approval (→ pending) | `POST` | `/api/companies/company-profiles/:profileId/reapply` | `modules/companies/companies.controller.ts:165` |
| Create a company with its associated external profile (onboarding) | `POST` | `/api/companies/create` | `modules/companies/companies.controller.ts:539` |
| Ask the customer to correct one uploaded document | `POST` | `/api/companies/documents/:fileId/request-change` | `modules/companies/companies.controller.ts:699` |
| Fetch company info from eTrade by TIN | `POST` | `/api/companies/fetch-etrade-info` | `modules/companies/companies.controller.ts:197` |
| Bind a completed Fayda verification to the company's owner or Power of Attorney | `POST` | `/api/companies/identity/fayda/complete` | `modules/companies/companies.controller.ts:414` |
| Declare the General Manager is the company's owner, copying the owner's verified identity across | `POST` | `/api/companies/identity/gm/same-as-owner` | `modules/companies/companies.controller.ts:432` |
| Declare the Power of Attorney is the company's owner, copying the owner's identity across | `POST` | `/api/companies/identity/poa/same-as-owner` | `modules/companies/companies.controller.ts:461` |
| Mark the current user's onboarding as complete | `POST` | `/api/companies/onboarding/complete` | `modules/companies/companies.controller.ts:527` |
| Begin onboarding: create a draft company + profile + role(s) so later steps can save incrementally | `POST` | `/api/companies/onboarding/start` | `modules/companies/companies.controller.ts:246` |
| Upload the Power of Attorney delegation letter, replacing any existing one | `POST` | `/api/companies/poa-delegation` | `modules/companies/companies.controller.ts:380` |
| Update a company | `PATCH` | `/api/companies/:id` | `modules/companies/companies.controller.ts:613` |
| Update a company profile's approval status | `PATCH` | `/api/companies/company-profiles/:profileId/status` | `modules/companies/companies.controller.ts:748` |
| Persist the user's current onboarding wizard step | `PATCH` | `/api/companies/onboarding-step` | `modules/companies/companies.controller.ts:504` |
| Update profile (flattened settings page) | `PATCH` | `/api/companies/profile` | `modules/companies/companies.controller.ts:219` |
| Soft-delete a company | `DELETE` | `/api/companies/:id` | `modules/companies/companies.controller.ts:634` |
| Remove a business-license file (staged for review on an approved company) | `DELETE` | `/api/companies/company-profiles/:profileId/license/:fileId` | `modules/companies/companies.controller.ts:338` |
| Remove the company's Power of Attorney — the verified identity, its details and the delegation paper together | `DELETE` | `/api/companies/identity/fayda/poa` | `modules/companies/companies.controller.ts:491` |
| Clear the General Manager's identity — the \"same as owner\" declaration or a verification, and the details either wrote | `DELETE` | `/api/companies/identity/gm` | `modules/companies/companies.controller.ts:448` |
| Undo the Power of Attorney \"same as owner\" declaration and the identity it copied, leaving the representative open to be verified in their own right | `DELETE` | `/api/companies/identity/poa/same-as-owner` | `modules/companies/companies.controller.ts:478` |
| Remove the Power of Attorney delegation letter (staged for review on an approved company) | `DELETE` | `/api/companies/poa-delegation/:fileId` | `modules/companies/companies.controller.ts:401` |
### Compliance
| Title | Method | Endpoint | Source |
| --- | --- | --- | --- |
| Create a compliance record | `POST` | `/api/compliance` | `modules/compliance/compliance.controller.ts:23` |
| Update a compliance record | `PATCH` | `/api/compliance/:id` | `modules/compliance/compliance.controller.ts:51` |
| Soft-delete a compliance record | `DELETE` | `/api/compliance/:id` | `modules/compliance/compliance.controller.ts:58` |
### Consignment
| Title | Method | Endpoint | Source |
| --- | --- | --- | --- |
| Create a new consignment | `POST` | `/api/consignments` | `modules/consignments/consignments.controller.ts:29` |
### Container
| Title | Method | Endpoint | Source |
| --- | --- | --- | --- |
| Create a new container | `POST` | `/api/containers` | `modules/container-management/containers.controller.ts:33` |
| Assign container to a wagon | `POST` | `/api/containers/:id/assign-wagon` | `modules/container-management/containers.controller.ts:66` |
| Unassign container from wagon | `POST` | `/api/containers/:id/unassign-wagon` | `modules/container-management/containers.controller.ts:73` |
| Update a container | `PATCH` | `/api/containers/:id` | `modules/container-management/containers.controller.ts:52` |
| Delete a container | `DELETE` | `/api/containers/:id` | `modules/container-management/containers.controller.ts:59` |
### Container Type
| Title | Method | Endpoint | Source |
| --- | --- | --- | --- |
| Create a container type | `POST` | `/api/container-types` | `modules/rule-engine/controllers/container-types.controller.ts:51` |
| Move a container type up or down in display order | `POST` | `/api/container-types/:id/move-order` | `modules/rule-engine/controllers/container-types.controller.ts:36` |
| Bulk reorder container types by ID list | `POST` | `/api/container-types/reorder` | `modules/rule-engine/controllers/container-types.controller.ts:28` |
| Update a container type | `PATCH` | `/api/container-types/:id` | `modules/rule-engine/controllers/container-types.controller.ts:58` |
| Soft-delete a container type | `DELETE` | `/api/container-types/:id` | `modules/rule-engine/controllers/container-types.controller.ts:65` |
### Contract
| Title | Method | Endpoint | Source |
| --- | --- | --- | --- |
| Create a new contract (DRAFT) with routes + cargo scope | `POST` | `/api/contracts` | `modules/contracts/contracts.controller.ts:188` |
| Approve one approval step in sequence | `POST` | `/api/contracts/:id/approval-steps/:stepId/approve` | `modules/contracts/contracts.controller.ts:552` |
| Reject one approval step — to the customer (terminal → REJECTED) or, via returnToStepId, back to an earlier approver (chain re-runs from there) | `POST` | `/api/contracts/:id/approval-steps/:stepId/reject` | `modules/contracts/contracts.controller.ts:571` |
| Customer submits a shipment request on a GENERAL customs contract | `POST` | `/api/contracts/:id/booking-requests` | `modules/contracts/contracts.controller.ts:170` |
| Create a shipment booking under a contract — Path A (customer) or Path B (GL Ethiopia) | `POST` | `/api/contracts/:id/bookings` | `modules/contracts/contracts.controller.ts:1076` |
| Complete an initiated booking after Operations finalized its clearance — cargo + binding day, window and departure checks, pricing and invoicing | `POST` | `/api/contracts/:id/bookings/:bookingId/complete` | `modules/contracts/contracts.controller.ts:1133` |
| Initiate a bare booking instance under an import/export contract (ONE_TIME or GENERAL) — no cargo, no date; enters per-booking clearance (AWAITING_DOCUMENTS). ONE_TIME customs instances are opened by the customer (or GL); GENERAL customs comes from a shipment request | `POST` | `/api/contracts/:id/bookings/initiate` | `modules/contracts/contracts.controller.ts:1105` |
| Customer cancels their own contract (blocked while a booking is live) | `POST` | `/api/contracts/:id/cancel` | `modules/contracts/contracts.controller.ts:526` |
| GL ET uploads customs declaration documents (multi-file) | `POST` | `/api/contracts/:id/clearance/declaration` | `modules/contracts/contracts.controller.ts:795` |
| GL DJ uploads Delivery Order (import) with vessel arrival + DO collected dates | `POST` | `/api/contracts/:id/clearance/delivery-order` | `modules/contracts/contracts.controller.ts:957` |
| Customer uploads clearance documents (fieldname = document key) | `POST` | `/api/contracts/:id/clearance/documents` | `modules/contracts/contracts.controller.ts:734` |
| GL replaces a clearance document in place (reason required) — the previous version is kept in the file history and the new one needs approving | `POST` | `/api/contracts/:id/clearance/documents/:fileKey/replace` | `modules/contracts/contracts.controller.ts:894` |
| GL ET sets duty/tax requirement and advises amount with notice attachment | `POST` | `/api/contracts/:id/clearance/duty` | `modules/contracts/contracts.controller.ts:808` |
| Customer uploads duty/tax payment slip on contract | `POST` | `/api/contracts/:id/clearance/duty-slip` | `modules/contracts/contracts.controller.ts:932` |
| Customer disputes the advised duty/tax with a reason — reopens the step so GL Ethiopia can re-advise (repeatable) | `POST` | `/api/contracts/:id/clearance/duty/dispute` | `modules/contracts/contracts.controller.ts:918` |
| GL ET confirms export release after declaration | `POST` | `/api/contracts/:id/clearance/export-release` | `modules/contracts/contracts.controller.ts:1008` |
| GL ET finalizes clearance → CLEARANCE_READY_FOR_BOOKING (legacy) | `POST` | `/api/contracts/:id/clearance/finalize` | `modules/contracts/contracts.controller.ts:788` |
| GL ET finalizes export clearance after post-booking transit permit upload | `POST` | `/api/contracts/:id/clearance/finalize-export-clearance` | `modules/contracts/contracts.controller.ts:1018` |
| GL ET finalizes import pre-clearance — unlocks Djibouti DO upload | `POST` | `/api/contracts/:id/clearance/finalize-pre-clearance` | `modules/contracts/contracts.controller.ts:838` |
| Operations finalizes self-clearance → customer may create the booking | `POST` | `/api/contracts/:id/clearance/ops-finalize` | `modules/contracts/contracts.controller.ts:1051` |
| Operations reviews a customer self-clearance document (Approve | Query) | `POST` | `/api/contracts/:id/clearance/ops-review` | `modules/contracts/contracts.controller.ts:1032` |
| GL uploads customs output documents (IM4/EX3/…) pre-booking | `POST` | `/api/contracts/:id/clearance/output-documents` | `modules/contracts/contracts.controller.ts:776` |
| GL DJ uploads Release Order + vessel departure date (export) | `POST` | `/api/contracts/:id/clearance/release-order` | `modules/contracts/contracts.controller.ts:978` |
| GL ET reviews a clearance document (Approve | Query) | `POST` | `/api/contracts/:id/clearance/review` | `modules/contracts/contracts.controller.ts:756` |
| GL DJ requests port amendment when RO vessel window is too short | `POST` | `/api/contracts/:id/clearance/ro-amendment` | `modules/contracts/contracts.controller.ts:997` |
| GL Djibouti picks the transit officer from the roster — unblocks the customs declaration; calling again reassigns | `POST` | `/api/contracts/:id/clearance/transit-assignee/assign` | `modules/contracts/contracts.controller.ts:863` |
| GL ET asks GL Djibouti to name the transit officer — required before the customs declaration | `POST` | `/api/contracts/:id/clearance/transit-assignee/request` | `modules/contracts/contracts.controller.ts:845` |
| GL ET uploads import transit permit documents (multi-file) | `POST` | `/api/contracts/:id/clearance/transit-permit` | `modules/contracts/contracts.controller.ts:944` |
| Confirm submit after a price change | `POST` | `/api/contracts/:id/confirm-submit` | `modules/contracts/contracts.controller.ts:380` |
| Generate contract document → CONTRACT_READY | `POST` | `/api/contracts/:id/contract/generate` | `modules/contracts/contracts.controller.ts:596` |
| Send the sudo-mode signing OTP to the contract company's registered phone (server picks the number) | `POST` | `/api/contracts/:id/contract/send-signing-otp` | `modules/contracts/contracts.controller.ts:667` |
| Apply digital signature (customer or staff/director/ceo) | `POST` | `/api/contracts/:id/contract/sign` | `modules/contracts/contracts.controller.ts:680` |
| Upload intake documents for a contract (DRAFT only) | `POST` | `/api/contracts/:id/documents` | `modules/contracts/contracts.controller.ts:354` |
| Generate unit-rate breakdown (no totals at contract phase) | `POST` | `/api/contracts/:id/generate-price` | `modules/contracts/contracts.controller.ts:366` |
| GL marks a pre-booking (contract) milestone complete | `POST` | `/api/contracts/:id/milestones/:code/complete` | `modules/contracts/contracts.controller.ts:1222` |
| Create a renewal draft linked via renewalOfId | `POST` | `/api/contracts/:id/renew` | `modules/contracts/contracts.controller.ts:705` |
| Staff lift a suspension — contract returns to its prior status | `POST` | `/api/contracts/:id/resume` | `modules/contracts/contracts.controller.ts:510` |
| Staff accept → set validity window + start approval chain | `POST` | `/api/contracts/:id/staff/accept` | `modules/contracts/contracts.controller.ts:387` |
| Staff reject contract | `POST` | `/api/contracts/:id/staff/reject` | `modules/contracts/contracts.controller.ts:476` |
| Staff return contract for customer updates | `POST` | `/api/contracts/:id/staff/request-changes` | `modules/contracts/contracts.controller.ts:460` |
| Customer submit contract (freezes contract_rate_snapshots) | `POST` | `/api/contracts/:id/submit` | `modules/contracts/contracts.controller.ts:373` |
| Staff freeze a signed contract (reversible, any post-signature step) | `POST` | `/api/contracts/:id/suspend` | `modules/contracts/contracts.controller.ts:492` |
| Pre-create validation + authoritative price preview: full booking price breakdown (rail, first/last mile, surcharges), overweight lines and 20ft weight-pairing errors for a shipment payload (no booking created) | `POST` | `/api/contracts/:id/validate-shipment` | `modules/contracts/contracts.controller.ts:1162` |
| GL marks a shipment request accepted + links the created booking | `POST` | `/api/contracts/booking-requests/:reqId/accept` | `modules/contracts/contracts.controller.ts:134` |
| Customer cancels their own pending shipment request | `POST` | `/api/contracts/booking-requests/:reqId/cancel` | `modules/contracts/contracts.controller.ts:160` |
| GL rejects a shipment request | `POST` | `/api/contracts/booking-requests/:reqId/reject` | `modules/contracts/contracts.controller.ts:149` |
| GL uploads post-booking operational documents (DO/RO/T1/…) | `POST` | `/api/contracts/bookings/:bookingId/documents` | `modules/contracts/contracts.controller.ts:1441` |
| GL ET advises duty & tax amount + declaration serial | `POST` | `/api/contracts/bookings/:bookingId/duty` | `modules/contracts/contracts.controller.ts:1260` |
| Customer uploads the duty/tax payment slip | `POST` | `/api/contracts/bookings/:bookingId/duty-slip` | `modules/contracts/contracts.controller.ts:1455` |
| GL DJ raises the post-offload final invoice (amount + invoice document) | `POST` | `/api/contracts/bookings/:bookingId/final-invoice` | `modules/contracts/contracts.controller.ts:1332` |
| Customer attaches the payment slip for the final invoice | `POST` | `/api/contracts/bookings/:bookingId/final-invoice-slip` | `modules/contracts/contracts.controller.ts:1374` |
| Customer approves the drafted final invoice — unlocks the payment slip | `POST` | `/api/contracts/bookings/:bookingId/final-invoice/approve` | `modules/contracts/contracts.controller.ts:1359` |
| GL (ET or DJ) confirms the payment slip — settles the final invoice | `POST` | `/api/contracts/bookings/:bookingId/final-invoice/confirm` | `modules/contracts/contracts.controller.ts:1386` |
| GL DJ logs a cargo exception with photo evidence | `POST` | `/api/contracts/bookings/:bookingId/incidents` | `modules/contracts/contracts.controller.ts:1479` |
| GL / Ops / Terminal marks a post-booking milestone complete | `POST` | `/api/contracts/bookings/:bookingId/milestones/:code/complete` | `modules/contracts/contracts.controller.ts:1205` |
| GL ET assigns a customs risk level (GREEN/YELLOW/RED) | `POST` | `/api/contracts/bookings/:bookingId/risk` | `modules/contracts/contracts.controller.ts:1241` |
| GL ET advises (or skips) the post-arrival additional duty/tax round (import) | `POST` | `/api/contracts/bookings/:bookingId/second-duty` | `modules/contracts/contracts.controller.ts:1399` |
| Customer attaches the additional duty/tax payment slip | `POST` | `/api/contracts/bookings/:bookingId/second-duty-slip` | `modules/contracts/contracts.controller.ts:1429` |
| GL station manager routes the shipment + binds staff | `POST` | `/api/contracts/bookings/:bookingId/station-assign` | `modules/contracts/contracts.controller.ts:1276` |
| Close (accept) the T1 set — GL ET after arrival (import) / GL DJ after gate pass (export) | `POST` | `/api/contracts/bookings/:bookingId/t1-close` | `modules/contracts/contracts.controller.ts:1316` |
| GL Djibouti uploads T1 transit documents (multi-file) after wagon allocation; locked once the train departs | `POST` | `/api/contracts/bookings/:bookingId/t1-documents` | `modules/contracts/contracts.controller.ts:1301` |
| GL ET uploads export transit permit documents (multi-file) | `POST` | `/api/contracts/bookings/:bookingId/transport-document` | `modules/contracts/contracts.controller.ts:1289` |
| Share a document with the other GL desk | `POST` | `/api/gl-exchange/:entityId` | `modules/contracts/gl-exchange.controller.ts:59` |
| Edit this contract\'s document articles only (per-contract; never touches the six shared templates) | `PUT` | `/api/contracts/:id/document/articles` | `modules/contracts/contracts.controller.ts:441` |
| Update contract | `PATCH` | `/api/contracts/:id` | `modules/contracts/contracts.controller.ts:327` |
| Uploader edits a shared document (title, visibility, file) | `PATCH` | `/api/gl-exchange/documents/:documentId` | `modules/contracts/gl-exchange.controller.ts:79` |
| Soft-delete DRAFT contract | `DELETE` | `/api/contracts/:id` | `modules/contracts/contracts.controller.ts:346` |
| Uploader removes a shared document | `DELETE` | `/api/gl-exchange/documents/:documentId` | `modules/contracts/gl-exchange.controller.ts:105` |
### Contract Template
| Title | Method | Endpoint | Source |
| --- | --- | --- | --- |
| Create a bulk contract template for a (cargo type, customs option) pair | `POST` | `/api/contract-templates` | `modules/contract-templates/contract-templates.controller.ts:56` |
| Add an article to the template | `POST` | `/api/contract-templates/:code/articles` | `modules/contract-templates/contract-templates.controller.ts:118` |
| Render an HTML preview of the template against mock contract data | `POST` | `/api/contract-templates/:code/preview` | `modules/contract-templates/contract-templates.controller.ts:97` |
| Replace the full ordered article list (used for reorder) | `PUT` | `/api/contract-templates/:code/articles` | `modules/contract-templates/contract-templates.controller.ts:111` |
| Update template metadata (name, title, recitals, active flag) | `PATCH` | `/api/contract-templates/:code` | `modules/contract-templates/contract-templates.controller.ts:77` |
| Update an article's title or body | `PATCH` | `/api/contract-templates/:code/articles/:articleId` | `modules/contract-templates/contract-templates.controller.ts:125` |
| Delete a staff-created bulk template (system templates refuse) | `DELETE` | `/api/contract-templates/:code` | `modules/contract-templates/contract-templates.controller.ts:84` |
| Remove an article from the template | `DELETE` | `/api/contract-templates/:code/articles/:articleId` | `modules/contract-templates/contract-templates.controller.ts:136` |
### Driver
| Title | Method | Endpoint | Source |
| --- | --- | --- | --- |
| Create a new driver | `POST` | `/api/drivers` | `modules/drivers/drivers.controller.ts:41` |
| Upload driver documents (code driver_docs) | `POST` | `/api/drivers/:id/documents` | `modules/drivers/drivers.controller.ts:80` |
| Update a driver | `PATCH` | `/api/drivers/:id` | `modules/drivers/drivers.controller.ts:128` |
| Delete a driver | `DELETE` | `/api/drivers/:id` | `modules/drivers/drivers.controller.ts:138` |
| Delete a driver document | `DELETE` | `/api/drivers/:id/documents/:fileId` | `modules/drivers/drivers.controller.ts:121` |
### Dropdown Setting
| Title | Method | Endpoint | Source |
| --- | --- | --- | --- |
| Create a new dropdown setting | `POST` | `/api/dropdown-settings` | `modules/dropdown-settings/dropdown-settings.controller.ts:61` |
| Append a single option to a setting | `POST` | `/api/dropdown-settings/:id/options` | `modules/dropdown-settings/dropdown-settings.controller.ts:98` |
| Replace the full option list for a setting | `PUT` | `/api/dropdown-settings/:id/options` | `modules/dropdown-settings/dropdown-settings.controller.ts:88` |
| Update a dropdown setting's metadata | `PATCH` | `/api/dropdown-settings/:id` | `modules/dropdown-settings/dropdown-settings.controller.ts:68` |
| Update a single option | `PATCH` | `/api/dropdown-settings/options/:optionId` | `modules/dropdown-settings/dropdown-settings.controller.ts:108` |
| Soft-delete a dropdown setting | `DELETE` | `/api/dropdown-settings/:id` | `modules/dropdown-settings/dropdown-settings.controller.ts:78` |
| Soft-delete a single option | `DELETE` | `/api/dropdown-settings/options/:optionId` | `modules/dropdown-settings/dropdown-settings.controller.ts:118` |
### EIMS Invoice
| Title | Method | Endpoint | Source |
| --- | --- | --- | --- |
| Register the invoice with MoR EIMS. Idempotent — an invoice that already has an IRN is returned unchanged | `POST` | `/api/invoices/:id/eims/register` | `modules/eims/eims-invoice.controller.ts:32` |
| Resolve an unacknowledged submission: record the IRN confirmed with MoR, or discard it. Clears the system-wide block | `POST` | `/api/invoices/:id/eims/resolve` | `modules/eims/eims-invoice.controller.ts:49` |
| Verify the invoice's stored IRN against EIMS | `POST` | `/api/invoices/:id/eims/verify` | `modules/eims/eims-invoice.controller.ts:42` |
### Exchange Setting
| Title | Method | Endpoint | Source |
| --- | --- | --- | --- |
| Set the USD→ETB fallback by hand (used only while CBE is unreachable) | `PATCH` | `/api/exchange-settings` | `modules/exchange-settings/exchange-settings.controller.ts:35` |
### Facility
| Title | Method | Endpoint | Source |
| --- | --- | --- | --- |
| Create a new facility | `POST` | `/api/facilities` | `modules/facilities/facilities.controller.ts:22` |
| Update a facility | `PATCH` | `/api/facilities/:id` | `modules/facilities/facilities.controller.ts:41` |
| Delete a facility (soft delete) | `DELETE` | `/api/facilities/:id` | `modules/facilities/facilities.controller.ts:48` |
### Fayda Verification
| Title | Method | Endpoint | Source |
| --- | --- | --- | --- |
| Start a VeriFayda 2.0 verification session | `POST` | `/api/fayda/verification/start` | `modules/verifayda/verifayda.controller.ts:44` |
### File Upload Setting
| Title | Method | Endpoint | Source |
| --- | --- | --- | --- |
| Create a new file upload setting | `POST` | `/api/file-upload-settings` | `modules/file-upload-settings/file-upload-settings.controller.ts:56` |
| Append a single field to a setting | `POST` | `/api/file-upload-settings/:id/fields` | `modules/file-upload-settings/file-upload-settings.controller.ts:93` |
| Replace the full field list for a setting | `PUT` | `/api/file-upload-settings/:id/fields` | `modules/file-upload-settings/file-upload-settings.controller.ts:83` |
| Update a file upload setting's metadata | `PATCH` | `/api/file-upload-settings/:id` | `modules/file-upload-settings/file-upload-settings.controller.ts:63` |
| Update a single field | `PATCH` | `/api/file-upload-settings/fields/:fieldId` | `modules/file-upload-settings/file-upload-settings.controller.ts:103` |
| Soft-delete a file upload setting | `DELETE` | `/api/file-upload-settings/:id` | `modules/file-upload-settings/file-upload-settings.controller.ts:73` |
| Soft-delete a single field | `DELETE` | `/api/file-upload-settings/fields/:fieldId` | `modules/file-upload-settings/file-upload-settings.controller.ts:113` |
### First Mile
| Title | Method | Endpoint | Source |
| --- | --- | --- | --- |
| Create a first-mile leg | `POST` | `/api/first-mile` | `modules/first-mile/first-mile.controller.ts:84` |
| Set per-vehicle actual distances (does not generate an invoice) | `POST` | `/api/first-mile/:id/distances` | `modules/first-mile/first-mile.controller.ts:124` |
| Generate the first-mile delivery-fee invoice | `POST` | `/api/first-mile/:id/invoice` | `modules/first-mile/first-mile.controller.ts:100` |
| Set the vehicles assigned to a first-mile pickup (multi-truck) | `POST` | `/api/first-mile/:id/vehicles` | `modules/first-mile/first-mile.controller.ts:114` |
| Accept a paid booking and create a first-mile leg | `POST` | `/api/first-mile/accept/:reference` | `modules/first-mile/first-mile.controller.ts:77` |
| Update a first-mile leg | `PATCH` | `/api/first-mile/:id` | `modules/first-mile/first-mile.controller.ts:91` |
| Soft-delete a first-mile leg | `DELETE` | `/api/first-mile/:id` | `modules/first-mile/first-mile.controller.ts:134` |
### Fuel
| Title | Method | Endpoint | Source |
| --- | --- | --- | --- |
| Record fuel purchase | `POST` | `/api/fuel/purchases` | `modules/fuel/fuel.controller.ts:22` |
### GPS Tracking
| Title | Method | Endpoint | Source |
| --- | --- | --- | --- |
| Register a GPS tracker | `POST` | `/api/gps/devices` | `modules/gps-tracking/gps-tracking.controller.ts:52` |
| Update a GPS tracker (name / assigned vehicle) | `PATCH` | `/api/gps/devices/:id` | `modules/gps-tracking/gps-tracking.controller.ts:59` |
| Delete a GPS tracker | `DELETE` | `/api/gps/devices/:id` | `modules/gps-tracking/gps-tracking.controller.ts:66` |
### Import Operation
| Title | Method | Endpoint | Source |
| --- | --- | --- | --- |
| Batch 12: record declaration serial number | `POST` | `/api/import-operations/customs/:bookingId/declaration` | `modules/import-operations/import-operations.controller.ts:53` |
| Batch 12: upload IM4/IM5/T1/permit/payment-slip documents | `POST` | `/api/import-operations/customs/:bookingId/documents` | `modules/import-operations/import-operations.controller.ts:44` |
| Batch 12: mark duties and taxes paid | `POST` | `/api/import-operations/customs/:bookingId/duties-taxes-paid` | `modules/import-operations/import-operations.controller.ts:71` |
| Batch 12: notify duties and taxes | `POST` | `/api/import-operations/customs/:bookingId/notify-duties-taxes` | `modules/import-operations/import-operations.controller.ts:62` |
| Batch 12: mark import release permitted | `POST` | `/api/import-operations/customs/:bookingId/release-permitted` | `modules/import-operations/import-operations.controller.ts:86` |
| Batch 12: assign customs risk | `POST` | `/api/import-operations/customs/:bookingId/risk` | `modules/import-operations/import-operations.controller.ts:80` |
| Batch 8: report a Djibouti import incident / exception | `POST` | `/api/import-operations/djibouti-incidents` | `modules/import-operations/import-operations.controller.ts:32` |
| Batch 16: create an empty container return record | `POST` | `/api/import-operations/empty-container-returns` | `modules/import-operations/import-operations.controller.ts:101` |
| Batch 16: advance empty container return workflow | `POST` | `/api/import-operations/empty-container-returns/:id/status` | `modules/import-operations/import-operations.controller.ts:107` |
### Incident
| Title | Method | Endpoint | Source |
| --- | --- | --- | --- |
| Report an incident | `POST` | `/api/incidents` | `modules/incidents/incidents.controller.ts:36` |
| Update an incident | `PATCH` | `/api/incidents/:id` | `modules/incidents/incidents.controller.ts:72` |
| Delete an incident | `DELETE` | `/api/incidents/:id` | `modules/incidents/incidents.controller.ts:79` |
### Interchange Document
| Title | Method | Endpoint | Source |
| --- | --- | --- | --- |
| Generate interchange document from a train schedule handover | `POST` | `/api/interchange-documents/generate-from-schedule` | `modules/interchange-documents/interchange-documents.controller.ts:41` |
| Acknowledge an interchange document | `PATCH` | `/api/interchange-documents/:id/acknowledge` | `modules/interchange-documents/interchange-documents.controller.ts:48` |
| Dispute an interchange document | `PATCH` | `/api/interchange-documents/:id/dispute` | `modules/interchange-documents/interchange-documents.controller.ts:58` |
### Last Mile
| Title | Method | Endpoint | Source |
| --- | --- | --- | --- |
| Create a last-mile leg | `POST` | `/api/last-mile` | `modules/last-mile/last-mile.controller.ts:97` |
| Set each truck\'s own detention window (arrived at destination / returned) | `POST` | `/api/last-mile/:id/detention-times` | `modules/last-mile/last-mile.controller.ts:142` |
| Set per-vehicle actual distances (does not generate an invoice) | `POST` | `/api/last-mile/:id/distances` | `modules/last-mile/last-mile.controller.ts:132` |
| Generate the delivery-fee invoice for a last-mile leg | `POST` | `/api/last-mile/:id/invoice` | `modules/last-mile/last-mile.controller.ts:179` |
| Record proof of delivery (signature + photos) and complete the leg | `POST` | `/api/last-mile/:id/proof-of-delivery` | `modules/last-mile/last-mile.controller.ts:166` |
| Set the vehicles assigned to a last-mile delivery (multi-truck) | `POST` | `/api/last-mile/:id/vehicles` | `modules/last-mile/last-mile.controller.ts:122` |
| Set each truck\'s warehouse gate arrival/departure times | `POST` | `/api/last-mile/:id/warehouse-gate-times` | `modules/last-mile/last-mile.controller.ts:154` |
| Accept a paid booking and create a last-mile leg | `POST` | `/api/last-mile/accept/:reference` | `modules/last-mile/last-mile.controller.ts:90` |
| Update a last-mile leg | `PATCH` | `/api/last-mile/:id` | `modules/last-mile/last-mile.controller.ts:104` |
| Soft-delete a last-mile leg | `DELETE` | `/api/last-mile/:id` | `modules/last-mile/last-mile.controller.ts:113` |
### Last Mile Request
| Title | Method | Endpoint | Source |
| --- | --- | --- | --- |
| Truck & Machinery chief approves the request — the advance defaults to the live last-mile rate; LM contract becomes signable and the advance invoice follows the customer signature | `POST` | `/api/last-mile-requests/:id/approve` | `modules/last-mile-requests/last-mile-requests.controller.ts:119` |
| Customer agrees and signs the LM contract — then the advance invoice is issued | `POST` | `/api/last-mile-requests/:id/contract/sign` | `modules/last-mile-requests/last-mile-requests.controller.ts:83` |
| Truck & Machinery chief rejects the request with a reason | `POST` | `/api/last-mile-requests/:id/reject` | `modules/last-mile-requests/last-mile-requests.controller.ts:130` |
| Customer confirms which containers go via EDR last-mile | `POST` | `/api/last-mile-requests/:id/submit` | `modules/last-mile-requests/last-mile-requests.controller.ts:108` |
### Locomotive
| Title | Method | Endpoint | Source |
| --- | --- | --- | --- |
| Create a locomotive | `POST` | `/api/locomotives` | `modules/locomotives/locomotives.controller.ts:58` |
| Decommission a locomotive | `POST` | `/api/locomotives/:id/decommission` | `modules/locomotives/locomotives.controller.ts:72` |
| Update a locomotive | `PATCH` | `/api/locomotives/:id` | `modules/locomotives/locomotives.controller.ts:65` |
| Permanently delete a locomotive (irreversible; refused if any train references it) | `DELETE` | `/api/locomotives/:id/permanent` | `modules/locomotives/locomotives.controller.ts:82` |
### Maintenance
| Title | Method | Endpoint | Source |
| --- | --- | --- | --- |
| Record maintenance cost | `POST` | `/api/maintenance/costs` | `modules/maintenance/maintenance.controller.ts:38` |
| Define/adjust a service interval (e.g. oil change every 10,000 km) | `POST` | `/api/maintenance/intervals` | `modules/maintenance/maintenance.controller.ts:59` |
| Create part | `POST` | `/api/maintenance/parts` | `modules/maintenance/maintenance.controller.ts:150` |
| Schedule maintenance | `POST` | `/api/maintenance/schedules` | `modules/maintenance/maintenance.controller.ts:31` |
| Create warranty | `POST` | `/api/maintenance/warranties` | `modules/maintenance/maintenance.controller.ts:186` |
| Create work order | `POST` | `/api/maintenance/work-orders` | `modules/maintenance/maintenance.controller.ts:110` |
| Update part | `PATCH` | `/api/maintenance/parts/:id` | `modules/maintenance/maintenance.controller.ts:170` |
| Update maintenance schedule | `PATCH` | `/api/maintenance/schedules/:id` | `modules/maintenance/maintenance.controller.ts:45` |
| Update work order | `PATCH` | `/api/maintenance/work-orders/:id` | `modules/maintenance/maintenance.controller.ts:134` |
| Deactivate a service interval (stops auto-scheduling) | `DELETE` | `/api/maintenance/intervals/:id` | `modules/maintenance/maintenance.controller.ts:73` |
| Delete part | `DELETE` | `/api/maintenance/parts/:id` | `modules/maintenance/maintenance.controller.ts:177` |
| Delete warranty | `DELETE` | `/api/maintenance/warranties/:id` | `modules/maintenance/maintenance.controller.ts:200` |
| Delete work order | `DELETE` | `/api/maintenance/work-orders/:id` | `modules/maintenance/maintenance.controller.ts:141` |
### Notification Inbox
| Title | Method | Endpoint | Source |
| --- | --- | --- | --- |
| Mark all my notifications as read | `POST` | `/api/notifications/read-all` | `modules/notification-inbox/notification-inbox.controller.ts:53` |
| Mark one of my notifications as read | `PATCH` | `/api/notifications/:id/read` | `modules/notification-inbox/notification-inbox.controller.ts:44` |
### Organization User
| Title | Method | Endpoint | Source |
| --- | --- | --- | --- |
| Create an organization user without assigning positions | `POST` | `/api/backoffice/organizations/:orgId/users` | `modules/backoffice/backoffice.controller.ts:24` |
| Replace org-scoped roles assigned to an employee user | `PUT` | `/api/backoffice/organizations/:orgId/employee-users/:userId/roles` | `modules/backoffice/backoffice.controller.ts:58` |
### OTP
| Title | Method | Endpoint | Source |
| --- | --- | --- | --- |
| Send OTP | `POST` | `/api/otp/send` | `modules/otp/otp.controller.ts:41` |
| Verify OTP | `POST` | `/api/otp/verify` | `modules/otp/otp.controller.ts:62` |
### Password Reset
| Title | Method | Endpoint | Source |
| --- | --- | --- | --- |
| Send a password-reset code to the account's email AND phone | `POST` | `/api/auth/forgot-password/request` | `modules/auth/forgot-password.controller.ts:30` |
| Validate a staff-issued reset link and return its set-password ticket | `POST` | `/api/auth/forgot-password/resolve-link` | `modules/auth/forgot-password.controller.ts:73` |
| Exchange a valid reset code for a single-use set-password ticket | `POST` | `/api/auth/forgot-password/verify` | `modules/auth/forgot-password.controller.ts:62` |
| Send a password-reset link to a customer's primary contact | `POST` | `/api/backoffice/customers/:companyId/reset-password` | `modules/auth/customer-reset.controller.ts:49` |
### Payment
| Title | Method | Endpoint | Source |
| --- | --- | --- | --- |
| Finance confirms a USD invoice paid by bank transfer — slip file required, settles the full balance | `POST` | `/api/billing/invoices/:id/confirm-offline` | `modules/billing/billing.controller.ts:81` |
| Confirm an OTP-debit payment (CAC Bank) for one of the customer's invoices | `POST` | `/api/billing/my-invoices/:id/confirm` | `modules/billing/portal-billing.controller.ts:102` |
| Initiate payment for one of the customer's invoices | `POST` | `/api/billing/my-invoices/:id/pay` | `modules/billing/portal-billing.controller.ts:86` |
| Live still-payable check + payer name for a CBE bill (called while CBE is on the line) | `POST` | `/api/internal/payments/bill-query` | `modules/payment/internal-payment.controller.ts:56` |
| Apply a payment.succeeded / payment.failed event from the payment service (idempotent) | `POST` | `/api/internal/payments/mark-paid` | `modules/payment/internal-payment.controller.ts:45` |
| Initiate payment for an invoice | `POST` | `/api/payments/initiate` | `modules/billing/payment.controller.ts:39` |
| Success-redirect ack: mark payment processing + invoice PAYMENT_PROCESSING (webhook remains source of truth) | `POST` | `/api/payments/redirect-success/:bookingId` | `modules/payment/payment.controller.ts:89` |
### Priority Config
| Title | Method | Endpoint | Source |
| --- | --- | --- | --- |
| Create a priority config | `POST` | `/api/priority-configs` | `modules/rule-engine/controllers/priority-configs.controller.ts:51` |
| Move a priority config up or down in display order | `POST` | `/api/priority-configs/:id/move-order` | `modules/rule-engine/controllers/priority-configs.controller.ts:66` |
| Bulk reorder priority configs by ID list | `POST` | `/api/priority-configs/reorder` | `modules/rule-engine/controllers/priority-configs.controller.ts:58` |
| Update a priority config | `PATCH` | `/api/priority-configs/:id` | `modules/rule-engine/controllers/priority-configs.controller.ts:74` |
| Soft-delete a priority config | `DELETE` | `/api/priority-configs/:id` | `modules/rule-engine/controllers/priority-configs.controller.ts:81` |
### Priority Rule Change Request
| Title | Method | Endpoint | Source |
| --- | --- | --- | --- |
| Submit a priority-rule change for approval | `POST` | `/api/priority-rule-change-requests` | `modules/rule-engine/controllers/priority-rule-change-requests.controller.ts:34` |
| Approve and apply a pending change | `POST` | `/api/priority-rule-change-requests/:id/approve` | `modules/rule-engine/controllers/priority-rule-change-requests.controller.ts:52` |
| Reject a pending change | `POST` | `/api/priority-rule-change-requests/:id/reject` | `modules/rule-engine/controllers/priority-rule-change-requests.controller.ts:65` |
### Procurement
| Title | Method | Endpoint | Source |
| --- | --- | --- | --- |
| Create an asset acquisition | `POST` | `/api/procurement/acquisitions` | `modules/procurement/procurement.controller.ts:56` |
| Create an asset disposal | `POST` | `/api/procurement/disposals` | `modules/procurement/procurement.controller.ts:90` |
| Create a vendor | `POST` | `/api/procurement/vendors` | `modules/procurement/procurement.controller.ts:28` |
| Update an asset acquisition | `PATCH` | `/api/procurement/acquisitions/:id` | `modules/procurement/procurement.controller.ts:75` |
| Update a vendor | `PATCH` | `/api/procurement/vendors/:id` | `modules/procurement/procurement.controller.ts:41` |
| Delete an asset acquisition | `DELETE` | `/api/procurement/acquisitions/:id` | `modules/procurement/procurement.controller.ts:82` |
| Delete an asset disposal | `DELETE` | `/api/procurement/disposals/:id` | `modules/procurement/procurement.controller.ts:103` |
| Delete a vendor | `DELETE` | `/api/procurement/vendors/:id` | `modules/procurement/procurement.controller.ts:48` |
### Rate
| Title | Method | Endpoint | Source |
| --- | --- | --- | --- |
| Create a rate (DRAFT) | `POST` | `/api/rates` | `modules/rule-engine/controllers/rates.controller.ts:46` |
| CEO approves a rate | `POST` | `/api/rates/:id/approve` | `modules/rule-engine/controllers/rates.controller.ts:70` |
| Submit rate for CEO approval | `POST` | `/api/rates/:id/submit` | `modules/rule-engine/controllers/rates.controller.ts:63` |
| Update a DRAFT rate | `PATCH` | `/api/rates/:id` | `modules/rule-engine/controllers/rates.controller.ts:56` |
| Soft-delete a rate | `DELETE` | `/api/rates/:id` | `modules/rule-engine/controllers/rates.controller.ts:82` |
### Rate Change Request
| Title | Method | Endpoint | Source |
| --- | --- | --- | --- |
| Propose a change to a LIVE rate | `POST` | `/api/rate-change-requests` | `modules/rule-engine/controllers/rate-change-requests.controller.ts:23` |
| Approve a rate change and put it into effect | `POST` | `/api/rate-change-requests/:id/approve` | `modules/rule-engine/controllers/rate-change-requests.controller.ts:37` |
| Reject a rate change — the rate keeps its current value | `POST` | `/api/rate-change-requests/:id/reject` | `modules/rule-engine/controllers/rate-change-requests.controller.ts:48` |
### Route
| Title | Method | Endpoint | Source |
| --- | --- | --- | --- |
| Create route | `POST` | `/api/routes` | `modules/routes/routes.controller.ts:61` |
| Update route | `PATCH` | `/api/routes/:id` | `modules/routes/routes.controller.ts:68` |
| Deactivate route | `DELETE` | `/api/routes/:id` | `modules/routes/routes.controller.ts:90` |
| Permanently delete a route (irreversible; refused while any train schedule references it) | `DELETE` | `/api/routes/:id/permanent` | `modules/routes/routes.controller.ts:79` |
### Schedule
| Title | Method | Endpoint | Source |
| --- | --- | --- | --- |
| Reschedule train for maintenance (new departure + rebalance) | `POST` | `/api/train-scheduling/schedules/:id/maintenance` | `modules/scheduling-reschedule/scheduling-reschedule.controller.ts:52` |
| Execute a confirmed reschedule plan | `POST` | `/api/train-scheduling/schedules/:id/reschedule/execute` | `modules/scheduling-reschedule/scheduling-reschedule.controller.ts:30` |
| Preview reschedule / government preempt plan | `POST` | `/api/train-scheduling/schedules/:id/reschedule/preview` | `modules/scheduling-reschedule/scheduling-reschedule.controller.ts:20` |
### Service Type
| Title | Method | Endpoint | Source |
| --- | --- | --- | --- |
| Create a service type | `POST` | `/api/service-types` | `modules/rule-engine/controllers/service-types.controller.ts:51` |
| Move a service type up or down in display order | `POST` | `/api/service-types/:id/move-order` | `modules/rule-engine/controllers/service-types.controller.ts:36` |
| Bulk reorder service types by ID list | `POST` | `/api/service-types/reorder` | `modules/rule-engine/controllers/service-types.controller.ts:28` |
| Update a service type | `PATCH` | `/api/service-types/:id` | `modules/rule-engine/controllers/service-types.controller.ts:58` |
| Soft-delete a service type | `DELETE` | `/api/service-types/:id` | `modules/rule-engine/controllers/service-types.controller.ts:65` |
### Shipping Line
| Title | Method | Endpoint | Source |
| --- | --- | --- | --- |
| Create a shipping line | `POST` | `/api/shipping-lines` | `modules/rule-engine/controllers/shipping-lines.controller.ts:33` |
| Update a shipping line | `PATCH` | `/api/shipping-lines/:id` | `modules/rule-engine/controllers/shipping-lines.controller.ts:40` |
| Soft-delete a shipping line | `DELETE` | `/api/shipping-lines/:id` | `modules/rule-engine/controllers/shipping-lines.controller.ts:47` |
### Signature
| Title | Method | Endpoint | Source |
| --- | --- | --- | --- |
| Create or update the reusable saved signature | `PUT` | `/api/me/signature` | `modules/signatures/signatures.controller.ts:23` |
### Support Chat
| Title | Method | Endpoint | Source |
| --- | --- | --- | --- |
| Start chatting with a company (returns the thread if one exists) | `POST` | `/api/support/agent/conversations` | `modules/support-chat/support-chat-agent.controller.ts:49` |
| Reply as an agent, optionally with attachments | `POST` | `/api/support/agent/conversations/:id/messages` | `modules/support-chat/support-chat-agent.controller.ts:74` |
| Mark a thread read (agent side) | `POST` | `/api/support/agent/conversations/:id/read` | `modules/support-chat/support-chat-agent.controller.ts:114` |
| Send a message as the customer (optionally with attachments), opening the thread if needed | `POST` | `/api/support/conversation/messages` | `modules/support-chat/support-chat.controller.ts:65` |
| Mark my company's thread read (customer side) | `POST` | `/api/support/conversation/read` | `modules/support-chat/support-chat.controller.ts:102` |
### Support Content
| Title | Method | Endpoint | Source |
| --- | --- | --- | --- |
| Restore a version — re-saves it as a new version, never destructive | `POST` | `/api/support-content/documents/:slug/versions/:version/restore` | `modules/support-content/support-content.controller.ts:123` |
| Upload an image or video for a help section | `POST` | `/api/support-content/media` | `modules/support-content/support-content.controller.ts:55` |
| Replace a document's payload, recording a new version | `PATCH` | `/api/support-content/documents/:slug` | `modules/support-content/support-content.controller.ts:93` |
### Train
| Title | Method | Endpoint | Source |
| --- | --- | --- | --- |
| Register a new train | `POST` | `/api/trains` | `modules/trains/trains.controller.ts:33` |
| Update a train | `PATCH` | `/api/trains/:id` | `modules/trains/trains.controller.ts:52` |
| Delete a train | `DELETE` | `/api/trains/:id` | `modules/trains/trains.controller.ts:59` |
### Train Build
| Title | Method | Endpoint | Source |
| --- | --- | --- | --- |
| Build a train: code + yard + 2+ locomotives (+ optional wagons) | `POST` | `/api/train-builder` | `modules/trains/train-builder.controller.ts:50` |
| Reactivate a deactivated train back to AVAILABLE | `POST` | `/api/train-builder/:id/activate` | `modules/trains/train-builder.controller.ts:158` |
| Deactivate the train (park it) — only allowed with no active schedule | `POST` | `/api/train-builder/:id/deactivate` | `modules/trains/train-builder.controller.ts:149` |
| Persist a drag-reorder of the full consist | `POST` | `/api/train-builder/:id/reorder-wagons` | `modules/trains/train-builder.controller.ts:142` |
| Append AVAILABLE wagons from the train's yard to the consist | `POST` | `/api/train-builder/:id/wagons` | `modules/trains/train-builder.controller.ts:109` |
| Detach one wagon and move it to MAINTENANCE status | `POST` | `/api/train-builder/:id/wagons/:wagonId/maintenance` | `modules/trains/train-builder.controller.ts:131` |
| Replace the locomotive set (minimum 1, same yard) | `PUT` | `/api/train-builder/:id/locomotives` | `modules/trains/train-builder.controller.ts:78` |
| Edit the train's name and fixed import/export run numbers | `PATCH` | `/api/train-builder/:id/details` | `modules/trains/train-builder.controller.ts:88` |
| Relocate the train — its locomotives and wagons move to the new yard with it | `PATCH` | `/api/train-builder/:id/yard` | `modules/trains/train-builder.controller.ts:100` |
| Disband the train (release wagons and locomotives) | `DELETE` | `/api/train-builder/:id` | `modules/trains/train-builder.controller.ts:165` |
| Detach one wagon from the consist | `DELETE` | `/api/train-builder/:id/wagons/:wagonId` | `modules/trains/train-builder.controller.ts:120` |
### Train Schedule
| Title | Method | Endpoint | Source |
| --- | --- | --- | --- |
| Staff: place a paid booking onto a fitting train (notifies customer on date change) | `POST` | `/api/train-scheduling/bookings/:bookingId/allocate` | `modules/train-scheduling/controllers/train-scheduling.controller.ts:880` |
| Staff: expire a reservation and free its capacity | `POST` | `/api/train-scheduling/bookings/:bookingId/expire` | `modules/train-scheduling/controllers/train-scheduling.controller.ts:845` |
| Staff: mark a reserved booking paid and allocate it now | `POST` | `/api/train-scheduling/bookings/:bookingId/mark-paid` | `modules/train-scheduling/controllers/train-scheduling.controller.ts:835` |
| Re-point a booking to another OPEN same-route schedule | `POST` | `/api/train-scheduling/bookings/:bookingId/move-schedule` | `modules/train-scheduling/controllers/train-scheduling.controller.ts:855` |
| Preview a bulk train schedule | `POST` | `/api/train-scheduling/bulk/preview` | `modules/train-scheduling/controllers/train-scheduling.controller.ts:302` |
| Create a bulk train schedule | `POST` | `/api/train-scheduling/bulk/schedules` | `modules/train-scheduling/controllers/train-scheduling.controller.ts:316` |
| Assign bulk bookings to a train schedule | `POST` | `/api/train-scheduling/bulk/schedules/:id/assign-bookings` | `modules/train-scheduling/controllers/train-scheduling.controller.ts:349` |
| Cancel bulk train schedule | `POST` | `/api/train-scheduling/bulk/schedules/:id/cancel` | `modules/train-scheduling/controllers/train-scheduling.controller.ts:976` |
| Preview a container train schedule | `POST` | `/api/train-scheduling/container/preview` | `modules/train-scheduling/controllers/train-scheduling.controller.ts:295` |
| Create a container train schedule | `POST` | `/api/train-scheduling/container/schedules` | `modules/train-scheduling/controllers/train-scheduling.controller.ts:309` |
| Assign container bookings to a train schedule | `POST` | `/api/train-scheduling/container/schedules/:id/assign-bookings` | `modules/train-scheduling/controllers/train-scheduling.controller.ts:335` |
| Cancel container train schedule | `POST` | `/api/train-scheduling/container/schedules/:id/cancel` | `modules/train-scheduling/controllers/train-scheduling.controller.ts:969` |
| Preview a mixed-capable train schedule | `POST` | `/api/train-scheduling/preview` | `modules/train-scheduling/controllers/train-scheduling.controller.ts:288` |
| Permanently trim free wagons off / couple yard wagons onto the schedule's built train (weight & length limits incl. tolerance enforced, every change logged) | `POST` | `/api/train-scheduling/schedules/:id/adjust-consist` | `modules/train-scheduling/controllers/train-scheduling.controller.ts:197` |
| Mark a dispatched train arrived (move assets to destination yard, free assets) | `POST` | `/api/train-scheduling/schedules/:id/arrive` | `modules/train-scheduling/controllers/train-scheduling.controller.ts:915` |
| Assign bookings to a train schedule (mixed-capable) | `POST` | `/api/train-scheduling/schedules/:id/assign-bookings` | `modules/train-scheduling/controllers/train-scheduling.controller.ts:323` |
| Assign one linked unallocated booking to wagons (preserves existing assignments) | `POST` | `/api/train-scheduling/schedules/:id/assign-unassigned-booking` | `modules/train-scheduling/controllers/train-scheduling.controller.ts:423` |
| Confirm a booking's cargo loaded at its origin yard (any direction; train must be at that yard) | `POST` | `/api/train-scheduling/schedules/:id/bookings/:bookingId/load` | `modules/train-scheduling/controllers/train-scheduling.controller.ts:564` |
| Confirm a booking's cargo unloaded at its destination yard — per-booking arrival, may precede the train's final arrival | `POST` | `/api/train-scheduling/schedules/:id/bookings/:bookingId/unload` | `modules/train-scheduling/controllers/train-scheduling.controller.ts:577` |
| Log the train passing a station (final station triggers arrival) | `POST` | `/api/train-scheduling/schedules/:id/checkpoints` | `modules/train-scheduling/controllers/train-scheduling.controller.ts:903` |
| Confirm cargo loaded on the train (any direction; unblocks import-Djibouti dispatch) | `POST` | `/api/train-scheduling/schedules/:id/confirm-loading` | `modules/train-scheduling/controllers/train-scheduling.controller.ts:662` |
| Dispatch a scheduled train | `POST` | `/api/train-scheduling/schedules/:id/dispatch` | `modules/train-scheduling/controllers/train-scheduling.controller.ts:514` |
| Staff finished document review early — run the batch/payment phase now (applies to the whole route-day group) | `POST` | `/api/train-scheduling/schedules/:id/doc-review-complete` | `modules/train-scheduling/controllers/train-scheduling.controller.ts:824` |
| Finalize a draft train schedule | `POST` | `/api/train-scheduling/schedules/:id/finalize` | `modules/train-scheduling/controllers/train-scheduling.controller.ts:507` |
| Depart loaded import train from Djibouti | `POST` | `/api/train-scheduling/schedules/:id/import-djibouti/depart` | `modules/train-scheduling/controllers/train-scheduling.controller.ts:675` |
| Upload/check an import Djibouti-side document | `POST` | `/api/train-scheduling/schedules/:id/import-djibouti/documents` | `modules/train-scheduling/controllers/train-scheduling.controller.ts:622` |
| Mark import Djibouti gatepass permission granted | `POST` | `/api/train-scheduling/schedules/:id/import-djibouti/gatepass-granted` | `modules/train-scheduling/controllers/train-scheduling.controller.ts:632` |
| Generate import load list / marshalling document summary | `POST` | `/api/train-scheduling/schedules/:id/import-djibouti/load-list` | `modules/train-scheduling/controllers/train-scheduling.controller.ts:685` |
| Confirm import cargo loaded on train at Djibouti | `POST` | `/api/train-scheduling/schedules/:id/import-djibouti/loaded-on-train` | `modules/train-scheduling/controllers/train-scheduling.controller.ts:652` |
| Mark import train ready for loading at Djibouti | `POST` | `/api/train-scheduling/schedules/:id/import-djibouti/ready-for-loading` | `modules/train-scheduling/controllers/train-scheduling.controller.ts:642` |
| Confirm intercity cargo loaded (train must be at the booking's origin yard) | `POST` | `/api/train-scheduling/schedules/:id/intercity/:bookingId/load` | `modules/train-scheduling/controllers/train-scheduling.controller.ts:590` |
| Confirm intercity cargo unloaded at the booking's destination yard (completes the booking) | `POST` | `/api/train-scheduling/schedules/:id/intercity/:bookingId/unload` | `modules/train-scheduling/controllers/train-scheduling.controller.ts:602` |
| Accept intercity bookings onto this train (opens their pay window; capacity re-checked per booking) | `POST` | `/api/train-scheduling/schedules/:id/intercity/accept` | `modules/train-scheduling/controllers/train-scheduling.controller.ts:541` |
| Maintenance reschedule: move the train to a new departure with every allocated booking aboard — links, wagons and window settings unchanged | `POST` | `/api/train-scheduling/schedules/:id/maintenance` | `modules/train-scheduling/controllers/train-scheduling.controller.ts:798` |
| Pin physical wagons to train set slots | `POST` | `/api/train-scheduling/schedules/:id/pin-wagons` | `modules/train-scheduling/controllers/train-scheduling.controller.ts:500` |
| Run wagon-level allocation for all eligible linked bookings | `POST` | `/api/train-scheduling/schedules/:id/run-allocation` | `modules/train-scheduling/controllers/train-scheduling.controller.ts:747` |
| Manually run the batch fill for a schedule | `POST` | `/api/train-scheduling/schedules/:id/run-batch` | `modules/train-scheduling/controllers/train-scheduling.controller.ts:739` |
| Switch out commercial bookings to allocate a government booking in their place | `POST` | `/api/train-scheduling/schedules/:id/switch-government-booking` | `modules/train-scheduling/controllers/train-scheduling.controller.ts:439` |
| Move a wagon's whole load to another wagon (empty → move/repin, loaded → swap loads) | `POST` | `/api/train-scheduling/schedules/:id/wagons/:wagonId/move-load` | `modules/train-scheduling/controllers/train-scheduling.controller.ts:402` |
| Update global train scheduling rules (singleton) | `PATCH` | `/api/train-scheduling/global-rules` | `modules/train-scheduling/controllers/train-scheduling.controller.ts:124` |
| Open or close a schedule booking window | `PATCH` | `/api/train-scheduling/schedules/:id/booking-window` | `modules/train-scheduling/controllers/train-scheduling.controller.ts:756` |
| Update a container number on a wagon slot | `PATCH` | `/api/train-scheduling/schedules/:id/container-items/:itemId` | `modules/train-scheduling/controllers/train-scheduling.controller.ts:391` |
| Mark import bookings loaded/unloaded on this schedule (tracking only, does not affect dispatch) | `PATCH` | `/api/train-scheduling/schedules/:id/import-loading-status` | `modules/train-scheduling/controllers/train-scheduling.controller.ts:474` |
| Mark bookings loaded/unloaded on this schedule (any direction, pre-dispatch only) | `PATCH` | `/api/train-scheduling/schedules/:id/loading-status` | `modules/train-scheduling/controllers/train-scheduling.controller.ts:487` |
| Reschedule a train's departure date — only before the booking window opens, and only if the new date still leaves room for the booking lead window | `PATCH` | `/api/train-scheduling/schedules/:id/schedule-date` | `modules/train-scheduling/controllers/train-scheduling.controller.ts:784` |
| Override the booking-window rule for one schedule (open/close hour, duration, doc-review, payment, lead days) — only before the window opens | `PATCH` | `/api/train-scheduling/schedules/:id/window-rule` | `modules/train-scheduling/controllers/train-scheduling.controller.ts:770` |
| Unassign a booking from a train schedule | `DELETE` | `/api/train-scheduling/schedules/:id/bookings/:bookingId` | `modules/train-scheduling/controllers/train-scheduling.controller.ts:363` |
| Remove an empty wagon slot from a train | `DELETE` | `/api/train-scheduling/schedules/:id/wagons/:trainSetWagonId` | `modules/train-scheduling/controllers/train-scheduling.controller.ts:378` |
### Transit Agent
| Title | Method | Endpoint | Source |
| --- | --- | --- | --- |
| Create a transit agent | `POST` | `/api/transit-agents` | `modules/transit-agents/transit-agents.controller.ts:66` |
| Update a transit agent | `PATCH` | `/api/transit-agents/:id` | `modules/transit-agents/transit-agents.controller.ts:73` |
| Soft-delete a transit agent | `DELETE` | `/api/transit-agents/:id` | `modules/transit-agents/transit-agents.controller.ts:80` |
### Truck Type
| Title | Method | Endpoint | Source |
| --- | --- | --- | --- |
| Create a truck type | `POST` | `/api/truck-types` | `modules/truck-types/truck-types.controller.ts:58` |
| Update a truck type | `PATCH` | `/api/truck-types/:id` | `modules/truck-types/truck-types.controller.ts:65` |
| Soft-delete a truck type | `DELETE` | `/api/truck-types/:id` | `modules/truck-types/truck-types.controller.ts:72` |
### User Trade Access
| Title | Method | Endpoint | Source |
| --- | --- | --- | --- |
| Set the trade directions a backoffice user may see | `PUT` | `/api/user-trade-access/:userId` | `modules/user-trade-access/user-trade-access.controller.ts:45` |
### Vehicle
| Title | Method | Endpoint | Source |
| --- | --- | --- | --- |
| Create a new vehicle | `POST` | `/api/vehicles` | `modules/vehicles/vehicles.controller.ts:37` |
| Update a vehicle | `PATCH` | `/api/vehicles/:id` | `modules/vehicles/vehicles.controller.ts:78` |
| Delete a vehicle | `DELETE` | `/api/vehicles/:id` | `modules/vehicles/vehicles.controller.ts:88` |
### Wagon
| Title | Method | Endpoint | Source |
| --- | --- | --- | --- |
| Create a new wagon | `POST` | `/api/wagons` | `modules/wagons/wagons.controller.ts:39` |
| Assign wagon to a train | `POST` | `/api/wagons/:id/assign-train` | `modules/wagons/wagons.controller.ts:100` |
| Unassign wagon from train | `POST` | `/api/wagons/:id/unassign-train` | `modules/wagons/wagons.controller.ts:107` |
| Set the status of multiple wagons (audited in wagon_status_logs) | `POST` | `/api/wagons/bulk-status` | `modules/wagons/wagons.controller.ts:121` |
| Transfer multiple wagons to a destination yard | `POST` | `/api/wagons/bulk-transfer` | `modules/wagons/wagons.controller.ts:114` |
| Update a wagon | `PATCH` | `/api/wagons/:id` | `modules/wagons/wagons.controller.ts:71` |
| Delete a wagon | `DELETE` | `/api/wagons/:id` | `modules/wagons/wagons.controller.ts:93` |
| Permanently delete a wagon (irreversible; refused if it has movements, containers or train-set slots) | `DELETE` | `/api/wagons/:id/permanent` | `modules/wagons/wagons.controller.ts:82` |
### Wagon Transfer Request
| Title | Method | Endpoint | Source |
| --- | --- | --- | --- |
| File a count-only wagon-transfer request | `POST` | `/api/wagon-transfer-requests` | `modules/wagons/wagon-transfer-requests.controller.ts:50` |
| Withdraw a request that has not moved any wagon yet (use close-short once wagons have moved) | `POST` | `/api/wagon-transfer-requests/:id/cancel` | `modules/wagons/wagon-transfer-requests.controller.ts:167` |
| OCC: end the request with fewer wagons than asked for — what moved stays, the requester is told the shortfall | `POST` | `/api/wagon-transfer-requests/:id/close-short` | `modules/wagons/wagon-transfer-requests.controller.ts:153` |
| OCC: pick wagons and execute the transfer | `POST` | `/api/wagon-transfer-requests/:id/fulfill` | `modules/wagons/wagon-transfer-requests.controller.ts:142` |
| OCC: accept-and-execute a subset of pending requests (auto-picks available wagons; the rest stay PENDING) | `POST` | `/api/wagon-transfer-requests/bulk-fulfill` | `modules/wagons/wagon-transfer-requests.controller.ts:73` |
### Wagon Type
| Title | Method | Endpoint | Source |
| --- | --- | --- | --- |
| Create a wagon type | `POST` | `/api/wagon-types` | `modules/wagon-types/wagon-types.controller.ts:53` |
| Update a wagon type | `PATCH` | `/api/wagon-types/:id` | `modules/wagon-types/wagon-types.controller.ts:60` |
| Soft-delete a wagon type | `DELETE` | `/api/wagon-types/:id` | `modules/wagon-types/wagon-types.controller.ts:67` |
### Warehouse
| Title | Method | Endpoint | Source |
| --- | --- | --- | --- |
| Create a warehouse allocation rule | `POST` | `/api/warehouse-allocation-rules` | `modules/warehouses/warehouse-rules.controller.ts:33` |
| Preview the yard/warehouse/zone a booking would be allocated to | `POST` | `/api/warehouse-allocation/preview` | `modules/warehouses/warehouse-rules.controller.ts:55` |
| Create a storage / demurrage fee rule | `POST` | `/api/warehouse-fee-rules` | `modules/warehouses/warehouse-rules.controller.ts:70` |
| Acknowledge / snooze an item fee-accrual alert | `POST` | `/api/warehouse-fees/accrual/:inventoryId/acknowledge` | `modules/warehouses/warehouse-rules.controller.ts:106` |
| Create warehouse | `POST` | `/api/warehouses` | `modules/warehouses/warehouses.controller.ts:51` |
| Create a yard within a warehouse | `POST` | `/api/warehouses/:warehouseId/yards` | `modules/warehouses/warehouses.controller.ts:78` |
| Update a warehouse allocation rule | `PATCH` | `/api/warehouse-allocation-rules/:id` | `modules/warehouses/warehouse-rules.controller.ts:40` |
| Update a fee rule | `PATCH` | `/api/warehouse-fee-rules/:id` | `modules/warehouses/warehouse-rules.controller.ts:77` |
| Update warehouse | `PATCH` | `/api/warehouses/:id` | `modules/warehouses/warehouses.controller.ts:64` |
| Delete a warehouse allocation rule | `DELETE` | `/api/warehouse-allocation-rules/:id` | `modules/warehouses/warehouse-rules.controller.ts:47` |
| Delete a fee rule | `DELETE` | `/api/warehouse-fee-rules/:id` | `modules/warehouses/warehouse-rules.controller.ts:84` |
| Remove an accrual acknowledgement (re-surface for alerts) | `DELETE` | `/api/warehouse-fees/accrual/:inventoryId/acknowledge` | `modules/warehouses/warehouse-rules.controller.ts:119` |
### Warehouse Fee Invoice
| Title | Method | Endpoint | Source |
| --- | --- | --- | --- |
| Generate a truck-detention invoice for a last-mile leg (per truck per day) | `POST` | `/api/last-mile/:id/generate-truck-detention-invoice` | `modules/warehouses/warehouse-invoice.controller.ts:28` |
| Record a payment against a warehouse fee invoice | `POST` | `/api/warehouse-fee-invoices/:id/pay` | `modules/warehouses/warehouse-invoice.controller.ts:109` |
| Initiate Telebirr/Waafi payment for a warehouse fee invoice | `POST` | `/api/warehouse-fee-invoices/:id/pay-online` | `modules/warehouses/warehouse-invoice.controller.ts:116` |
| Generate a warehouse fee invoice from Batch 5 fee calculation | `POST` | `/api/warehouse-inventory/:id/generate-fee-invoice` | `modules/warehouses/warehouse-invoice.controller.ts:20` |
| Cancel a warehouse fee invoice | `PATCH` | `/api/warehouse-fee-invoices/:id/cancel` | `modules/warehouses/warehouse-invoice.controller.ts:102` |
### Warehouse Inspection Report
| Title | Method | Endpoint | Source |
| --- | --- | --- | --- |
| Upload inspection images / documents | `POST` | `/api/warehouse-inspection-reports/:id/attachments` | `modules/warehouses/warehouse-inspection.controller.ts:68` |
| Create an inspection / damage report for an inventory item | `POST` | `/api/warehouse-inventory/:inventoryId/inspection-reports` | `modules/warehouses/warehouse-inspection.controller.ts:37` |
| Update an inspection report | `PATCH` | `/api/warehouse-inspection-reports/:id` | `modules/warehouses/warehouse-inspection.controller.ts:61` |
### Warehouse Inventory
| Title | Method | Endpoint | Source |
| --- | --- | --- | --- |
| Deliver import goods to the customer + capture proof of delivery | `POST` | `/api/warehouse-inventory/:id/deliver` | `modules/warehouses/warehouse-inventory.controller.ts:594` |
| Final terminal release / gate clearance (blocked while fees unpaid) | `POST` | `/api/warehouse-inventory/:id/gate-clearance` | `modules/warehouses/warehouse-inventory.controller.ts:219` |
| Load READY_FOR_LOADING inventory onto a wagon | `POST` | `/api/warehouse-inventory/:id/load` | `modules/warehouses/warehouse-inventory.controller.ts:384` |
| Move inventory to another warehouse/yard/zone | `POST` | `/api/warehouse-inventory/:id/move` | `modules/warehouses/warehouse-inventory.controller.ts:359` |
| Mark reserved inventory READY_FOR_LOADING | `POST` | `/api/warehouse-inventory/:id/ready-for-loading` | `modules/warehouses/warehouse-inventory.controller.ts:373` |
| Mark inspected IMPORT inventory READY_FOR_PICKUP | `POST` | `/api/warehouse-inventory/:id/ready-for-pickup` | `modules/warehouses/warehouse-inventory.controller.ts:391` |
| Issue a DO / release order for ready-for-pickup inventory | `POST` | `/api/warehouse-inventory/:id/release` | `modules/warehouses/warehouse-inventory.controller.ts:402` |
| Mark received inventory as STORED (optional explicit warehouse/yard/zone) | `POST` | `/api/warehouse-inventory/:id/store` | `modules/warehouses/warehouse-inventory.controller.ts:366` |
| Auto-load READY_FOR_LOADING inventory with PAID bookings | `POST` | `/api/warehouse-inventory/auto-load-ready` | `modules/warehouses/warehouse-inventory.controller.ts:125` |
| Bulk auto-unload all arrived bookings into the warehouse | `POST` | `/api/warehouse-inventory/auto-unload-arrived` | `modules/warehouses/warehouse-inventory.controller.ts:118` |
| Approve delivery — customer records their full name (signature optional) | `POST` | `/api/warehouse-inventory/bookings/:bookingId/approve-delivery` | `modules/warehouses/warehouse-inventory.controller.ts:470` |
| Ask the customer to sign the handover (creates one if none, then notifies) | `POST` | `/api/warehouse-inventory/bookings/:bookingId/request-handover-signature` | `modules/warehouses/warehouse-inventory.controller.ts:509` |
| Unload a single arrived booking into a location | `POST` | `/api/warehouse-inventory/bookings/:bookingId/unload` | `modules/warehouses/warehouse-inventory.controller.ts:209` |
| Bulk-dispatch loaded EXPORT inventory (LOADED → DISPATCHED) | `POST` | `/api/warehouse-inventory/bulk-dispatch-export` | `modules/warehouses/warehouse-inventory.controller.ts:195` |
| Bulk mark received inventory inspection PASSED (EXPORT → READY_FOR_LOADING) | `POST` | `/api/warehouse-inventory/bulk-mark-inspected` | `modules/warehouses/warehouse-inventory.controller.ts:202` |
| Unload all eligible export items assigned to an arrived Djibouti-side train | `POST` | `/api/warehouse-inventory/export/auto-unload-at-djibouti` | `modules/warehouses/warehouse-inventory.controller.ts:294` |
| Customer signs one handover (EDR last-mile: one signature per truck) | `POST` | `/api/warehouse-inventory/handovers/:handoverId/sign` | `modules/warehouses/warehouse-inventory.controller.ts:493` |
| Unload all eligible assigned bookings of an ARRIVED import train (→ UNLOADED) | `POST` | `/api/warehouse-inventory/import/auto-unload-arrived-bookings` | `modules/warehouses/warehouse-inventory.controller.ts:244` |
| Receive inventory at a warehouse location | `POST` | `/api/warehouse-inventory/receive` | `modules/warehouses/warehouse-inventory.controller.ts:322` |
| Bulk-receive selected eligible PAID bookings into a location | `POST` | `/api/warehouse-inventory/receive-bulk` | `modules/warehouses/warehouse-inventory.controller.ts:140` |
| Reserve stored inventory for a PAID booking | `POST` | `/api/warehouse-inventory/reserve` | `modules/warehouses/warehouse-inventory.controller.ts:330` |
| Load selected inventory items onto their allocated wagons for a train | `POST` | `/api/warehouse-inventory/train/:scheduleId/load` | `modules/warehouses/warehouse-inventory.controller.ts:184` |
| Mark loaded inventory DISPATCHED (left the terminal) | `PATCH` | `/api/warehouse-inventory/:id/dispatch` | `modules/warehouses/warehouse-inventory.controller.ts:602` |
| Record Yes/No double handling after unloading (Yes applies the double-handling fee rule) | `PATCH` | `/api/warehouse-inventory/bookings/:bookingId/double-handling` | `modules/warehouses/warehouse-inventory.controller.ts:556` |
### Warehouse Yard
| Title | Method | Endpoint | Source |
| --- | --- | --- | --- |
| Create a zone within a yard | `POST` | `/api/warehouse-yards/:yardId/zones` | `modules/warehouses/warehouse-yards.controller.ts:50` |
| Update warehouse yard | `PATCH` | `/api/warehouse-yards/:id` | `modules/warehouses/warehouse-yards.controller.ts:36` |
### Warehouse Zone
| Title | Method | Endpoint | Source |
| --- | --- | --- | --- |
| Update warehouse zone | `PATCH` | `/api/warehouse-zones/:id` | `modules/warehouses/warehouse-zones.controller.ts:37` |
### Weight Limit Rule
| Title | Method | Endpoint | Source |
| --- | --- | --- | --- |
| Create a weight limit rule | `POST` | `/api/weight-limit-rules` | `modules/rule-engine/controllers/weight-limit-rules.controller.ts:32` |
| Update a weight limit rule | `PATCH` | `/api/weight-limit-rules/:id` | `modules/rule-engine/controllers/weight-limit-rules.controller.ts:39` |
| Soft-delete a weight limit rule | `DELETE` | `/api/weight-limit-rules/:id` | `modules/rule-engine/controllers/weight-limit-rules.controller.ts:46` |
### Yard
| Title | Method | Endpoint | Source |
| --- | --- | --- | --- |
| Create a yard | `POST` | `/api/yards` | `modules/rule-engine/controllers/yards.controller.ts:53` |
| Move a yard up or down in display order | `POST` | `/api/yards/:id/move-order` | `modules/rule-engine/controllers/yards.controller.ts:38` |
| Bulk reorder yards by ID list | `POST` | `/api/yards/reorder` | `modules/rule-engine/controllers/yards.controller.ts:30` |
| Update a yard | `PATCH` | `/api/yards/:id` | `modules/rule-engine/controllers/yards.controller.ts:60` |
| Soft-delete a yard | `DELETE` | `/api/yards/:id` | `modules/rule-engine/controllers/yards.controller.ts:67` |
### Yard Distance
| Title | Method | Endpoint | Source |
| --- | --- | --- | --- |
| Create a yard distance | `POST` | `/api/yard-distances` | `modules/rule-engine/controllers/yard-distances.controller.ts:42` |
| Update a yard distance | `PATCH` | `/api/yard-distances/:id` | `modules/rule-engine/controllers/yard-distances.controller.ts:49` |
| Soft-delete a yard distance | `DELETE` | `/api/yard-distances/:id` | `modules/rule-engine/controllers/yard-distances.controller.ts:56` |

View File

@@ -60,7 +60,6 @@
"@nestjs/typeorm": "^11.0.1",
"@nestjs/websockets": "^11.1.27",
"@tria-plc/api-common": "file:../../local-packages/tria-plc-api-common-1.6.0.tgz",
"@tria-plc/auditlog": "file:../../local-packages/tria-plc-auditlog-1.1.2.tgz",
"@tria-plc/iamapi-common": "file:../../local-packages/tria-plc-iamapi-common-1.0.0.tgz",
"amqp-connection-manager": "^5.0.0",
"amqplib": "^2.0.1",

View File

@@ -16,7 +16,6 @@ import {
import { IamBaselineSeeder, IamSeedModule } from "@edr/iam-seed";
import { IamModule } from "@tria-plc/iamapi-common";
import { SharedAuthModule } from "@tria-plc/api-common/modules/auth/shared-auth.module";
import { MezgebModule } from "@tria-plc/auditlog";
import appConfig from "./config/app.config";
import databaseConfig from "./config/database.config";
@@ -112,7 +111,6 @@ import { LastMileRequestsModule } from "./modules/last-mile-requests/last-mile-r
import { InterchangeDocumentsModule } from "./modules/interchange-documents/interchange-documents.module";
import { ImportOperationsModule } from "./modules/import-operations/import-operations.module";
import { AiModule } from "./modules/ai/ai.module";
import { AuditModule } from "./modules/audit/audit.module";
import { LoggerMiddleware } from "./logger.middleware";
import { LoginAudienceMiddleware } from "./modules/auth/login-audience.middleware";
import { PositionTypePermissionsCache } from "./common/position-type-permissions.cache";
@@ -169,19 +167,6 @@ if (!process.env.APPLICATION_NAME) {
return dataSource;
},
}),
// Request + entity-level audit logging over RabbitMQ (@tria-plc/auditlog).
// Must come after TypeOrmModule above so it picks up this app's DataSource.
// rmqUrl falls back the same way notifications.module.ts's RABBITMQ_URL
// does: the dev broker only provisions the `edr` user on the `payment`
// vhost (docker-compose's RABBITMQ_DEFAULT_USER/VHOST), so an unset
// RABBITMQ_URL must land there too, not on guest@'/' (403 ACCESS_REFUSED).
MezgebModule.forRoot({
applicationName: "freight-api",
rmqUrl:
process.env.RABBITMQ_URL ??
process.env.PAYMENT_RABBITMQ_URL ??
"amqp://localhost:5672",
}),
SharedAuthModule,
IamModule.forRoot({
applications: [EDR_FREIGHT_APPLICATION],
@@ -257,7 +242,6 @@ if (!process.env.APPLICATION_NAME) {
EimsModule,
FleetHistoryModule,
AiModule,
AuditModule,
],
providers: [
EdrOrgSeeder,

View File

@@ -56,11 +56,6 @@ import { EmployeePositionActivePeriod } from "@tria-plc/iamapi-common/entities/i
import { UnitConfiguration } from "@tria-plc/iamapi-common/entities/iam/organization-structure/unit-configuration.entity";
import { Site } from "@tria-plc/iamapi-common/entities/iam/site/site.entity";
import { SiteSetting } from "@tria-plc/iamapi-common/entities/iam/site/site-setting.entity";
import { AuditLog, AuditLogCommand } from "@tria-plc/auditlog";
// @tria-plc/auditlog's entities live in node_modules, same as the iam ones —
// the glob below only matches this app's own src/**/*.entity.ts.
const auditEntities = [AuditLog, AuditLogCommand];
const iamEntities = [
UnitSetting,
@@ -185,7 +180,6 @@ export function buildDataSourceOptions(): DataSourceOptions {
entities: [
__dirname + "/../**/*.entity.{ts,js}",
...iamEntities,
...auditEntities,
],
migrations: [],
};

View File

@@ -10,7 +10,6 @@ import {
ResponseTransformInterceptor,
createValidationPipe,
} from "@edr/api-common";
import { getAuditLoggerConfig } from "@tria-plc/auditlog";
import { AppModule } from "./app.module";
@@ -167,11 +166,6 @@ export async function createFreightApp(): Promise<NestExpressApplication> {
app.useGlobalFilters(new HttpExceptionFilter());
app.useGlobalInterceptors(new ResponseTransformInterceptor());
// Audit listener: consumes the RMQ events MezgebModule's client interceptor
// (app.module.ts) emits and persists them via the AuditLogController /
// AuditLogCommandController @EventPattern handlers. Same queue config the
// client side uses, reused from the package so the two never drift apart.
app.connectMicroservice(getAuditLoggerConfig());
await app.startAllMicroservices();
const config = new DocumentBuilder()

View File

@@ -1,32 +0,0 @@
import { Controller, Get, Query } from "@nestjs/common";
import { ApiOperation, ApiQuery, ApiTags } from "@nestjs/swagger";
import { BookingStaff } from "../../common/booking-guards";
import { FREIGHT_PERMS } from "../../seed/freight-permissions.registry";
import { AuditService } from "./audit.service";
@ApiTags("audit")
@Controller("audit")
@BookingStaff(FREIGHT_PERMS.audit.view)
export class AuditController {
constructor(private readonly auditService: AuditService) {}
@Get("logs")
@ApiOperation({ summary: "List freight-api audit log commands" })
@ApiQuery({ name: "skip", type: Number, required: false })
@ApiQuery({ name: "take", type: Number, required: false })
list(@Query("skip") skip?: string, @Query("take") take?: string) {
// Same fallback chain @tria-plc/auditlog's client interceptor uses to
// stamp AuditLog.application (mezgeb/client/client-audit.interceptor.js)
// — reading it here instead of a hardcoded literal means this can't
// silently drift out of sync with whatever APPLICATION_NAME/APP_NAME
// actually is at runtime.
const application =
process.env.APPLICATION_NAME ?? process.env.APP_NAME ?? "DEFAULT";
return this.auditService.list(
application,
skip !== undefined ? parseInt(skip, 10) : undefined,
take !== undefined ? parseInt(take, 10) : undefined,
);
}
}

View File

@@ -1,13 +0,0 @@
import { Module } from "@nestjs/common";
import { TypeOrmModule } from "@nestjs/typeorm";
import { AuditLogCommand } from "@tria-plc/auditlog";
import { AuditController } from "./audit.controller";
import { AuditService } from "./audit.service";
@Module({
imports: [TypeOrmModule.forFeature([AuditLogCommand])],
controllers: [AuditController],
providers: [AuditService],
})
export class AuditModule {}

View File

@@ -1,70 +0,0 @@
import { Injectable } from "@nestjs/common";
import { InjectRepository } from "@nestjs/typeorm";
import { Repository } from "typeorm";
import { AuditLogCommand } from "@tria-plc/auditlog";
import { CLIENT_APP_HEADER } from "../auth/login-audience.middleware";
export interface AuditLogListResult {
count: number;
items: AuditLogCommand[];
}
/**
* Own read path onto @tria-plc/auditlog's tables, gated by AuditController's
* @BookingStaff — the package's own AuditLogCommandController (mounted at
* /api/audit-log-commands) ships with no guards at all, so it can't be used
* directly for a permission-gated UI. Query mirrors the package's
* AuditLogCommandService.buildAuditLogQuery/getAllAuditLogs exactly.
*/
@Injectable()
export class AuditService {
constructor(
@InjectRepository(AuditLogCommand)
private readonly auditLogCommandRepository: Repository<AuditLogCommand>,
) {}
async list(
application: string,
skip = 0,
take = 10,
): Promise<AuditLogListResult> {
const [items, count] = await this.auditLogCommandRepository
.createQueryBuilder("audit_log_commands")
.leftJoinAndSelect("audit_log_commands.auditLog", "auditLog")
.andWhere(
"(audit_log_commands.auditLogId IS NULL OR auditLog.application = :application)",
{ application },
)
.andWhere(
"(audit_log_commands.auditLogId IS NULL OR auditLog.status = :status)",
{ status: "Commit" },
)
// Backoffice-only view: portal (customer-facing) writes carry the same
// request-header set by every axios call from that app — see
// login-audience.middleware.ts. Rows with no linked auditLog (child/
// event commands with no request context) stay visible; they aren't
// attributable to any frontend, so they're not portal noise either.
.andWhere(
"(audit_log_commands.auditLogId IS NULL OR auditLog.requestHeader ->> :clientAppHeader = :clientApp)",
{ clientAppHeader: CLIENT_APP_HEADER, clientApp: "backoffice" },
)
.select([
"audit_log_commands.id",
"audit_log_commands.createdAt",
"audit_log_commands.deletedAt",
"audit_log_commands.entityName",
"audit_log_commands.queryMethod",
"audit_log_commands.changes",
"audit_log_commands.payload",
"auditLog.id",
"auditLog.user",
])
.addOrderBy("audit_log_commands.createdAt", "DESC")
.skip(skip)
.take(take)
.getManyAndCount();
return { count, items };
}
}

View File

@@ -1,4 +1,4 @@
import { Injectable, Logger, SetMetadata } from "@nestjs/common";
import { Injectable, Logger } from "@nestjs/common";
import { Nack, RabbitSubscribe } from "@golevelup/nestjs-rabbitmq";
import { Public } from "@edr/api-common";
import {
@@ -14,11 +14,6 @@ import { PaymentService as PaymentSvc } from "./payment.service";
const FREIGHT_QUEUE = PAYMENT_QUEUES[PaymentService.FREIGHT];
// @tria-plc/auditlog's global ClientLoggerInterceptor (present in deployed builds)
// crashes on non-HTTP contexts (`originalUrl.split` on a RabbitMQ message) and the
// resulting requeue storm blocks payment.succeeded forever. Its IgnoreLoggerAudit
// decorator is just this metadata key — set it directly so we don't need the package.
@SetMetadata("ignoreAuditLogger", true)
@Injectable()
export class PaymentEventsConsumer {
private readonly logger = new Logger(PaymentEventsConsumer.name);

View File

@@ -91,9 +91,8 @@ export class CargoTypesRepository implements ICargoTypesRepository {
/**
* Diffs the wagon-type links through the relation query builder rather than
* an entity save: junction-row inserts from save() broadcast afterInsert with
* no entity attached, which the @tria-plc/auditlog subscriber (deployed
* builds) dereferences and crashes the request on.
* an entity save, so junction rows are written without broadcasting
* afterInsert events for entity-less inserts.
*/
private async syncWagonTypes(
id: string,

View File

@@ -1187,11 +1187,6 @@ export const CONFIG_SETTINGS_PERMISSIONS: FreightPermissionSeed[] = [
"edr_freight_app:settings:dropdown:manage",
"Manage dropdown settings",
),
perm(
"b4c00001-0001-4000-8000-000000000001",
"edr_freight_app:audit:view",
"View audit logs",
),
];
// M. Staff / IAM admin — NEW keys only. The employee_registration / role_assignment
@@ -1894,9 +1889,6 @@ export const FREIGHT_PERMS = {
manage: "edr_freight_app:settings:support_content:manage",
},
},
audit: {
view: "edr_freight_app:audit:view",
},
support: {
agentView: "edr_freight_app:support:agent_view",
agentSend: "edr_freight_app:support:agent_send",

View File

@@ -43,7 +43,6 @@ import ReportsHubPage from "./pages/reports/ReportsHubPage";
import ReportPage from "./pages/reports/ReportPage";
import AiBookingMockTestPage from "./pages/ai/AiBookingMockTestPage";
import PaymentsPage from "./pages/payments/PaymentsPage";
import AuditLogsPage from "./pages/audit/AuditLogsPage";
//import EmployeesPage from "./pages/dashboard/user-management/EmployeesPage";
import { RequirePermission } from "./components/auth/RequirePermission";
import { FREIGHT_PERMS } from "./lib/permissions";
@@ -773,14 +772,6 @@ const App = () => {
</RequirePermission>
}
/>
<Route
path="audit-logs"
element={
<RequirePermission permission={FREIGHT_PERMS.audit.view}>
<AuditLogsPage />
</RequirePermission>
}
/>
<Route
path="contract-templates"
element={

View File

@@ -184,13 +184,6 @@ const ROUTE_META: Array<{ prefix: string; meta: PageMeta }> = [
subtitle: "Manage dropdown options used across the platform",
},
},
{
prefix: "/dashboard/audit-logs",
meta: {
title: "Audit Logs",
subtitle: "Request and entity-level activity recorded across the freight API",
},
},
{
prefix: "/dashboard/configuration/contract-validity-periods",
meta: {

View File

@@ -8,7 +8,6 @@ import {
FileSignature,
FileText,
Hammer,
History,
Landmark,
LayoutDashboard,
LayoutGrid,
@@ -501,12 +500,6 @@ export const buildSidebarSections = (demoItems: SidebarItem[]): SidebarSection[]
FREIGHT_PERMS.settings.supportContent.manage,
],
},
{
label: "Audit logs",
href: "/dashboard/audit-logs",
icon: <History />,
permission: FREIGHT_PERMS.audit.view,
},
{
label: "Configuration",
href: "/dashboard/configuration",

View File

@@ -321,10 +321,6 @@ export const URL_CONSTANTS = {
SUMMARY: "/payments/summary",
},
AUDIT: {
LOGS: "/audit/logs",
},
LOCOMOTIVES: {
BASE: "/locomotives",
BY_ID: (id: string) => `/locomotives/${id}`,

View File

@@ -338,9 +338,6 @@ export const FREIGHT_PERMS = {
manage: "edr_freight_app:settings:support_content:manage",
},
},
audit: {
view: "edr_freight_app:audit:view",
},
staff: {
roles: {
view: "edr_freight_app:staff:roles:view",

View File

@@ -1,190 +0,0 @@
import { Badge, Box, Card, Stack, Text } from "@mantine/core";
import { useQuery } from "@tanstack/react-query";
import { PageContainer, PageHeader } from "@/components/page";
import { api } from "@/services/api";
import type {
AuditLogRow,
AuditQueryMethod,
AuditUser,
LocalizedText,
} from "@/services/audit.service";
import {
DataTable,
DataTableFooter,
usePagination,
type ColumnDef,
} from "@edr/ui-common";
const ACTION_LABELS: Record<AuditQueryMethod, string> = {
INSERT: "Created",
UPDATE: "Updated",
DELETE: "Deleted",
INSERT_CHILD: "Linked child",
DELETE_CHILD: "Unlinked child",
};
const ACTION_COLORS: Record<AuditQueryMethod, string> = {
INSERT: "edr-green",
UPDATE: "yellow",
DELETE: "red",
INSERT_CHILD: "indigo",
DELETE_CHILD: "gray",
};
function formatDateTime(iso: string): string {
const d = new Date(iso);
return Number.isNaN(d.getTime())
? "—"
: d.toLocaleString(undefined, {
year: "numeric",
month: "short",
day: "numeric",
hour: "2-digit",
minute: "2-digit",
});
}
// See LocalizedText: `name`/`title` lifted from a raw audited entity can be
// a plain string or IAM's { am, en } — never render either directly.
// "undefined undefined" is the producer's own broken template when no user
// was attached at all (unauthenticated/customer flows, e.g. Fayda
// verification) — filtered out here rather than shown as raw garbage.
function localize(value: LocalizedText | null | undefined): string | undefined {
if (!value) return undefined;
if (typeof value === "object") return value.en ?? value.am ?? undefined;
if (/^undefined(\s+undefined)?$/.test(value.trim())) return undefined;
return value;
}
function formatUser(user: AuditUser | null | undefined): string {
return localize(user?.name) ?? user?.id ?? "—";
}
function summarize(row: AuditLogRow): string {
if (row.changes?.length) {
return row.changes
.slice(0, 2)
.map((c) => c.field)
.join(", ") + (row.changes.length > 2 ? `, +${row.changes.length - 2} more` : "");
}
if (row.payload) {
return (
localize(row.payload.name) ?? localize(row.payload.title) ?? row.payload.id ?? "—"
);
}
return "—";
}
const tableHeader =
"text-xs font-semibold uppercase tracking-wide text-muted-foreground";
export default function AuditLogsPage() {
const { pagination, setPagination } = usePagination({ pageSize: 20 });
const filter = {
skip: pagination.pageIndex * pagination.pageSize,
take: pagination.pageSize,
};
const { data, isLoading, isError } = useQuery(
api.audit.list.queryOptions({ input: { filter } }),
);
const rows = data?.items ?? [];
const total = data?.count ?? 0;
const pageCount = Math.max(1, Math.ceil(total / pagination.pageSize));
const columns: ColumnDef<AuditLogRow>[] = [
{
id: "time",
header: () => <span className={tableHeader}>Time</span>,
cell: ({ row }) => (
<span className="text-sm text-muted-foreground">
{formatDateTime(row.original.createdAt)}
</span>
),
},
{
id: "action",
header: () => <span className={tableHeader}>Action</span>,
cell: ({ row }) => (
<Badge
color={ACTION_COLORS[row.original.queryMethod] ?? "gray"}
variant="light"
radius="sm"
>
{ACTION_LABELS[row.original.queryMethod] ?? row.original.queryMethod}
</Badge>
),
},
{
id: "entity",
header: () => <span className={tableHeader}>Entity</span>,
cell: ({ row }) => (
<span className="font-mono text-sm text-foreground">
{row.original.entityName}
</span>
),
},
{
id: "user",
header: () => <span className={tableHeader}>User</span>,
cell: ({ row }) => (
<span className="text-sm text-foreground">
{formatUser(row.original.auditLog?.user)}
</span>
),
},
{
id: "summary",
header: () => <span className={tableHeader}>Summary</span>,
cell: ({ row }) => (
<span className="truncate text-sm text-muted-foreground">
{summarize(row.original)}
</span>
),
},
];
return (
<PageContainer>
<PageHeader
title="Audit Logs"
subtitle="Request and entity-level activity recorded across the freight API."
/>
<Card p={0}>
<Stack gap={0}>
<Box px="md" pt="md" pb="sm" w="100%">
<Text size="sm" c="dimmed">
{total} record{total !== 1 ? "s" : ""}
</Text>
</Box>
<Box style={{ overflowX: "auto" }} w="100%">
<DataTable
columns={columns}
data={rows}
status={isLoading ? "loading" : isError ? "error" : "success"}
pagination={{
pageIndex: pagination.pageIndex,
pageSize: pagination.pageSize,
pageCount,
totalCount: total,
}}
tableOptions={{
state: { pagination },
onPaginationChange: setPagination,
manualPagination: true,
pageCount,
}}
containerClassName="border-0 shadow-none bg-transparent"
footer={DataTableFooter}
/>
</Box>
</Stack>
</Card>
</PageContainer>
);
}

View File

@@ -166,11 +166,6 @@ import {
type SaveLocomotivePayload,
} from "./locomotives.service";
import { overviewService } from "./overview.service";
import {
auditService,
type AuditLogListFilter,
type PaginatedAuditLogs,
} from "./audit.service";
import { reportsService } from "./reports.service";
import type { ReportQueryInput, ReportResult } from "@/types/reports";
import {
@@ -2153,15 +2148,6 @@ export const api = {
),
},
audit: {
list: endpoint<{ filter?: AuditLogListFilter }, PaginatedAuditLogs>(
"audit",
"list",
({ filter }) => auditService.list(filter),
({ filter }) => ["audit", "list", filter ?? {}],
),
},
signatures: {
mySignature: endpoint<void, SavedSignature | null>(
"me",

View File

@@ -1,70 +0,0 @@
import { api as client } from "../auth/http";
import { unwrap } from "@/utils/endpoint";
import { URL_CONSTANTS } from "@/constants/URLS";
const A = URL_CONSTANTS.AUDIT;
// Shape from @tria-plc/auditlog's AuditLogCommandController — see
// local-packages/FRONTEND_GUIDE.md.
export type AuditQueryMethod =
| "INSERT"
| "UPDATE"
| "DELETE"
| "INSERT_CHILD"
| "DELETE_CHILD";
export interface AuditFieldChange {
field: string;
from: unknown;
to: unknown;
}
// IAM entities (users, orgs, positions, ...) name themselves bilingually —
// see edr-org.seeder.ts. Any `name`/`title` field lifted from a raw audited
// entity (auditLog.user, payload) can come back as either a plain string or
// this shape; both `name` fields below reflect that.
export type LocalizedText = string | { am?: string; en?: string };
// The vendored interceptor's own broken template produces a plain string
// ("undefined undefined") when no user was attached at all (unauthenticated/
// customer flows) — that's the non-bilingual string case for `name` here.
export interface AuditUser {
id?: string;
name?: LocalizedText;
organizationId?: string;
organizationName?: string;
[key: string]: unknown;
}
export interface AuditLogRow {
id?: string;
createdAt: string;
deletedAt?: string | null;
entityName: string;
queryMethod: AuditQueryMethod;
changes?: AuditFieldChange[] | null;
payload?: { name?: LocalizedText; title?: LocalizedText; id?: string } | null;
auditLog?: { id?: string; user?: AuditUser | null };
}
export interface AuditLogListFilter {
skip?: number;
take?: number;
}
export interface PaginatedAuditLogs {
items: AuditLogRow[];
count: number;
}
export const auditService = {
list: async (filter?: AuditLogListFilter): Promise<PaginatedAuditLogs> => {
const params: Record<string, number | undefined> = {
skip: filter?.skip,
take: filter?.take,
};
const response = await client.get<PaginatedAuditLogs>(A.LOGS, { params });
const data = unwrap(response.data) as PaginatedAuditLogs;
return { items: data.items ?? [], count: data.count ?? 0 };
},
};

2227
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff