Commit Graph

5105 Commits

Author SHA1 Message Date
Hagernesh Tadesse
e598f03104 Merge pull request #1470 from Tria-plc/Wrehousechanges
feat(billing): render EIMS documents in the MoR tax-document layout
2026-09-01 07:33:14 +03:00
Hagernesh
ed839ebb92 feat(billing): render EIMS documents in the MoR tax-document layout 2026-09-01 04:28:48 +00:00
Hagernesh Tadesse
7c5531c0c8 Merge pull request #1469 from Tria-plc/Wrehousechanges
Mor Verify fix
fix(eims): sign the /v1/verify request body
2026-09-01 06:45:50 +03:00
Hagernesh
fe3d398757 fix(eims): sign the /v1/verify request body 2026-09-01 03:43:10 +00:00
Hagernesh
dd9f597e01 fix(eims): resolve Addis Ababa addresses against MoR's sub-city tier
MoR's location master has no zone level in a chartered city: CITY_NAME is the
sub-city and LOCALITY_DESC is the numbered woreda. e-Trade fills the gap by
repeating the region in ZONE, pushing the sub-city into CITY_NAME and the
woreda number into LOCALITY_DESC, so every Addis Ababa buyer failed CITY_NAME
lookup and could not be filed at all.

Retry one level down when ZONE repeats the region, accepting it only when
both shifted levels resolve exactly — an unknown zone still fails with its own
message. Match a zero-padded 07 against MoR's own WOREDA 7 spelling, and
add the reviewed AMAHARA / KOLFIE KERANIYO / MISRAK SHOA aliases.

Verified against the dev database: 2 of 40 companies resolved before, 31 after.
2026-09-01 03:43:09 +00:00
Hagernesh
3721ac1ef0 feat(warehouses): gate loading queues on the train's station loading window
Warehouse loading queues and the train schedule page now share one truth:
the schedule's per-yard stationWorkLogs. A booking's items aren't loadable
until "Start loading" has been clicked for their boarding yard, mirroring
the same assertStationWorkStarted check the train schedule page's own Load
button already enforces.

- LoadableTrainRow/LoadableTrain carry originStationId + stationWorkLogs.
- TrainLoadableItem carries originYardId/originYardLabel/loadingWindowStarted,
  computed from station_work_logs in the same query.
- New YardLoadingWindows component surfaces the per-yard windows on the
  loading panel; ReceiveInventoryModal and LoadToTrainPanel wire it in.
- Invalidate loadable-trains/train-loadable-items/warehouse-inventory
  queries alongside train-scheduling ones, since they render off the same
  data.

Fixes the StationWorkLogJson typo that broke the freight-api build.
2026-09-01 03:43:09 +00:00
Hagernesh Tadesse
c7133ba1da Merge pull request #1467 from Tria-plc/Wrehousechanges
Wrehousechanges
2026-08-31 15:08:00 +03:00
Nathnael Wondisha
790d17f846 Merge pull request #1466 from Tria-plc/freight/fix/quick-fixes
fix
2026-08-31 14:46:54 +03:00
Nathnael
0ac85ebc1f fix 2026-08-31 11:45:55 +00:00
Hagernesh
cc50a6687a feat(bookings): per-row stations + totals footer on carriage acceptance sheet
The sheet printed Departure/Arrival Station as booking-level constants and
squeezed the footer figures into unrelated table columns (gross weight under
Cargo Name, full/empty under Wagon No.).

Stations now resolve per row from the slot's own board/alight yard, falling
back to the schedule's origin/destination and then the booking yards —
mirroring buildExportLoadListHtml's leg-slot handling.

The paper form's footer becomes its own tile block: In Total Wagon No., Tare
Weight, Load Capacity, Gross Weight, Equated Length, Full Wagon, Empty Wagon,
Total Amount. Rendered as .tile so buildTabularFallbackPdf still prints every
figure where Chromium is absent.
2026-08-31 11:28:36 +00:00
marshal
313e0053c3 Merge pull request #1464 from Tria-plc/freight_feature/usermanagement
fix train build issue
2026-08-29 23:28:35 +03:00
Marshal
678c5d7d49 fix train build issue 2026-08-29 20:26:16 +00:00
marshal
87ef817b1f Merge pull request #1463 from Tria-plc/freight_feature/usermanagement
fix train build issue
2026-08-29 22:26:48 +03:00
Marshal
ef1d310413 fix train build issue 2026-08-29 19:26:09 +00:00
marshal
522a65f0e7 Merge pull request #1461 from Tria-plc/freight_feature/usermanagement
feat(contracts): implement staff cancellation of contracts with reaso…
2026-08-29 14:36:08 +03:00
Marshal
6ac41cf078 feat(contracts): implement staff cancellation of contracts with reason and notification 2026-08-29 11:31:13 +00:00
Hagernesh
f23500c273 fix(train-scheduling): drop wagon slots with no physical wagon pinned from marshalling docs
A slot can hold a LOADED allocation with no physical wagon backing it
— a fleet shortfall can leave a booking's allocation unpinned, and a
REAL cut nulls physical_wagon_id without ever touching the slot's own
status. Neither buildImportLoadListHtml, buildExportLoadListHtml, nor
intercityOnBoardView checked for this: they rendered a ghost row (dash
wagon number, but cargo/container info still listed) and counted it
toward the Wagons tile.

