Commit Graph

419 Commits

Author SHA1 Message Date
Nathnael
562c8b48ba feat: updateing the loading and unloading 2026-08-24 14:01:19 +00:00
Nathnael
17c2dca3cc feat(reports): record loading and unloading time per stop
The OCC report publishes total loading and unloading time and the other
activity left over from a station stay, but nothing recorded when handling
started or ended — the July 2026 seed had to write the figure into a
checkpoint note. Four nullable stamps now ride the stop's arrival row, which
is the row the staying-time report builds a stay from (a turnaround's
departure belongs to a different schedule).

Handling is unloading start to loading end, so a container stop reads as one
window and a bulk station that only loads or only unloads still reports its
half; other activity is the rest of the stay. Both stay NULL where nothing
was logged rather than collapsing to zero.

- station-staying-time: + loading/unloading and other activity per stop
- turnaround-cycle: + the same, summed over the cycle's stops
- loading-unloading (new): per train per station per period, so a week or
  month view is that train's average over its stops
- the stop/stay query moves to operations-classification, shared by both
2026-08-24 12:09:04 +00:00
Marshal
716444e60a feat: add pagination to schedule history and consolidation approvals
- Implemented pagination in ScheduleHistoryPanel to manage large history entries.
- Updated API to support pagination parameters for schedule history.
- Enhanced ConsolidationApprovalsPage with tabbed navigation and pagination for approval rows.
- Introduced new types for paginated responses in bookings and train scheduling services.
- Added a database migration to create an index on wagon_booking_allocations for performance improvements.
2026-08-23 04:51:34 +00:00
Marshal
e2189040fa feat: add pagination to schedule history and consolidation approvals
- Implemented pagination in ScheduleHistoryPanel to manage large history entries.
- Updated API to support pagination parameters for schedule history.
- Enhanced ConsolidationApprovalsPage with tabbed navigation and pagination for approval rows.
- Introduced new types for paginated responses in bookings and train scheduling services.
- Added a database migration to create an index on wagon_booking_allocations for performance improvements.
2026-08-23 04:49:58 +00:00
marshal
105879b531 Merge pull request #1399 from Tria-plc/freight_feature/usermanagement
feat(train-scheduling): mid-route consist changes, audit history, saf…
2026-08-23 06:59:27 +03:00
Marshal
8e6fc09aac feat(train-scheduling): mid-route consist changes, audit history, safer workspace
- planned couples: loose wagons join the train at a route stop, added
  from the schedule yards tab; capacity credits them per corridor edge
  and coupling validates locomotive weight/length caps per leg
- real-cut toggle: a cut wagon permanently leaves the train build at
  its cut yard (soft cut still sits out one trip only)
- fix heaviest-leg display counting a shared slot's full cargo on
  every spanned edge (phantom pull-weight overload on S-2026-00045)
- confirmation dialogs for workspace add/load/unload/remove actions
- train-builder History and Detached-wagons tabs, backed by paginated
  endpoints; builder detaches now always write adjustment-log rows

