Commit Graph

5074 Commits

Author SHA1 Message Date
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
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
Hagernesh
9e1e680394 feat(train-scheduling): numbered marshalling-doc picker per corridor stop
Backend now generates a separate marshalling document per corridor stop
where the consist actually coupled/uncoupled/switched something
(marshallingStops/marshallingDocumentAt), instead of one 'current
position' doc. Wires that into the backoffice:

- TrainScheduleV2DetailPage: the single Intercity Marshalling menu item
  becomes one item per stop with a logged change, falling back to the
  old single item when nothing has happened yet.
- TrainScheduleTrackPage: same fallback/menu treatment on its own
  marshalling button.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-28 12:11:08 +00:00
Hagernesh Tadesse
8be886eb1d Merge pull request #1441 from Tria-plc/credit-invoice
feat(import-operations): bulk Excel upload for yard-resident empty co…
2026-08-28 14:57:43 +03:00
Hagernesh
3f4fd8648a feat(import-operations): bulk Excel upload for yard-resident empty containers
Empties already sitting in an EDR yard but never entered in the system had
to be typed one at a time. Adds a bulk path: parse the sheet in the browser
(all-or-nothing, row-numbered errors), preview it, then POST one batch.

The server rejects the batch if any container already has a non-COMPLETED
return, so re-uploading the same sheet cannot duplicate boxes. No interchange
notification fires — these are historical rows, not a live handover.

Company is an Autocomplete over registered customers that also accepts a
typed name, since a backfilled box may belong to a company that is not a
customer yet. Exact name match sets customer_id; the name always lands in the
new empty_container_returns.company_name.

Also fixes the single Record Return modal, which collected Yard and Zone and
then dropped them before the API call, and did not invalidate the returns
list after a standalone return.
2026-08-28 11:38:19 +00:00
Nathnael Wondisha
132374ed03 Merge pull request #1440 from Tria-plc/freight/nati-2
feat(bookings): surface cargo declared on the shipment request
2026-08-28 14:29:02 +03:00
Nathnael
df8e10e041 feat(bookings): surface cargo declared on the shipment request
A GENERAL + customs contract does not let the customer book directly: they
submit a shipment request, and initiateForShipmentRequest opens a BARE booking
from it — "the request itself carries the quantities; the instance carries
none". Between initiation and completeUnderContract the booking legitimately
holds no cargo, so the export reported 0 containers for a customer who had
declared, say, 2 x 20FT. 23 bookings on dev data are in that state.

Adds two columns and one filter reading booking_requests.requested_lines:
- "Requested cargo" — the declared lines as text ("2 x 20FT"), handling the
  bulk shape too (tons / item count), not only containers.
- "Requested containers" — the declared box count, with a matching min/max
  filter on the list and the export.

Deliberately a separate column rather than a fallback inside the real container
count: a declared 2 x 20FT is a request, not two boxes on a booking, and
merging them would overstate operational totals. The two compose instead —
Containers = 0 AND Requested containers >= 1 is exactly the set awaiting
completion after clearance.

requested_lines is free-form jsonb, so the container array is guarded by
jsonb_typeof before jsonb_array_elements; one malformed row would otherwise
500 the whole list.
2026-08-28 11:23:52 +00:00
Hagernesh Tadesse
662af04b53 Merge pull request #1439 from Tria-plc/credit-invoice
feat(train-scheduling): log leg-slot board/alight events at checkpoints
2026-08-28 14:18:10 +03:00
Hagernesh
9cd29a21ed feat(train-scheduling): log leg-slot board/alight events at checkpoints
Leg slots (stampSlotLegs — bookings boarding/alighting mid-corridor)
reaching their board/alight yard produced no ScheduleWagonAdjustmentLog
row, unlike planned couples/cuts. Without it the marshalling document
has no way to show a wagon coupling in already loaded, or uncoupling
with cargo, at a mid-corridor stop.

recordCheckpoint now logs ADD at the board yard and REMOVE at the
alight yard for these slots, deduped against existing rows since
passedYardIds re-includes earlier stops on every checkpoint call.
Purely observational — the physical wagon was already pinned to the
slot at schedule-build time, so no wagon/consist state changes.