Reproduced live on S-2026-00073: 5 slots (seq 60-64) with no physical
wagon, from a booking currently held out for lack of fleet, rendered
as phantom rows on the origin doc and every numbered marshalling doc
— visible as the Seq column running up to 64 despite only 54 real
wagons.

All three now additionally require physicalWagonId (or a resolved
wagonNumber, for the import doc's already-flattened shape) before a
slot gets a row. Added coverage for all three call sites.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-29 10:43:34 +00:00
Hagernesh Tadesse
3346e82d9d Merge pull request #1460 from Tria-plc/Wrehousechanges
feat(train-scheduling): per-row Departure/Arrival Station on marshall…
2026-08-29 13:06:31 +03:00
Hagernesh
7df96d3609 feat(train-scheduling): per-row Departure/Arrival Station on marshalling docs
buildExportLoadListHtml's wagon table (origin export doc, Marshalling
2/3, and the intercity fallback) had no station columns — only the
import doc did. Adds Departure Station / Arrival Station per row,
mirroring the import doc's existing pattern: a whole-route wagon reads
the schedule's own origin/destination, a leg-slot wagon reads its own
boardYardId/alightYardId instead (resolved via a new yardLabelById
opt, computed once per document from board+alight yard ids across the
trainSet).

Also fixes two pre-existing off-by-one colspans on the EMPTY row and
the 'No wagons on this train set' placeholder, now that the real
column count changed with the two new ones.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-29 10:02:46 +00:00
Hagernesh Tadesse
b6d27379b1 Merge pull request #1459 from Tria-plc/Wrehousechanges
fix(train-scheduling): scope numbered marshalling docs to their own s…
2026-08-29 12:45:10 +03:00
Hagernesh
122b6d447a fix(train-scheduling): scope numbered marshalling docs to their own stop's coupling
Marshalling 2's wagon list was computed from intercityOnBoardView's
CURRENT state (boardYardId == null || hasLoaded), which can't tell
'coupled at THIS stop' from 'coupled at a LATER stop that has, by
generation time, also already happened' — both read LOADED once the
trip has moved on. Reprinting Marshalling 2 (Dire Dawa) after
Marshalling 3's stop (Adama) had already run leaked Adama's 5 coupled
wagons into Dire Dawa's document (54 wagons became 59), reproduced
live on S-2026-00073.

marshallingDocumentAt now corrects the on-board list against the
adjustment log: a leg-slot wagon belongs on a stop's document only if
it has a logged ADD at or before that stop's own timestamp. Extracted
as wagonsAsOfStop so it's unit-testable without mocking the DB.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-29 09:38:53 +00:00
Abubeker Yasin
beb98edbf7 Merge pull request #1458 from Tria-plc/reschedule
feat: ( bookings ) add authenticated My Bookings history to the passe…
2026-08-29 12:29:33 +03:00
Hagernesh Tadesse
b2c64c1435 Merge pull request #1457 from Tria-plc/Wrehousechanges
fix(train-scheduling): drop not-yet-boarded wagons from every marshal…
2026-08-29 12:28:56 +03:00
Abubeker Yasin
6551660e5f feat: ( bookings ) add authenticated My Bookings history to the passenger portal 2026-08-29 12:28:42 +03:00
Hagernesh
80a057dde8 fix(train-scheduling): drop not-yet-boarded wagons from every marshalling doc
Leg-slot wagons planned to couple at a LATER stop were being counted
into the Wagons/Allocations/Total containers tiles on the ORIGIN
marshalling document (Import Load List, export load list) — inflating
the departing count from 54 to 64 on a real train, plus a 'TO BE
LOADED AT X' / 'TO LOAD AT X' row and a separate 'To load en route'
tally to work around it.

