Commit Graph

4663 Commits

Author SHA1 Message Date
Marshal
cd1e9c38a7 feat: add new audit endpoints for consolidation approvals and invoice management 2026-08-19 06:42:19 +00:00
Marshal
e6e4c07077 feat: implement parity checks for 20ft container bookings to ensure even numbers 2026-08-19 06:20:47 +00:00
Marshal
db64a1878b 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:59:15 +00: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
Marshal
22a3fb98ee feat: Implement consolidated booking functionality
- Added support for viewing and managing consolidated bookings in BookingRequestDetailPage.
- Enhanced BookingRequestsPage to display paired bookings in a single row.
- Introduced pairedDecision method in bookings service to handle decisions for both halves of a consolidated pair.
- Updated contracts service to include methods for manual consolidation of odd-20ft bookings.
- Created new components for selecting and editing consolidation partners.
- Added tests for paired decision logic and manual consolidation scenarios.
- Updated UI to reflect changes in booking handling and provide user feedback for odd container counts.
2026-08-18 12:50:35 +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
Marshal
1ca7776143 Enhance manual payment processing for USD and ETB invoices
- Updated API documentation and summaries to reflect support for both USD and ETB invoices.
- Modified data structures to include trade direction for invoices.
- Adjusted UI components to accommodate manual payment confirmations and display relevant information.
- Implemented filtering options for currency in the manual payments worklist.
2026-08-17 09:13:09 +00:00
Marshal
df488ebfaa chnages 2026-08-17 08:01:29 +00:00
Marshal
c3c3d08a41 fix issue 2026-08-17 07:55:52 +00:00
Marshal
52fb705a3e changes 2026-08-17 06:18:16 +00:00
Marshal
0e228bfd8d changes 2026-08-17 06:14:25 +00:00
marshal
90c268d821 Merge pull request #1304 from Tria-plc/dev
freight
2026-08-17 08:45:17 +03:00
marshal
a2c30a3c96 Merge pull request #1303 from Tria-plc/freight_feature/usermanagement
feat: enhance booking and scheduling features with shipping line supp…
2026-08-16 11:20:09 +03:00
Marshal
5ce5cbe29d feat: enhance booking and scheduling features with shipping line support and improved search functionality 2026-08-16 08:18:40 +00:00
marshal
12e6bb40b4 Merge pull request #1301 from Tria-plc/dev
freight
2026-08-15 22:02:01 +03:00
marshal
c331df3eba Merge pull request #1300 from Tria-plc/freight_feature/usermanagement
feat: enhance booking management with shipping line support and cargo…
2026-08-15 21:54:07 +03:00
Marshal
9f53114778 feat: enhance booking management with shipping line support and cargo handling improvements 2026-08-15 18:48:33 +00:00
marshal
9c770df509 Merge pull request #1299 from Tria-plc/freight_feature/usermanagement
fix: improve handling of container sizes in wagon cancellation logic
2026-08-15 14:55:57 +03:00
Marshal
156fa9d2e4 fix: improve handling of container sizes in wagon cancellation logic 2026-08-15 11:54:35 +00:00
marshal
9e21934983 Merge pull request #1298 from Tria-plc/freight_feature/usermanagement
fix issue
2026-08-15 13:34:35 +03:00
Marshal
b22f8c838e fix issue 2026-08-15 10:34:05 +00:00
marshal
fa7f6e507a Merge pull request #1297 from Tria-plc/freight_feature/usermanagement
Freight feature/usermanagement
2026-08-15 13:17:37 +03:00
Marshal
ca9f631f6f Merge branch 'dev' of github.com:Tria-plc/edr-platform into freight_feature/usermanagement 2026-08-15 10:12:59 +00:00
Marshal
dc38e843a6 feat: full wagon cancel, leg board, wagon dates
feat(freight): editable train leg times, SL invoice payer
2026-08-15 10:12:37 +00:00
Marshal
c9c2d4dcb3 booking cancellation 2026-08-15 08:53:24 +00:00
Hagernesh Tadesse
a01a574bd3 Merge pull request #1296 from Tria-plc/eims-integration
Eims integration
2026-08-15 11:44:17 +03:00
Hagernesh
d40c340e1c feat(billing): 80mm thermal invoice layout (ADD-P001)
GET billing/invoices/:id/document?format=thermal renders a dedicated 80mm
receipt template (72mm printable, 4mm margins each side), not a CSS variant
of the A4 layout — the A4 CSS is absolutely-positioned/fixed-px, tuned for a
210mm page, and doesn't reflow at thermal width. No seal (not a thermal
convention, renders badly on 1-bit thermal heads); line items stack
(description, then qty x rate = amount) instead of a table, since a real
table leaves ~10-14 chars for description at this width.