Stage 3 of the multi-stop marshalling plan (per-event numbered docs);
stages 4-6 (generalized doc builder, endpoints, UI) still open.
2026-08-28 11:16:46 +00:00
Nathnael Wondisha
cfd182cfea Merge pull request #1438 from Tria-plc/freight/nati-2
Freight/nati 2
2026-08-28 14:09:18 +03:00
Nathnael
aa3100d161 Merge branch 'dev' into freight/nati-2 2026-08-28 11:08:35 +00:00
marshal
b33e70d85b Merge pull request #1437 from Tria-plc/freight_feature/usermanagement
fix issues
2026-08-28 13:35:20 +03:00
Marshal
3015de7508 fix issues 2026-08-28 10:34:02 +00:00
Nathnael
74fb05207c feat(bookings): filter by container count, export per-type quantities
Container filters on the booking-requests list:
- "Container type" — bookings carrying that type.
- "Containers" — a count of BOXES (booking_container is one row per line with
  a quantity, so this sums quantity rather than counting rows), as an exact
  value or a range. It reads the container-type filter when one is set, so the
  one control answers both "10 containers in total" and "10 forty-footers".

Export gains a column per container type ("20FT containers", "40FT
containers"), plus the total "Containers" column and the two filters. Container
types are reference rows, not a constant, so `ExportDataset` gains an optional
`dynamicFields` resolver — DB-driven columns appended to the static list and
cached for the process, mirroring the existing `ExportFilterDef.optionsQuery`.
Adding a 45ft container type adds its column with no code change. The type id
is interpolated into raw SQL (ExportField.select has no parameter bag), so the
resolver drops any id that is not a uuid.

Also repoints the export's "Container VGM" column at the per-line sum. It was
projecting bookings.cargo_total_weight_vgm, which the portal wizard leaves at 0
for container freight — the same trap the tonnage fix addressed — so the column
read 0 for every portal-created container booking. Non-zero on dev data goes
from 54 to 170 of 208 container bookings.
2026-08-28 10:02:01 +00:00
Nathnael
339b8a8682 feat(bookings): filter and export booking content
The booking-requests list could filter by freight type but not by what is
actually in the booking, and the export's only cargo column showed the
commodity name — blank for every container booking, which stores no
commodity at all.

Adds one resolver, `bookingContentSql`, that answers "what did the customer
say is in this booking" per freight type: the container lines they entered
("2 × 40FT, 1 × 20FT") for container freight, since the wizard asks them for
no description; the commodity they picked from the cargo tree for bulk,
falling back to their free-text description.

List filters:
- "Content" — a single select flattening the cargo tree the same way the
  booking wizard presents it (group, then each commodity as "Bulk → Wheat").
  Picking a GROUP matches its whole subtree via a recursive walk, so "Bulk"
  returns all 44 bulk bookings rather than the 0 that carry the group id
  itself. This makes the existing, previously unexposed `cargoTypeId` param
  group-aware.
- "Content contains" — a contains-search over the description, the commodity
  name and the container types, so container bookings are reachable by "40FT"
  even though they carry no words of the customer's own.

Both apply through `applyListFilters`, so the list, its summary tiles and its
facets agree, and both are declared on the bookings export dataset — the
export button already forwards the page's filters verbatim.

Export fields: "Content" (default), plus "Cargo description" as its own
column. The old `cargo` column is unchanged and still selectable, relabelled
"Cargo (commodity)"; it loses only its default tick, so saved presets that
name it keep working.
2026-08-28 09:42:56 +00:00
Nathnael
0e9cfbce66 fix(reports): count container tonnage in exports and reports
Every SQL tonnage in the export datasets and report definitions used
`COALESCE(b.bulk_total_weight_tons, b.cargo_total_weight_vgm)`. COALESCE
falls through on NULL, never on 0 — and the portal booking wizard stores
`cargo_total_weight_vgm = 0` for container freight on purpose, because VGM
is captured per container line, not as a booking-level figure. So every
portal-created container booking reported as weighing nothing. The
backoffice wizard does store a booking-level total, so the same table holds
both shapes and the numbers looked erratic rather than uniformly zero.

Extract the resolver the TypeScript side already has three copies of
(bookingCargoTons, cargoTonsAndItems, totalVgmTons) into one SQL helper:
NULLIF both booking-level columns, then fall back to
SUM(booking_container.total_vgm_tons). Applied to the bookings and
train-schedules export datasets, the cargo-summary, contract-utilization and
booking-status-breakdown reports, and the intercity booking list.

On dev data this recovers 116 of 154 zero-weight container bookings and
raises live booking tonnage from 42,973 t to 61,424 t.
2026-08-28 09:20:46 +00:00
Nathnael
a6b2519136 feat(billing): filter invoices and manual payments by invoice type
Adds a free-form `types` CSV filter to the invoice list DTO and query
(same treatment as `paymentMethods` — each billing source mints its own
type string, so an IsIn would drop real values), carries it into the
invoices export dataset, and surfaces a Type column plus filter pill on
both the Invoices and Manual Payments tables.
2026-08-28 09:17:41 +00:00
Nathnael
78c6f8be0a feat(bookings): show the invoice number in Pricing & payment
The booking's freight invoice is looked up through the existing invoice
list endpoint (source=booking, sourceId matched by search), newest first
so a re-issue supersedes the old number.
2026-08-28 09:17:35 +00:00
marshal
081f66fb82 Merge pull request #1434 from Tria-plc/freight_feature/usermanagement
Freight feature/usermanagement
2026-08-28 10:34:31 +03:00
Marshal
ba56974e32 feat(train): enhance train history and scheduling features
- Added a reason field to train history entries for detach/maintenance actions.
- Updated TrainHistoryPanel to display the reason for wagon detachments.
- Introduced per-wagon load/unload functionality in ScheduleWorkspacePanel with a modal for managing individual wagons.
- Implemented API endpoints for loading and unloading specific wagons, including the ability to cancel remaining wagons with a reason.
- Refactored detach request handling in TrainBuilderDetailPage to streamline the process and remove the approval flow, requiring a reason for detachments.
- Updated types and services to support new wagon loading/unloading features and booking wagon retrieval.
2026-08-28 07:33:28 +00:00
Abubeker Yasin
bf2a6e827b Update index.ts 2026-08-28 10:02:48 +03:00
Marshal
8b8870e85e fix issue 2026-08-27 20:58:44 +00:00
Roba Boru
e00a1145b2 Merge branch 'main' of https://github.com/Tria-plc/edr-platform into feature/group-booking 2026-08-27 22:56:30 +03:00
Roba Boru
58be74bc29 Merge branch 'dev' of https://github.com/Tria-plc/edr-platform into feature/group-booking 2026-08-27 22:55:42 +03:00
Roba Boru
1603ff8211 Update group booking and package 2026-08-27 22:54:59 +03:00
Nathnael Wondisha
649a3c226c Merge pull request #1433 from Tria-plc/freight/nati-2
Freight/nati 2
2026-08-27 15:59:22 +03:00
Nathnael
7d9d1fb518 fix: invoice filter 2026-08-27 12:40:03 +00:00
Nathnael
63bd9197c5 fix: add platofmr transactions to the invoice pdf 2026-08-27 11:56:10 +00:00
marshal
8bd8955ac5 Merge pull request #1432 from Tria-plc/staging
Staging
2026-08-27 14:24:54 +03:00
marshal
09ae1276c9 Merge pull request #1431 from Tria-plc/dev
dev
2026-08-27 14:18:04 +03:00
marshal
041c165d6c Merge pull request #1430 from Tria-plc/freight_feature/usermanagement
feat: simplify dispatch logic by removing manual loading checks and u…
2026-08-27 14:17:27 +03:00
Marshal
1626903192 feat: simplify dispatch logic by removing manual loading checks and updating related comments 2026-08-27 11:16:43 +00:00
Nathnael Wondisha
81334258d3 Merge pull request #1429 from Tria-plc/freight/nati-2
Freight/nati 2
2026-08-27 12:40:16 +03:00