1680 Commits

Author SHA1 Message Date
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
Nathnael
41e8c08ba3 feat: WIP Contnet managemtn 2026-08-08 09:37:28 +00:00
Hagernesh Tadesse
268737cc4f Merge pull request #1180 from Tria-plc/lastmilerequest
Lastmilerequest
2026-08-08 12:30:43 +03:00
Hagernesh
c20f395f5f fix: stamp export self-haul truck arrival on receive to warehouse
Import's customer_truck_assignments.arrived_at is set by a separate later
gate action (release()'s arrival branch — the truck returning to collect
already-warehoused goods). Export has no equivalent second step: the truck
delivering cargo to the warehouse arrives and is received in the same act,
so its arrival was never recorded anywhere.

Add markCustomerTruckArrived, mirroring release()'s existing self-haul
departure UPDATE (plate-matched, COALESCE(arrived_at, NOW())), and call it
from receive()/bulkReceive() for EXPORT bookings
2026-08-08 09:28:14 +00:00
Stephanos A.
3a78605893 Merge pull request #1177 from Tria-plc/alpha
Alpha
2026-08-08 11:18:49 +03:00
Hagernesh
8d3dfa4113 fix(eims): map buyer Wereda to a MoR code too, fail locally if unmapped
BuyerDetails.Wereda had the same problem Region did: companies.woreda holds
names ("Yeka") MoR has no confirmed regex for, but every Wereda value MoR has
actually shown us (seller "12"/"13", the collection's "574") is 1-3 digits
like Region. Precautionary, not confirmed -- but the fix is identical either
way: resolve through EIMS_BUYER_WEREDA_CODES and refuse to file rather than
send a guessed code.

Generalises the Region resolver (resolveRegionCode -> resolveLocationCode) to
cover both fields instead of duplicating it.

No code was invented for "Yeka" -- EIMS_BUYER_WEREDA_CODES ships empty, so
this buyer now fails locally (new stop) instead of silently sending a name
that was never verified against MoR's schema.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-08 07:51:41 +00:00
Hagernesh
aec4f3d654 fix(eims): match MoR's payload rules found by live rejections
Three live attempts turned six guesses into facts. Each fix below is the
gateway's own words, not a reading of the collection.

DocumentNumber and InvoiceCounter move differently, because MoR constrains
them differently. The counter must not skip -- "Invoice counter is not
correct. expected : 1" -- so a definitively refused document hands it back.
The document number must not repeat, so the attempt burns it. Both stay spent
after an ambiguous result, where MoR may have stored the document.

NatureOfSupplies is normalised to MoR's exact lowercase constant and rejected
outright if it is neither 'goods' nor 'service'; its schema branches on this
as a oneOf, so "Service" invalidated the whole ItemList.

Buyer region resolves through a name->code map and now FAILS locally when
unmapped. MoR validates Region against ^[0-9]{1,3}$ on both the seller and
buyer sides, so a name can never be sent and a guessed code on a tax document
is worse than refusing to file.

Seller phone, email, region and wereda are checked against MoR's own regexes
before anything is sent, so a placeholder like "_" fails locally instead of
costing a request and a counter.

EIMS_TAX_CODE stays required and unset in .env.example: the choice between
VAT0 (zero-rated) and VATEX (exempt) is a tax position awaiting finance, and
MoR's enum is recorded there for whoever decides.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-08 06:57:50 +00:00
Hagernesh
2e26936bf1 fix(eims): retimestamp the EIMS migration to 3330000000000
3300000000000 collided with BookingWagonCancellations after the rebase.
3320000000000 is also unavailable: BulkContractTemplates3320000000000 is
already recorded in freight.migrations on the shared dev database from a
branch not present in this checkout, so checking only src/migrations is not
sufficient.

3330000000000 is unique across src/migrations and greater than the current
maximum timestamp recorded in freight.migrations.

Rename the migration file and class. The migration has no explicit name field
and no other code references its previous identity.

Verify migration discovery through the actual runtime path:
scripts/migrate.js loads compiled dist/migrations/*.js migrations, while
application boot does not run migrations automatically. Confirm the renamed
migration is present in dist.

For controlled dev verification, remove its migration-history row and run
pnpm migration:run again. The migration is discovered and applied under
3330000000000; its idempotent DDL produces no schema changes where the EIMS
schema already exists.
2026-08-08 04:56:57 +00:00
Hagernesh
6b1ffa831f feat(eims): surface filing state in backoffice and alert on failures
Two gaps that only bite in production: nobody could see an invoice's filing
state, and a blocked chain was visible only in the logs.

A failed filing now notifies the staff who can act on it. An ambiguous result
is HIGH priority because it blocks every further invoice for the system number
until someone resolves it, and nothing else would surface that -- the sweep
just goes quiet. A deterministic rejection affects one invoice, so it is
normal priority. The alert never throws: it must not mask the filing outcome.

The backoffice invoice detail page gains an EIMS card showing status, IRN,
counter, submitted and acknowledged timestamps, and the gateway's own error
message, with actions gated on invoices:eims_register. FAILED offers "File
again" -- the reservation model already allows re-registering a rejected
invoice, so retry needed no new endpoint. UNKNOWN offers no re-file button at
all, since resubmitting risks a duplicate registration, and instead explains
that a supervisor must record the IRN or discard the attempt.

Also aligns the migration class name with its renamed file. The DDL is
idempotent, so re-applying under the new name is a no-op against the columns;
it leaves one superseded row in freight.migrations.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-08 04:56:57 +00:00
Marshal
a05753f4a6 Merge branch 'dev' of github.com:Tria-plc/edr-platform into freight_feature/usermanagement
# Conflicts:
#	apps/edr-freight-web/backoffice/src/App.tsx
2026-08-07 23:55:30 +00:00
Marshal
cd90ccb0f5 feat(contracts): per-cargo bulk contract templates 2026-08-07 23:50:27 +00:00
marshal
7d8a2847e0 Merge pull request #1172 from Tria-plc/freight_feature/usermanagement
Freight feature/usermanagement
2026-08-08 02:00:59 +03:00
Marshal
d2eb47d14b feat(ui): 2026-08-07 23:00:06 +00:00
Nathnael Wondisha
a9d0a7e9e6 Merge pull request #1170 from Tria-plc/freight/nati-2
fix: iam migration
2026-08-08 01:19:40 +03:00
Nathnael
7f1d8fa260 fix: iam migration 2026-08-07 22:18:41 +00:00
Nathnael Wondisha
55e0c6fb0b Merge pull request #1168 from Tria-plc/freight/nati-2
fix: rm iam migration from migrate
2026-08-08 01:10:56 +03:00
Nathnael
d44c3f3a38 fix: rm iam migration from migrate 2026-08-07 22:10:05 +00:00
Hagernesh
b8e702dbc0 CAS total 2026-08-07 15:08:40 +00:00
Hagernesh
67573d0835 feat(eims): file issued invoices on a cron sweep, off by default
Invoices are produced by the freight workflow rather than by a person, so the
production path for filing is a sweep, not the manual endpoint.

A @Cron picks the oldest never-submitted invoice and hands it to the existing
EimsInvoiceRegistrationService -- no registration logic is duplicated, and the
durable reservation still decides whether the submission may proceed. Sweeping
rather than hooking the eleven places an invoice can be created or issued keeps
the workflow untouched, puts the HTTP call outside the invoice transaction by
construction, and lets a crash or restart be picked up on the next tick.

invoices.eims_status is the queue; nothing new is persisted. Only NOT_SUBMITTED
is eligible: UNKNOWN is never retried automatically because the document may
already be filed, and FAILED waits for an explicit retry policy. The tick also
refuses to start while eims_system_state holds an in-flight submission or a
block, and only one invoice is filed per tick so a misconfiguration costs one
rejected document rather than a burst.

Requires both EIMS_ENABLED and EIMS_AUTO_SUBMIT; the second defaults to false
so authentication can be live long before filing is. Logs carry the invoice
number, status and IRN only.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-07 15:08:40 +00:00
Hagernesh
02db3d2e73 fix(eims): keep EIMS filing off the Finance role preset
Invoices are produced by the freight workflow, not by a person, so filing is
not a Finance job function. The manual endpoints exist for controlled testing
and exceptional operations, and are left out of every role preset so they are
assigned to named admins instead.

Split resolve onto its own permission, invoices:eims_resolve: resolving an
unacknowledged submission clears the system-wide chain block and can record an
IRN against an invoice, which is a supervisor action rather than an
operational one. eims/status stays on the ordinary invoices:view.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-07 15:08:40 +00:00