Commit Graph

1449 Commits

Author SHA1 Message Date
Marshal
4b8fea2d76 chore: update package overrides in pnpm-workspace.yaml for dependencies 2026-08-12 07:17:13 +00:00
Marshal
6434bb266c Merge branch 'dev' 2026-08-12 06:44:30 +00:00
marshalyordanos
5da36eb128 feat: add wagon usage computation and maintenance logging features
- Implemented  utility to calculate wagon usage metrics for train schedules.
- Created  for sending wagons to maintenance with optional notes.
- Added unit tests for train builder maintenance functionalities, including formatting train run labels and building maintenance notes.
- Developed  component for merging train schedules with detailed previews and reasons for merging.
- Introduced  component for selecting wagons with search functionality and selection limits.
- Created  for displaying and filtering audit logs, including detailed views of individual log entries.
- Added  for handling API interactions related to audit logs, including fetching logs and entity types.
2026-08-12 09:36:50 +03:00
Nathnael Wondisha
08eebd21a2 Merge pull request #1242 from Tria-plc/freight/nati-2
feat(freight-api): seal EDR side of contracts with the one global stamp
2026-08-12 06:31:12 +03:00
ghost2023
a603807e8e feat(freight-api): seal EDR side of contracts with the one global stamp 2026-08-12 06:26:46 +03:00
marshalyordanos
35e5404b41 Refactor code structure for improved readability and maintainability 2026-08-11 19:36:00 +03:00
Nathnael Wondisha
13dca1a287 Merge pull request #1241 from Tria-plc/freight/nati-2
Freight/nati 2
2026-08-11 17:47:26 +03:00
Nathnael
d6e349f329 feat(companies): onboard co-operative unions and farms
They hold a TIN but no business licence, so there is no eTrade record to look
their registration up in. A checkbox on the first wizard step marks them, and
everything that assumed a trade licence bends around it:

- The company step replaces the eTrade lookup with typed registration details
  — name, region, zone, woreda, kebele, house number — required exactly because
  they are now on screen. applyEtradeSourcedFields skips the lookup rather than
  failing it, so what the customer sends is what is stored.
- No freight-forwarder role. Forwarding is licensed work, so the option is not
  offered, and the API refuses it at start-onboarding and at every later
  role-add rather than letting approval fail on a document they cannot produce.
- No per-role business-licence upload, client-side or in the completion gate.
- Their own document set (company_onboarding_documents_cooperative) merges on
  top of the nationality one, admin-managed like every other set. Nationality
  wins a fileKey collision so no slot renders twice, and the DARS paper is not
  injected into it — the set it merges onto already carries one.
- The owner is typed in full; with no eTrade manager on file the licence
  comparison reports "nothing to compare against", which backoffice now
  explains rather than leaving as a bare dash.

Stored as an attributes flag, not a column: everything it changes is
behavioural, and nothing queries or joins on it.
2026-08-11 12:54:51 +00:00
Nathnael
72164b0b8e fix(freight-portal): stop owner details vanishing between wizard steps
SourcedField rendered read-only whenever a value existed, so the input a
customer had just typed into turned into a badge as soon as the step saved and
they navigated back — and dropped out of requiredKeys at the same time. It now
locks on ownership instead: a Fayda verification owns what its claims filled,
everything else stays an editable, prefilled input.

The representation step follows the same rule and asks in the right order:

- The power-of-attorney question collapses to its answer once given, with a
  button back to it (none for a freight forwarder, whose answer is forced).
- A foreign company picks how to prove the person outright — Fayda or a
  passport — rather than being shown both at once.
- The representative's own fields appear only once the person is established,
  and only for what the verification did not supply; what it did supply is
  already on the panel above and is no longer repeated beneath it.
- Dropped the freight-forwarder lecture and the DARS blurb; the badge and the
  upload field's own help text already say both.