Filters those slots out of buildImportLoadListHtml / buildExportLoadListHtml
entirely instead: a wagon not part of the departing consist gets no
row and no count on this document, full stop. Its own coupling shows
up on THAT stop's own numbered marshalling document once it actually
happens (intercityOnBoardView already filtered correctly there,
unaffected). Deletes the now-dead pendingBoardYardLabelBySlot special-
casing, loadsHere guards, and the 'to load en route' tile.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-29 09:27:29 +00:00
Hagernesh Tadesse
fdcc69c43e Merge pull request #1456 from Tria-plc/Wrehousechanges
feat(loading): notify loaded and left-behind containers, surface the CAS
2026-08-29 11:50:20 +03:00
Hagernesh
fd6f0d03b6 feat(loading): notify loaded and left-behind containers, surface the CAS
A booking is routinely loaded in parts, and nothing told the customer which
containers boarded and which stayed behind. The carriage acceptance sheet was
the only record, and it both totalled up cargo still sitting in the yard and
lived inside a Warehouse documents bundle that direct truck-to-train cargo
has no business in.

The sheet now marks each wagon Loaded or Not loaded and totals only the loaded
ones. On load, the customer gets an in-app, SMS and email notice carrying the
train number, route, departure time and both container lists — capped to a
summary on SMS and email, complete in the inbox. Anything left behind also
raises a warehouse-desk notice so somebody owns finding it space.

That desk is addressed by a new warehouse_inventory:get_notification
permission: a recipient selector, not a route guard, so ops can assign who
gets pinged without granting access to anything.

The GRN notice went out over SMS alone, to whatever phone number the gate
clerk typed. Where the receive carries a booking it now resolves the company
and delivers in-app, SMS and email, skipping the typed phone so the customer
is not texted twice; manual and backlog receives keep the old path.
2026-08-29 08:47:39 +00:00
marshal
abf856547a Merge pull request #1453 from Tria-plc/freight_feature/usermanagement
Freight feature/usermanagement
2026-08-29 10:40:53 +03:00
Marshal
978f1889c6 feat(transit-agents): give transit agents a portal login
Optional IAM account per agent — nullable, nothing backfilled, so
roster-only rows keep working. Staff invite existing ones; new ones get
an account when an email is supplied. Activation reuses the shipping-line
link path. SMS reachability widens to Djibouti.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-29 07:40:07 +00:00
Hagernesh Tadesse
0723752563 Merge pull request #1450 from Tria-plc/Wrehousechanges
feat(warehouses): backoffice UI for container stack positions
2026-08-29 09:42:57 +03:00
Hagernesh
312c1f1da3 feat(warehouses): backoffice UI for container stack positions
Surfaces the physical stack/slot model in the staff app.

- ZoneLayoutModal: stacks drawn level by level with occupancy colours,
  configured-vs-built-vs-occupied counts, and stack create/delete plus
  block/reserve/free on empty levels
- SlotPicker in the store and move modals, offering only the next
  fillable level of each stack so the form cannot suggest a position
  the API will refuse
- move modal warns when a container is buried, lists the blockers, and
  disables the action instead of firing a 409
- fix: move() now asserts accessibility server-side, matching release —
  both are exits from a stack
2026-08-29 06:39:29 +00:00
Marshal
7e163088d9 fix issue, add transit flow, fix cancellation 2026-08-28 22:13:49 +00:00
Hagernesh Tadesse
abfd55d43e Merge pull request #1449 from Tria-plc/Wrehousechanges
feat(warehouses): track physical container stack and slot positions
2026-08-28 19:11:23 +03:00
Hagernesh
a6ea1a48ac feat(warehouses): track physical container stack and slot positions
Extends the warehouse hierarchy below zone with ground stacks and vertical
slots, so a container's exact position is recorded rather than only its zone.

- freight.warehouse_zone_stacks / warehouse_zone_slots, plus nullable
  stack_id / slot_id on warehouse_inventory (existing rows stay valid)
- slot occupancy is derived from inventory status, guarded by a partial
  unique index, so no exit path has to remember to free a slot
- placement service: hierarchy validation, bottom-up stacking rules,
  accessibility/blocking-container reads, capacity vs slot summaries