PdfRenderService gains a thermal render path: full 80mm-width viewport,
content height measured via page.evaluate after settle (continuous-roll
receipts have no fixed page length), and a noFallback option — a Chromium
failure throws a clear error instead of silently degrading to the generic
A4/no-QR fallback, which would hand back a different document than what was
asked for. The frontend surfaces that as a toast pointing at the existing A4
download.

format is strictly validated (a4|thermal only, BadRequestException
otherwise), not silently coerced.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-15 08:38:35 +00:00
Hagernesh
47c2e82839 feat(freight-backoffice): EIMS cancel/receipt/memo actions in filing card
Adds cancel, sales/withholding receipt filing + listing + PDF download, and
credit/debit memo issuance to EimsFilingCard, each gated on its own
permission.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-15 08:38:35 +00:00
Hagernesh
8e70352407 feat(eims): printable receipt PDF with RRN and QR
eims-receipt-document.mapper.ts maps an EimsReceipt onto the shared
InvoiceDocumentModel layout, reading amounts back out of the stored request
body. Refuses to render anything not REGISTERED. GET
invoices/:id/eims/receipts/:receiptId/document, scoped to the invoice.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-15 08:38:35 +00:00
Hagernesh
d66cfe2328 feat(billing): issue credit/debit memos against registered invoices
POST billing/invoices/:id/memo files a MoR DEB/CRE memo by reusing
createInvoice unchanged. sourceId is the original invoice's own id, not its
source's — this structurally keeps memos out of findPayable/expirePayable/
billQuery's sourceId-keyed lookups regardless of status. Credit notes are
created settled; debit notes are created open/unpaid as a genuine new
receivable, not force-settled. memoIssue is granted to the chief position,
not the general finance role.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-15 08:38:35 +00:00
Hagernesh
87c51d7676 fix(eims): drain pre-reservation rejections in auto-submit sweep
A BadRequestException thrown before reserve() (config assertion, DEB/CRE
validation) left the invoice NOT_SUBMITTED with nothing persisted, so the
same row was retried every tick forever — a permanent head-of-line block on
every invoice behind it. Now marked FAILED, guarded by a fresh status
re-read so a reservation's own SUBMITTING/UNKNOWN/blocked state is never
clobbered.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-15 08:38:35 +00:00
Hagernesh
83d9265e85 Credit and Debt plus reason attribute inide register 2026-08-15 08:38:35 +00:00
Nathnael Wondisha
0adbc096a1 Merge pull request #1295 from Tria-plc/freight/nati-2
Freight/nati 2
2026-08-15 11:15:39 +03:00
Nathnael
69805315d9 feat: dev and staging bypass 2026-08-15 08:10:47 +00:00
Nathnael
6c4969f3fd feat(filters): migrate FleetCrudPages to the pill filter bar
Covers 6 pages at once: the shared FleetCrudPage<T> factory
(TrainMasterDataPage, WagonsCrudPage, ContainersCrudPage,
CargoesCrudPage, LocomotivesCrudPage) plus the standalone
WagonTypesCrudPage.

- FleetCrudPage gains an optional `statusOptions` prop; when passed it
  builds a Status enum FilterDef and swaps the old plain search Input
  for FilterBar + useFilters + applyClientFilters (client-bridge —
  these endpoints return bare arrays). Status option lists sourced
  from the actual entity/enum definitions, not guessed, and reused in
  each page's create/edit form Select instead of duplicating them.
  Column-header click-to-sort is untouched (separate mechanism).
- WagonTypesCrudPage (hand-rolled, not on the factory) gets a boolean
  Active/Inactive filter the same way, replacing its search-text hack
  that string-matched "active"/"inactive" against the query.
2026-08-15 07:51:31 +00:00
Nathnael
4b4ab21ea1 fix(filters): auto-apply radios, route popover bug, date operators, polish
- Radio-based filters (single-select enum, boolean) apply the instant
  an option is picked and close — no Apply click needed. Checkbox
  (multi-select) keeps the explicit Apply, since picking several is a
  multi-step gesture.
- Route filter's Select dropdowns portal separately by default, so a
  click landed as "outside" the outer pill popover and closed it
  before a pick registered — same class of bug DateBody had.
  `comboboxProps={{ withinPortal: false }}` fixes it.