VAT numbers accept any non-blank value. A foreign tax authority's carries
letters and dashes and a co-operative's follows neither pattern, so the 10-11
digit rule only ever rejected numbers we had no business judging.
2026-08-11 12:54:38 +00:00
Hagernesh
1be72799f4 fix(train-scheduling): exempt direct-to-train export bookings from GRN gate
confirmScheduleLoading's assertExportBookingsReceived checked every
wagon-assigned export booking for a warehouse GRN, with no exception for
DIRECT_TO_TRAIN (manual truck-straight-to-wagon) bookings — the one
call site that lacked the carve-out already applied everywhere else GRN
is checked (export-received-gate, booking-journey, carriage acceptance).
Warehouse-routed export cargo still requires GRN; direct handovers rely
on the carriage acceptance sheet instead.
2026-08-11 12:14:13 +00:00
Nathnael
8cf041f494 test(companies): rewrite identity specs around the single subject
fayda-identity covers the two subjects, the passport alternative and the
eTrade owner comparison in place of ~40 general-manager assertions.
poa-delegation drops the poaSameAsOwner waiver case and asserts the opposite:
a forwarder is refused without a delegation letter however it represents
itself.
2026-08-11 11:53:42 +00:00
Nathnael
f611c33722 feat(companies): migrate existing companies off the general manager
Data rescue, not just schema tidying — the order matters:

1. Backfill companies.email from generalManagerEmail before the key is
   stripped, or companies whose only address lived there stop receiving mail.
2. Backfill ownerName/Email/Phone (now required) from owner, GM, contact
   person, then the columns; seed etradeManagerName so existing rows read as
   matching rather than as a false mismatch.
3. Convert poaSameAsOwner: non-forwarders become poaDeclared='no' with their
   poa* details cleared; forwarders become 'yes' and keep them, so they now
   owe a DARS letter they were previously waived.
4. Derive poaDeclared for everyone else.
5. Move drafts off the deleted 'personnel' wizard step.
6. Drop the general_manager_* columns and strip gm*/poaSameAsOwner.

down() restores the column shape only; the stripped values are gone by design.
2026-08-11 11:53:36 +00:00
Nathnael
a1bcdfb692 refactor(companies): one verified identity per company, no general manager
Replaces the owner/general-manager/PoA trio with a single identity whose
subject is the PoA when the company declares one and the owner otherwise.

- Drop the general manager everywhere: entity columns, DTOs, required-field
  list, self-service attributes, gm* identity handling.
- New explicit poaDeclared answer ("yes"/"no") replaces poaSameAsOwner. A
  DARS delegation letter is required iff it is "yes"; a freight forwarder is
  forced to "yes" server-side and gets no waiver.
- Owner name/email/phone become typeable and required, prefilled from the
  eTrade lookup, never falling back to the authenticated account.
- Store eTrade's manager separately (etradeManagerName/Phone) and expose
  ownerMatchesEtrade so backoffice compares the asserted owner against the
  licence instead of against itself.
- Foreign companies satisfy the identity with Fayda or a passport number, on
  whichever subject is verifying (poaPassportNumber added).
- Write companies.email/phone from the owner unconditionally, so a company
  without a Fayda-verified owner still has a notification address.