Migrations 3660 (planned_wagon_couples, planned_wagon_real_cuts) and
3670 (adjustment log train_schedule_id nullable) — both applied to the
dev DB by hand; watch mode does not run migrations.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-23 03:55:54 +00:00
marshal
f4115f658d Merge pull request #1389 from Tria-plc/freight_feature/usermanagement
fix issue
2026-08-22 04:24:35 +03:00
Marshal
1ab2cfdb3b fix issue 2026-08-22 01:23:50 +00:00
Hagernesh
8181834037 Ticket
Empty container returns now generate a downloadable Equipment Interchange Receipt: container number & size, exact return timestamp, depot/location, condition, and the carrier/booking reference. Customers can download it from their booking's Documents tab (own-booking returns only); staff can pull any via the same endpoint.
2026-08-21 08:59:06 +00:00
Marshal
c5909b8ec7 [200~feat: add CustomsPaymentsCard and PaymentsTab components for handling customs payments and payment summaries 2026-08-21 08:47:47 +00:00
Marshal
d99b294c21 fix issue 2026-08-20 18:10:29 +00:00
Marshal
9e1d5ee9f2 [200~feat: add CustomsPaymentsCard and PaymentsTab components for handling customs payments and payment summaries 2026-08-20 15:45:42 +00:00
Hagernesh
0ceb595714 feat(bookings): add ad-hoc additional-charge module
New freight.additional_charge table, independent of BookingClearanceCharge
(unbounded per booking, free-text reason). Draft -> send issues an invoice
and notifies the customer in-app/SMS/email; settles via the standard
.invoice.paid event. Adds the Additional charges row-menu entry
next to Cancel booking, permission-gated.

Not included: the Additional Payments tab UI, add-charge modal, portal pay
flow.
2026-08-20 12:59:33 +00:00
marshal
3604e78fbe Merge pull request #1364 from Tria-plc/freight_feature/usermanagement
Freight feature/usermanagement
2026-08-20 14:49:27 +03:00
Nathnael
f28b6faf29 feat(operations-reporting): plan station targets per cargo category
The OCC report plans a station lane per cargo type — Nagad–Mojo container
and Nagad–Mojo fertilizer are separate numbers — but a target's identity
was period + metric + dimension + dimensionKey, so the two collided on
one slot. `cargo_category` is now part of the row and of the uniqueness
check; it stays null for cargo_category and container_class targets,
whose dimensionKey already carries the category.

The config grid showed raw codes (VOLUME_TONS, cargo_category, a yard
code). The list read now sends readable twins alongside the stored codes,
which stay exactly as they are because the reports join on them — the
same shape YardDistancesService uses. Labels resolve per dimension rather
than from one merged map: CONTAINER_EXPORT exists in both vocabularies
and reads differently in each, and merging them gave every cargo-category
row the container-class wording.
2026-08-20 11:36:32 +00:00
Nathnael
2ec818e590 Merge branch 'dev' into freight/nati-2
# Conflicts:
#	apps/edr-freight-api/src/app.module.ts
#	apps/edr-freight-api/src/seed/freight-permissions.registry.ts
#	apps/edr-freight-web/backoffice/src/components/layout/sidebar-sections.tsx
#	apps/edr-freight-web/backoffice/src/constants/URLS.ts
#	apps/edr-freight-web/backoffice/src/lib/permissions.ts
2026-08-20 11:33:19 +00:00
Marshal
c889797870 feat(bookings): two-level clearance charges (port + misc) billed to customer with invoices 2026-08-20 10:29:59 +00:00
Marshal
a809215e74 feat(bookings): two-level clearance charges (port + misc) billed to customer with invoices 2026-08-20 10:29:58 +00:00
Marshal
8d7551bb8e fix(portal): show declaration, T1 and Djibouti clearance documents to the customer 2026-08-20 08:43:08 +00:00
Marshal
c38fcff00d feat(bookings): two-level clearance charges (port + misc) billed to customer with invoices 2026-08-20 05:48:16 +00:00
Marshal
c138da6137 feat(bookings): two-level clearance charges (port + misc) billed to customer with invoices 2026-08-20 05:24:57 +00:00
Hagernesh
fc55f48371 feat(eims): implement POST /v1/bulkRegister
New endpoints:
  POST invoices/eims/bulk-register  { invoiceIds: [...] }  — trigger
  POST eims/webhook/bulk-register                          — MoR's callback

Fundamentally different shape from single register: bulkRegister
answers only {conversationId, status:202} immediately: MoR processes
the array asynchronously and pushes the real per-invoice results
(a mix of accepted/rejected in one array, per the collection's own
examples) to a webhook configured out of band. So this ships as two
halves that don't share a call stack — EimsBulkRegistrationService.
registerBulk() reserves a contiguous block of counters (durable
reservation, same doctrine as single register, extended to N items)
and submits; handleBulkCallback(), invoked by the new
EimsWebhookController whenever MoR gets around to it, settles.

New EimsSystemState.inFlightConversationId is the bulk equivalent of
inFlightInvoiceId — a whole batch outstanding, not one invoice — and
the two markers block each other since they share the same counter
sequence. The conversation id isn't known until MoR's 202 arrives, so
reservation stamps a locally-generated placeholder first (same
commit-before-the-network-call reasoning as single register), then
swaps it for MoR's real id right after — the only value the callback
can actually use to find the batch again.

Only the first invoice in a bulk batch chains via PreviousIrn — every
other item gets an empty string, matching the collection's own
two-invoice example exactly (MoR doesn't expect a batch to chain to
IRNs that don't exist yet at submission time).

Webhook has no auth (MoR has no JWT to send) — the conversation id
embedded in the payload is what stands between this and a forged
callback: an item only ever touches an invoice actually holding that
exact id, and an unknown id is logged and ignored, never applied.

Migration 3580000000000: eims_system_state.in_flight_conversation_id,
invoices.eims_bulk_conversation_id (tags which batch an invoice was
submitted in, so a stuck batch — webhook never arrived — can be found
and reconciled by conversation id). Applied to dev DB and recorded in
freight.migrations directly (idempotent IF NOT EXISTS DDL).

Not live-testable from this sandbox (no route to MoR's real gateway).
Signing the whole array as one envelope, the way single /v1/register
was confirmed live to need despite the collection's raw example
showing no envelope, is the reasonable extension of that confirmed
behavior, not a blind guess — but it has not itself been exercised
against the real gateway. Left for the first live bulk attempt to
confirm, same as every other MoR-facing assumption this integration
has made.
2026-08-19 18:35:09 +00:00
Nathnael
fb21ad1541 feat(WIP): filtering, exporting and more reports 2026-08-19 13:51:18 +00:00
marshal
c1150f8af2 Merge pull request #1337 from Tria-plc/freight_feature/usermanagement
Freight feature/usermanagement
2026-08-18 16:19:34 +03:00
Marshal
40904049cf feat: implement consolidation approval process for shared-wagon bookings
- Add migration for consolidation approvals table and status enum
- Create ConsolidationApprovalService to handle approval logic
- Implement repository for managing consolidation approvals
- Add entity for consolidation approval with necessary fields
- Develop frontend components for displaying and managing consolidation approvals
- Create tests for consolidation approval service to ensure correct behavior
2026-08-18 13:17:55 +00:00
Nathnael Wondisha
065f34aa67 Merge pull request #1335 from Tria-plc/freight/feat/yard-loc-ac
feat: yard scoping to position
2026-08-18 15:57:03 +03:00
Nathnael
6823a32fee feat: yard scoping to position 2026-08-18 12:55:11 +00:00
Marshal
c723b660e2 feat(freight): offer built-train wagons per boarding yard on multi-yard consists 2026-08-18 08:27:18 +00:00
Hagernesh
83d9265e85 Credit and Debt plus reason attribute inide register 2026-08-15 08:38:35 +00:00
Nathnael
a15d4d922e Merge branch 'dev' into freight/nati-2 2026-08-14 13:52:23 +00:00
Nathnael
4a4d3077d7 feat: Stripe-style filter bar for freight backoffice (pilot: contracts)
Replace the ad-hoc filter controls with a URL-linkable pill filter bar:
each filter is a pill that opens a type-aware popover (text/enum/date/
number/boolean, each with the right operator set), overflow filters live
behind a searchable "More filters" menu, sorting is a separate control,
and filter state round-trips through the URL query string (shareable,
back/forward-safe, backward compatible with existing ?statuses=A,B links).

Frontend (apps/edr-freight-web/backoffice/src/components/filters/):
- FilterDef schema + a pure url.ts codec (parse/serialize/toApiParams),
  with a 24-case round-trip + malformed-input test suite
- useFilters hook driving react-query params straight from useSearchParams,
  debounced search, saved views in localStorage (@mantine/hooks
  useLocalStorage), page-reset-on-filter-change baked into one
  setSearchParams call instead of a separate effect
- FilterBar/FilterPill/OperatorSelect/MoreFiltersMenu/SortControl +
  per-type popover bodies (Mantine)
- ContractRequestsPage migrated end to end as the pilot

Backend (apps/edr-freight-api):
- pagination.util: applySort() — whitelisted sortBy resolved against a
  per-module column map (never interpolated), with a mandatory `id ASC`
  tiebreaker so paginating a non-unique sort can't drop/duplicate rows
- facets.util: computeFacets() — one GROUP BY per enum column, each
  omitting its own predicate, so picking a value doesn't hide its siblings
- contracts/bookings: list-summary now returns real filter-scoped facet
  counts (contracts' getStatusCounts was unfiltered/global; superseded)
- deleted drivers/vehicles findAllWithFilters — dead code that
  interpolated an unwhitelisted sortBy straight into orderBy()
- migration: missing bookings(status)/wagons(status) indexes +
  (created_at DESC, id ASC) partials on the hot list tables

UI polish pass: inactive pill uses the opaque "default" variant instead
of a faint tinted outline, active pill uses "light" not "filled", larger
X hit target, applied filters sort first, sort control separated behind
a divider on the right and wraps independently from the filter row,
popover option rows are fully clickable (count moved inside the native
label) with bigger hit area and font, fixed a real date-filter bug where
the calendar's own portal falsely registered as an "outside click" and
closed the popover, and fixed a timezone bug where bare YYYY-MM-DD
strings were parsed as UTC instead of local time (shifts a day for EAT).

Not in this commit: rollout to the other ~59 list pages, the Ethiopian-
calendar DateBody branch, and the Family-B (client-side) bridge mode —
tracked in the filter-bar plan.
2026-08-14 13:18:46 +00:00
Hagernesh
b4f2ec1c75 feat(train-scheduling): load returned empties onto an export train
Empties had no way onto a departure: the return record could name a train
but nothing seated it on a wagon. Export schedules now expose a loading
action that packs selected returns onto free wagons at one 40ft or two 20ft
each, enforced both in the picker and in the API (existing empties on the
schedule count against their wagon).

Adds container_size, train_schedule_id and wagon_sequence_no to
freight.empty_container_returns.
2026-08-14 09:57:50 +00:00
Marshal
b9ba830a09 shipping line 2026-08-13 18:56:52 +00:00
Marshal
0e00a98ef3 feat: implement shipping line booking completion functionality
- Added ShippingLineBookingCompletionController and associated service to handle the completion of shipping line bookings.
- Introduced a new module for booking completion to maintain module separation and avoid cyclic dependencies.
- Updated the train scheduling global rules to set default desk hours to 24 hours.
- Modified existing services and entities to accommodate the new booking completion logic.
- Enhanced the front-end components to support the new booking completion flow, including updates to the booking detail and bookings pages.
- Implemented validation and error handling for booking completion, ensuring that only approved bookings can be completed.
- Added migration to set default desk hours in the database.
2026-08-13 15:10:27 +00:00
Marshal
5837ac7b6e Merge branch 'dev' 2026-08-13 14:14:31 +00:00
marshalyordanos
9fff469ffa feat: implement shipping line bookings management
- Add ShippingLineBookingsPage for listing and managing shipping line bookings.
- Create ShippingLineDocumentsModal for document uploads related to bookings.
- Introduce ShippingLineInitiateModal for initiating new shipping line bookings.
- Implement booking document state management with booking-doc-state utility.
- Add shipping line bookings service for API interactions.
- Update index to export new components and services.
- Enhance types for freight to include shipping line credits.
2026-08-13 15:54:40 +03:00
Hagernesh
c4bf3c9479 feat(freight): add company logo setting applied to every generated document
New logo-settings module (mirrors stamp-settings): single uploaded logo,
stored via FilesService/MinIO, injected as a data URL into invoice/receipt,
contract, warehouse, train-scheduling, and payment-receipt PDFs. Adds a
matching backoffice settings page and settings:logo:view/manage permissions.

>
2026-08-13 10:54:23 +00:00
marshalyordanos
9aae132dd4 feat: add shipping line companies management
- Implement ShippingLineCompaniesService for registering and managing shipping line companies.
- Create ResendActivationAction component for resending activation links to shipping lines.
- Develop ShippingLineCompaniesPage for listing and registering shipping lines with validation.
- Introduce shippingLineCompanies.service for API interactions related to shipping lines.
- Define types for shipping line companies, including registration and pagination.
- Add placeholder pages for shipping line portal, including home, bookings, help, invoices, and settings.
2026-08-13 08:54:20 +03:00
Hagernesh
2d4da8110b eims integration master test complete 2026-08-12 14:12:23 +00:00
Marshal
a02d24806b feat(rule-engine): add fuel surcharge rate trigger and cargo flag 2026-08-12 11:22:51 +00:00
Marshal
4efd65c105 feat: key bulk templates by trade direction
Bulk contract templates are now unique per (cargo type, trade direction,
customs option) instead of (cargo type, customs option). Intercity is
domestic and crosses no border, so it carries no customs variant:
with_customs stays null there, enforced by a check constraint.

Contract resolution already passed the trade direction through but the
bulk lookup dropped it, so one template served all three directions.

Container templates are unchanged — cargo_type_id is null on those rows,
which keeps them out of both the new index and the constraint.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-12 09:20:28 +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
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
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
marshalyordanos
ea6eccbf09 feat(upload): increase document upload size limits to 50MB across the application 2026-08-11 09:26:23 +03: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
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
41e8c08ba3 feat: WIP Contnet managemtn 2026-08-08 09:37:28 +00:00