- Route filter now pinned by default on both pages instead of behind
  More filters.
- Date filter: widened to before/between/after (repository already
  applies each bound independently) via a new `dateRangeParams`
  helper — the old positional `{v[0]:from, v[1]:to}` mapping silently
  put a "before" pick in the from param. Added a calendar icon + sm
  sizing, and a wider popover so the range presets sidebar has room.
- Pill's clear (X) button enlarged; search box border/text opacity
  strengthened to match the pill trigger restyle.
2026-08-15 07:31:19 +00:00
Nathnael
839dfdbae3 feat(filters): route filter, select-styled trigger, tune default pin set
- New "route" FilterType: RouteBody popover (searchable origin +
  destination selects, apply once both are picked), wired into
  ContractRequestsPage and BookingRequestsPage. Bookings already had
  server-side originYardId/destinationYardId; contracts gets both new
  (contract_routes is one-to-many, so origin/destination are separate
  EXISTS subqueries, not a join).
- Inactive FilterPill trigger restyled to read like a closed Mantine
  Select (opaque solid border, trailing chevron) instead of a dashed
  "+" pill — trigger only, popover body/position unchanged.
- Search box text set to regular weight.
- A couple more filters (Direction, Freight) pinned by default per
  page on top of the existing always-pinned ones; the rest stay behind
  More filters.
2026-08-15 07:23:43 +00:00
Nathnael
e5e75b4166 feat(filter-bar): migrate 3 more fleet pages, extract footer adapter
- ruleEngineFooterProps.ts: toRuleEngineFooterProps() — the
  useFilters-to-RuleEngineListFooter pagination adapter had already been
  hand-written twice (TrucksOnSitePage, CompliancePage) with the same
  pageSize-drop risk useFilters itself just had fixed; extracted before a
  third copy could drift.
- CompliancePage, FuelPurchasePage, IncidentsPage: ListControls ->
  FilterBar + applyClientFilters, same mechanical pattern as the
  warehouse pages (search + one date range, endpoints take no params at
  all per the inventory sweep — confirmed correct bucket, not assumed).
2026-08-14 14:13:11 +00:00
Nathnael
fa32fa6a96 feat(filter-bar): migrate BookingRequestsPage (richest Family A page)
13 useState fields -> 11 FilterDef entries + useFilters. Notable pieces:

- the deep-link-follow useEffect is gone entirely — controls.values reads
  live off the URL every render, so a link opened while the page is
  already mounted (the header document-review alarm) just works, and
  every filter auto-pins its own pill once it has a value (FilterBar's
  secondary/pinned split), so a deep link can never land behind "More
  filters" hidden. Confirmed the encoding stays back-compatible with the
  existing ?statuses=A,B&tradeDirection=IMPORT links via url.test.ts.
- "Paid, not allocated" (a checkbox that overrides the payment-status
  filter) became a boolean FilterDef with a custom toParams — ordered
  after paymentStatus in the defs array so its toParams wins on merge,
  matching the original override semantics exactly.
- booking kind and status stay pinned pills (always visible, matching the
  old primary row); direction/freight/payment/ownership/yards/dates are
  secondary (behind "More filters", matching the old advanced collapse).
2026-08-14 14:08:08 +00:00
Nathnael
d94c444a51 feat(filter-bar): fix pageSize bug, add client bridge, migrate 4 more pages
fix(useFilters): pageSize was a hardcoded constant, never read from the
URL, with no setter — the DataTable/RuleEngineListFooter page-size
dropdown silently did nothing on every page using the filter bar,
including the already-shipped ContractRequestsPage pilot. Added a `size`
URL param (mirrors `page`), `setPageSize`, and wired
`tableProps().onPaginationChange` to route page-size vs page-index
changes to the right setter instead of only ever calling setPage().

feat(clientFilter): applyClientFilters — the Family-B bridge the plan
called for. Generalizes ListControls'/useListControls' one hardcoded
search box + one date range to every FilterDef, matched against
`row[def.key]`. Reuses matchesDayRange/toDayString from
hooks/useListControls.ts (imported, not duplicated) so the inclusive-
range/timezone-safe semantics stay defined in exactly one place. Lets a
page ship the full pill-bar UI immediately and flip to server-side
filtering later by deleting one function call — no endpoint changes
required up front.