2026-08-11 11:53:26 +00:00
Hagernesh Tadesse
8cbd67ae59 Merge pull request #1236 from Tria-plc/eims-integration
feat(permissions): grant the director full warehouse authority
2026-08-11 11:55:42 +03:00
Hagernesh
dac2186c02 feat(permissions): grant the director full warehouse authority
The director position held no warehouse permissions at all. It now
carries the same warehouse set as the chief tier: dashboard, warehouse
/yard/zone CRUD, allocation and fee rule CRUD, the inventory operation
set, inspection reports, interchange documents and fee invoices.
Unlike the dispatcher, the director owns the allocation and fee rules
themselves. The positions seeder backfills existing environments on
boot.
2026-08-11 08:49:41 +00:00
marshalyordanos
07a120af5e feat(permissions): add granular permissions for train management actions
feat(train-builder): update permissions checks for train actions in UI
feat(contracts): enhance contract view and shipment request pages with new features
test(shipment-preview): add tests for customs clearing logic in booking preview
style(contract-sign-bar): create CSS for fixed sign bar layout
2026-08-11 11:48:16 +03:00
marshalyordanos
ea6eccbf09 feat(upload): increase document upload size limits to 50MB across the application 2026-08-11 09:26:23 +03:00
marshalyordanos
997998dcb2 feat(wagons): enforce wagon availability limits in transfer requests 2026-08-10 23:30:18 +03:00
marshalyordanos
40672e9c41 feat(freight): surface scheduling clock and gate pay during drain 2026-08-10 21:47:28 +03:00
marshal
d3096939e7 Merge pull request #1223 from Tria-plc/freight_feature/usermanagement
fix(freight-backoffice): render wagon transfer reason as sanitized HT…
2026-08-10 17:54:28 +03:00
Nathnael
615f7e6798 feat: add choice to etrade fetcher 2026-08-10 14:49:17 +00:00
Marshal
e29e3ff837 fix(freight-backoffice): render wagon transfer reason as sanitized HTML in a modal 2026-08-10 14:02:13 +00:00
Nathnael
c24c93bcf3 feat: add 11 digit vat 2026-08-10 13:56:25 +00:00
Hagernesh
b701ae3f47 Revert "docs: trim eims.types.ts file header comment"
This reverts commit f4b8dab58d921791b4b3f69e3c9952b6802835cc.
2026-08-10 12:14:18 +00:00
Hagernesh
1b4a317d92 docs: trim eims.types.ts file header comment
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-10 12:14:18 +00:00
Hagernesh
bf8eadbc85 fix: portal cannot load last-mile confirmation request
LAST_MILE_REQUESTS URL constants were missing the /api prefix every other
endpoint in URLS.ts carries — all five calls (get, submit, contract
view/document/sign) 404'd against the deployed API, so the departure
notification's confirm link never loaded for the customer.

Also widen GET /last-mile-requests/:id from @BookingStaff to @MixedAudience
with the same ownership check submit()/sign() already use — the confirm
page calls this as its first request, before the customer has done
anything else, so it can't be staff-only.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-10 12:14:18 +00:00
Nathnael Wondisha
29d472351f Merge pull request #1213 from Tria-plc/freight/nati-2
Freight/nati 2
2026-08-10 12:33:07 +03:00
ghost2023
d07d35e438 feat: allow owner to represent company as power of attorney 2026-08-10 12:31:13 +03:00
Nathnael Wondisha
826a313c9d Merge pull request #1207 from Tria-plc/freight/nati-2
Freight/nati 2
2026-08-10 09:56:59 +03:00
Nathnael
124c51071b fix: log email and sms 2026-08-10 06:56:07 +00:00
Nathnael
3d53f67156 fix: gm and poa email requirement 2026-08-10 06:46:25 +00:00
marshal
270ca2a141 Merge pull request #1205 from Tria-plc/freight_feature/usermanagement
Freight feature/usermanagement
2026-08-10 00:17:56 +03:00
Marshal
573e777e8f changes 2026-08-09 21:16:52 +00:00
Hagernesh
cde67e4c6a fix(freight): correct direct CAS lines and sync inventory on schedule load
End-to-end testing of the direct truck-to-train flow surfaced two defects
in the carriage acceptance sheet's no-wagon fallback. Container numbers
were read from freight.containers, which only gains rows at allocation,
so a direct booking's declared containers never appeared; they are read
from booking_container_units now, seal numbers included. The weight used
bulkTotalWeightTons alone, which is null for CONTAINER and PER_TON bulk
bookings, printing 0.00 — cargoTotalWeightVgm is the real weight there
and only holds an item count for PER_ITEM break-bulk.