- stack CRUD with auto-generated slots; reuses warehouse-zone permissions
- slot support folded into the existing move()/store() paths
- fix: validateLocation now rejects a mismatched warehouse/yard/zone triple
- seed:warehouse-layout builds the layout from a JSON config
2026-08-28 16:09:41 +00:00
Hagernesh Tadesse
9de4e9e238 Merge pull request #1448 from Tria-plc/credit-invoice
feat(warehouses): record container vs bulk freight type
2026-08-28 18:10:00 +03:00
Hagernesh Tadesse
493f3a0924 Merge pull request #1447 from Tria-plc/credit-invoice
feat(warehouses): delete yards/zones and inspect zone contents
2026-08-28 18:06:11 +03:00
Hagernesh
ba79b36d90 feat(warehouses): record container vs bulk freight type
Nullable freight_type on the warehouse, null meaning it takes both.
No backfill: every existing warehouse is unrestricted today and
writing a value would narrow allocation behind the operator's back.
Reuses FREIGHT_TYPES from the booking entity rather than a third copy
of the same two values.
2026-08-28 15:04:39 +00:00
Hagernesh
d4373dfbe4 feat(warehouses): delete yards/zones and inspect zone contents
Yard and zone soft-delete, refused with 409 while a yard still has
zones or a zone still holds inventory. warehouse_zones:delete was
missing from the catalog — the role presets spread every zone key, so
its absence crashes FreightPositionsSeeder at boot; a migration seeds
it everywhere.

Clicking a zone opens its contents as a datatable. Container identity
comes from booking_container_units for booked cargo and from
containers for backlog registrations; bulk cargo keeps its row with no
container number rather than disappearing from the zone.
2026-08-28 14:54:17 +00:00
Hagernesh Tadesse
81109308dc Merge pull request #1446 from Tria-plc/credit-invoice
feat(warehouses): allow deleting a warehouse
2026-08-28 17:41:49 +03:00
Hagernesh
8ef50f9aff feat(warehouses): allow deleting a warehouse
Soft-delete route guarded by warehouses:delete, refused with 409 while
yards remain — zones and inventory hang off a yard, so cascading would
orphan stock. Backoffice list gets a delete action in both views,
omitted when the user lacks the permission.
2026-08-28 14:40:18 +00:00
Abubeker Yasin
224d46402d Merge pull request #1445 from Tria-plc/reschedule
Reschedule
2026-08-28 16:38:08 +03:00
Abubeker Yasin
1026c3b273 Merge branch 'dev' into reschedule 2026-08-28 16:36:51 +03:00
Abubeker Yasin
104a11b275 feat: (reschedule): hide the reschedule button from viewers who did not book the trip 2026-08-28 16:16:51 +03:00
Hagernesh Tadesse
1aedf91b99 Merge pull request #1444 from Tria-plc/credit-invoice
refactor(container-returns): surface the template download in the header
2026-08-28 15:37:30 +03:00
Hagernesh
1a4079a25d refactor(container-returns): surface the template download in the header
The import template was a link inside the bulk upload modal, so getting the
template meant opening a dialog that asks for the file you do not have yet.
It becomes a Download Template button next to Bulk Upload; the modal keeps
only its one-line explainer.
2026-08-28 12:34:56 +00:00
Abubeker Yasin
e2c2d677bc fix: ( bookings ) report the full amount paid after a reschedule 2026-08-28 15:29:47 +03:00
Hagernesh Tadesse
bcaf7f3247 Merge pull request #1443 from Tria-plc/credit-invoice
feat(container-returns): show booking ref, company and return time
2026-08-28 15:27:19 +03:00
Hagernesh
3a640de45a feat(container-returns): show booking ref, company and return time
The returns table renders one list holding both booking-linked and standalone
empty returns, but a booking-linked row showed the literal string Associated
in place of its reference, and nothing showed the owning company.

listEmptyReturns becomes a raw projection joining freight.bookings and
freight.companies, so each row carries bookingReference and a companyName that
falls back to the booking's company when none was typed on the return itself.

Return date was collected as a bare date input, storing every return at 00:00.
All three entry points — booking-linked, standalone and the bulk default — now
use datetime-local seeded from local time rather than UTC, and the column
renders date and time.
2026-08-28 12:25:25 +00:00
Hagernesh Tadesse
2d8b022f6b Merge pull request #1442 from Tria-plc/credit-invoice
Container return association withcompany
2026-08-28 15:13:25 +03:00
Hagernesh
a183f00657 fix(container-returns): dedupe company names in the return picker
freight.companies allows duplicate names, and Mantine v9 throws on duplicate
Autocomplete option values, taking down the whole Container Returns page with
a render error.

Dedupes the option list by trimmed name. resolveId now returns an id only
when exactly one company carries the name — an ambiguous name resolves to
nothing, so the return keeps the typed company name rather than silently
attaching to whichever duplicate happened to come first.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-28 12:11:08 +00:00