Migrated to the filter bar (mechanical, pattern established by
ContractRequestsPage):
- WarehouseInvoicesPage, LoadedInventoryPage, TrucksOnSitePage — Family B
  (ListControls/useListControls → FilterBar + applyClientFilters)
- CustomersPage — Family A (useState bag → useFilters), no filter pills
  needed here (search + sort only), the SegmentedControl "view" stays
  page-level tab state (like ContractStatusTabs), not a filter pill —
  it now resets the page via controls.setPage(1) on change, the same
  hazard useFilters already guards its own filters against
2026-08-14 14:03:33 +00:00
Nathnael
a15d4d922e Merge branch 'dev' into freight/nati-2 2026-08-14 13:52:23 +00:00
Nathnael
524f9aab56 fix(filter-bar): responsive layout broken on narrow viewports
The two-zone layout (left: filters, right: sort+save) hardcoded
flexWrap: "nowrap" on the outer container so the right zone would never
get pushed below the left on desktop. On a phone that's the same
two-column squeeze with no room for either side: the search box and
right-hand controls got clipped/overlapped instead of stacking.

- outer container: flex-col below the `sm` breakpoint (stacks to two full-
  width rows), flex-row + nowrap at `sm` and up (the original two-zone,
  right-pinned behavior)
- right zone swapped from <Group wrap="nowrap"> to a plain div with
  Tailwind classes — Group's `wrap` prop sets an inline flex-wrap style,
  which always wins over a `sm:flex-nowrap` class regardless of
  breakpoint, so the old responsive attempt could never have worked
- search input's hardcoded minWidth: 220 (wider than a lot of phones)
  dropped to a shrinkable 160
- saved-view card grid: base 2 columns forced text-clipping card overflow
  on a phone; base is now 1, growing to 2+ only once there's room
2026-08-14 13:51:48 +00:00
Nathnael
bec9575311 refactor(filter-bar): saved views as auto-named cards, not a name-prompt menu
Replace the modal "type a name to save" flow with a one-click Save that
auto-generates the label from the active filters (describeQuery, reusing
the same formatter FilterPill uses for pill text — one source of truth,
nothing to type, nothing to fall out of sync). Saved views render as a
grid of cards at the top of the filter bar instead of being buried in a
dropdown.

- useSavedViews: localStorage state, extracted so FilterBar owns one
  instance instead of two components each subscribing separately
- SaveViewButton: filled (not outline) since it's the one action-y
  control among a row of filter pills; flashes to a disabled "Saved" +
  checkmark state for 1.5s on click, in addition to the corner toast —
  the toast alone wasn't registering as feedback
- SavedViewCards: click to apply, per-card delete, active one highlighted
- moved from the wrapping filter-pill row into the pinned right-hand zone
  (with sort) behind its own divider, so it can't shove pills around
- Save icon: BookmarkPlus -> Save (floppy disk reads as "save" faster)
- More filters button: outline/gray -> default variant — same low-
  contrast-outline problem the inactive filter pill had
- Clear button font bumped xs -> sm to match the rest of the bar
2026-08-14 13:48:54 +00:00
Abubeker Yasin
086c4fe8c9 Merge pull request #1293 from Tria-plc/alpha
fix: ( portal ) opt out of browser translation to stop booking crash
2026-08-14 16:41:35 +03:00
Hagernesh Tadesse
3caf5f66e9 Merge pull request #1292 from Tria-plc/dev
Empty Container wagon assignment and return handled
2026-08-14 16:38:37 +03:00
Abubeker Yasin
d952792e15 fix: ( portal ) opt out of browser translation to stop booking crash 2026-08-14 16:37:51 +03: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 Tadesse
60f0824fd3 Merge pull request #1291 from Tria-plc/eims-integration
feat(train-scheduling): show loaded empties on the marshalling document
2026-08-14 14:39:22 +03:00
Hagernesh
1adc19baef feat(train-scheduling): show loaded empties on the marshalling document
Empty containers loaded onto an export departure carry no booking and no
wagon allocation, so their wagons printed as EMPTY — no cargo allocated —
staff checking the paper against the consist found boxes the list denied.
Those wagons now print the container numbers with cargo type EMPTY
CONTAINER, count toward the 40ft/20ft tallies, and get their own summary
tile; only genuinely bare wagons keep the empty wording.
2026-08-14 11:30:25 +00:00
Hagernesh Tadesse
4a836428c0 Merge pull request #1290 from Tria-plc/eims-integration
feat(train-scheduling): load returned empties onto an export train
2026-08-14 14:18:31 +03:00