Separately, warehouse cargo can be loaded from the Load-to-Train queue or
from the schedule, but loading from the schedule never advanced
warehouse_inventory. The booking went IN_TRANSIT while its cargo still
read as sitting in the warehouse — the same inconsistency that produced
the spurious dispatch block. loadBooking now moves the inventory to
LOADED in the same transaction, and no-ops for direct bookings.
2026-08-09 16:05:59 +00:00
Nathnael
a47c7f6b7a Merge branch 'dev' into freight/nati-2 2026-08-09 13:44:18 +00:00
Nathnael
ff9bb4954a fix: the content document 2026-08-09 13:27:05 +00:00
Nathnael
182787e143 fix: gm email 2026-08-09 07:57:45 +00:00
Marshal
aedab1b436 keep cents in CBE bills and prices 2026-08-08 21:09:20 +00:00
Marshal
80a14c176b keep cents in CBE bills and prices 2026-08-08 20:45:05 +00:00
Nathnael Wondisha
12eb07752e Merge pull request #1192 from Tria-plc/freight/nati-2
Freight/nati 2
2026-08-08 20:38:37 +03:00
Nathnael
352c873961 fix: add user email fall back 2026-08-08 15:38:00 +00:00
Hagernesh
337ff6cfe3 feat(freight): support direct truck-to-train export handover
Export cargo reaches a train two ways, but the platform only modelled
one. Direct truck-to-train cargo loads straight onto the wagon, never
enters a warehouse and so never has a GRN — yet assertExportReceivedWithGrn
required one before the carriage acceptance sheet could be issued or the
booking loaded from inside its schedule.

Adds export_handover_mode to freight.bookings (null = WAREHOUSE, so
existing bookings are unaffected) and teaches the shared gate to skip
DIRECT_TO_TRAIN. Both call sites are fixed by that single early return.

For direct bookings the carriage acceptance sheet builds its lines from
the booking's own containers, falling back to the declared bulk tonnage,
and is issuable as soon as the mode is chosen. Direct bookings are also
removed from the warehouse receive queue, since that cargo is never
coming to the shed.

Staff choose the mode from the booking detail page via a new endpoint
reusing bookings:operations. Switching to direct is refused once
warehouse inventory exists, so the two flows cannot cross.

Warehouse-then-train keeps every gate it had.
2026-08-08 15:16:31 +00:00
Nathnael
7a32bb95ff refactor: rm company profile 2026-08-08 15:06:46 +00:00
Marshal
88b995d3d7 fix issue 2026-08-08 14:22:38 +00:00
Marshal
4b8119fd3c chnages 2026-08-08 14:02:58 +00:00
Marshal
a42d32c27c feat(billing): USD offline bank-transfer payments 2026-08-08 13:37:05 +00:00
Hagernesh
80e1648672 fix 2026-08-08 13:17:43 +00:00
Hagernesh
204bfd2421 fix: let trains dispatch with cargo still in the warehouse
The export-only assertAllocatedCargoLoaded guard threw a
BadRequestException whenever a booking on the schedule had warehouse
inventory in RECEIVED, STORED or READY_FOR_LOADING, making it
impossible to dispatch a train whose cargo had not been inspected and
loaded onto a wagon.

Leaving cargo behind is an operational decision, not an error state.
The dispatch confirm dialog already lists unassigned and unloaded
bookings and offers Dispatch anyway, so the readiness signal is
preserved — only the hard block is gone.

Wagon/locomotive conflicts and the Djibouti gatepass check still block
dispatch: those are physical and legal conflicts, not cargo readiness.
2026-08-08 13:17:43 +00:00
Marshal
83b9e32670 usd remove from the portal 2026-08-08 12:40:07 +00:00
Nathnael Wondisha
a140add64e Merge pull request #1181 from Tria-plc/freight/nati-2
Freight/nati 2
2026-08-08 12:38:32 +03:00