Commit Graph

4991 Commits

Author SHA1 Message Date
marshal
c1150f8af2 Merge pull request #1337 from Tria-plc/freight_feature/usermanagement
Freight feature/usermanagement
2026-08-18 16:19:34 +03:00
Abubeker Yasin
861900dc1b Merge pull request #1338 from Tria-plc/alpha
feat: ( audit ) resolve the actor from the session and audit all back…
2026-08-18 16:19:15 +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
5e96394450 Merge pull request #1336 from Tria-plc/freight/nati-2
lifecycle
2026-08-18 16:14:38 +03:00
Nathnael
dd6df306f2 lifecycle 2026-08-18 13:11:48 +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
Nathnael Wondisha
bd5d6d72b5 Merge pull request #1334 from Tria-plc/freight/feat/foreign-investors
fix: approval window
2026-08-18 15:51:45 +03: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
Nathnael
cbcc9a02e6 fix: approval window 2026-08-18 12:44:02 +00:00
Abubeker Yasin
0eb64ad9a1 feat: ( audit ) resolve the actor from the session and audit all backoffice mutations 2026-08-18 15:13:22 +03:00
Hagernesh Tadesse
ed317f870a Merge pull request #1333 from Tria-plc/eims-integration
feat(eims): alias bare Addis Ababa sub-city names to the CSV's woreda…
2026-08-18 14:58:20 +03:00
Hagernesh
f7be33eaaf feat(eims): alias bare Addis Ababa sub-city names to the CSV's woreda codes
Confirmed live 2026-08-17: buyer profiles store just the sub-city name
("Bole", "Arada") as their woreda, never the source CSV's specific
example-woreda name ("Bole Woreda 01") — hit for three different
buyers in a row (Bole, Arada, Lemi Kura) before any got past this
check. Since the CSV lists exactly one representative woreda per
Addis sub-city, alias the bare name to that same code instead of
waiting on a fuller table — use what's already been supplied first.

Lemi Kura itself isn't in the source CSV at all (one of Addis
Ababa's newer sub-cities) — still needs its real code from MoR.
2026-08-18 11:51:03 +00:00
Nathnael Wondisha
b95952d67a Merge pull request #1332 from Tria-plc/freight/feat/foreign-investors
Freight/feat/foreign investors
2026-08-18 14:49:28 +03:00
Nathnael
f3e64681c2 test(e2e): wait for rehydration before retyping the TIN after a switch
The switch clears the registration but not the tax number, so the company step
reopens with the old TIN and RHF re-seeds the field when the profile refetch
lands. A TIN typed before that arrived was silently replaced by the stored one
and the lookup ran against the wrong number.

The spec now waits for the rehydrated VAT value, then asserts the field holds
what it typed before waiting on eTrade.
2026-08-18 11:47:18 +00:00
Nathnael
f62054d4a4 fix(portal): accept a business licence the server already holds
A resumed onboarding asked for the per-role business licence again and refused
to submit until it was uploaded a second time, however many were already on
file. `getInfo` never populates `companyProfiles[].licenseFiles`, and the
wizard validates against exactly that — so every profile looked empty, while
`markOnboardingComplete` would have accepted the application as it stood.

Validation now reads `licenseProfiles[].uploaded` from the onboarding
requirements: the server's own verdict, already fetched by the step, and the
same source the API enforces on submit.

Reachable today by anyone who resumes onboarding after a licence upload, and by
every company that switches back to eTrade registration — which is where the
e2e suite hit it.
2026-08-18 11:47:18 +00:00
Nathnael Wondisha
d3f6e03749 Merge pull request #1331 from Tria-plc/freight/nati-2
feat: add service type fitler
2026-08-18 14:38:43 +03:00
Nathnael
6469c7fa52 test(e2e): cover every onboarding route across portal and backoffice
Five journeys, one file each, every one of them crossing from the portal into
the backoffice and cross-checking the database rather than the screen:

- ethiopian     eTrade verified, Fayda, approved, contract wizard reachable —
                including the dead end a TIN with no trade licence is for an
                ordinary company
- investor      foreign investment licence: nothing on file at eTrade, typed
                registration, passport identity, per-role licence still owed,
                and the backoffice's manual-entry badge and banner
- cooperative   no foreign option, no freight-forwarder role, the co-operative
                document set, no licence cards, its own badge and banner
- switch_back   settings → switch to eTrade → registration cleared, company
                pending, wizard reopened on the company step → re-run through
                eTrade → the flag is gone from the backoffice
- guards        the refused combinations, and the mid-wizard un-tick that has
                to clear the typed registration

Replaces the old onboarding.cy.ts (removed a commit earlier by accident of a
staged deletion): it drove a wizard shape that no longer exists — Fayda before
the company step, a "Personnel" step — so it could only ever have been red.

Notes for whoever edits these next. Attach files to the FIRST empty dropzone,
never by index — SmartFileInput removes the input once a file is on it. Resolve
the company from the database after any cross-origin hop, never from module
state: Cypress re-evaluates the spec bundle and Date.now() with it, which is
what latestJourney's run-stamp cutoff is for. And the deliberate eTrade 400 is
ignored as an uncaught exception — the portal handles that outcome on screen
but leaves the rejected request unhandled at the promise level.
2026-08-18 11:38:37 +00:00
Nathnael
03b16b4ee8 feat: add service type fitler 2026-08-18 11:38:07 +00:00
Nathnael
2f86557f61 test(e2e): teach the eTrade mock to answer "no record"
Every TIN resolved to the same canned company, so the premise of both manual
routes — eTrade holds nothing — could not be reached at all, and neither could
the dead end an ordinary company hits on a TIN with no trade licence.

The answer now follows the TIN's leading digit, so a spec picks its outcome by
picking its number and no per-spec stubbing is needed: 9… returns a
registration with no businesses (the co-operative / investor case, where the
API's resolveCompanyData finds no licence), 8… 404s the registration lookup
entirely, anything else behaves as before.
2026-08-18 11:37:58 +00:00
Nathnael
0608f8a7ea fix(portal): bring the settings tabs in line with the wizard's rules
The Company, Owner, Contact and Power of Attorney tabs had drifted from the
onboarding wizard while the manual-registration routes were added, and each
divergence locked somebody out of their own account:

- VAT was validated as 10-or-11 digits here and by onboarding's rule there. A
  foreign company's VAT is its own tax authority's and a co-operative's follows
  neither, so the stricter copy failed every save on the tab, not just the VAT
  field. The schema now reuses onboarding's, with a test pinning both rules.
- Fayda's phone claims are normalized on read, as the wizard does — the
  registry holds local numbers that neither the form nor the API's
  @IsValidPhone() accepts.
- The eTrade manager is shown read-only, because that record is exactly what
  the backoffice checks the company against; leaving it editable let a customer
  overwrite the value under review.
- A co-operative's per-role licence cards are hidden on the Documents tab. The
  API lifts the requirement, so the slots could never be filled or cleared.
2026-08-18 11:37:58 +00:00
Nathnael
431bd3c98b feat(companies): let a co-operative switch back to eTrade as well
Both manual-registration routes exist for one reason — eTrade holds no record
to fetch — so leaving one is the same act whichever it is. A co-operative that
has since taken out a trade licence had no way out at all: the flag is chosen
in the wizard, and an onboarded company can no longer reach it.

revert-to-etrade now accepts either flag and strips both, and a co-operative's
ACTIVE roles go back to Pending with it: those approvals were granted without a
per-role business licence, because a co-op owes none, and the moment it stops
being one that licence is due. Rejected, suspended and blacklisted roles are
left alone — promoting a blocked role to "awaiting approval" would launder a
staff decision away. An investor's roles are untouched; their licences were
always due and already reviewed.

Switching INTO a route stays with the wizard's nationality/role step, which
owns the mutually-exclusive rules; a second entry point would restate all three.
2026-08-18 11:37:43 +00:00
Nathnael
333232c4d9 fix(portal): persist the region a manual-registration company picks
The Region select called setValue without shouldDirty. `region` is an
eTrade-bundle key, and stepPayload sends those only when the customer changed
them this session — so for the two routes that type their address by hand (a
co-operative, a foreign investor) the region was dropped on every save while
zone, woreda and kebele went through, because those are registered inputs and
are dirty by construction.

Found by the new onboarding e2e suite: both manual-route companies finished
onboarding with zone/woreda/kebele on file and region empty.
2026-08-18 11:37:43 +00:00
Nathnael
0b8b9c39ab fix(companies): clear the typed registration when the manual-entry box is un-ticked
Going back in the wizard and un-ticking co-operative or investment licence used
to write the flag and nothing else. The registration the customer had typed
stayed on the company row, so `hasRegistrationDetails` still read as a passed
eTrade lookup, resume dropped them at their furthest step rather than the
company one, and the application could be finished on unverified data with no
flag left on it for the backoffice to show.

That transition now costs what the settings switch costs: the eTrade-sourced
columns and the manager captured beside them are cleared, and onboarding drops
back to the company step so the TIN actually goes through eTrade. Both the
reset payload and the attribute strip are now shared with
`revertToRegularCompany`, which did this correctly already.
2026-08-18 09:00:21 +00:00
Nathnael
0f11d9518f feat(backoffice): flag customers whose registration was typed, not fetched
Two kinds of customer reach approval with a registration nobody checked: a
co-operative union or farm, which holds no trade licence, and a foreign
investor, whose licence comes from the Investment Commission rather than the
trade registry. Both were reviewed on screens that read exactly like an
eTrade-verified company's, with only a small Registration field naming the
difference.

They now carry an orange "Manual entry" badge in the customers list and beside
the company name, and their overview opens with an alert saying the name,
registration and address below are the customer's own statement — pointing the
reviewer at the paper that stands in for the licence (the co-operative
certificate, or the investment licence) before approving. Approval itself is
not blocked.
2026-08-18 08:45:38 +00:00
Nathnael
23d752d1cc feat(portal): let an investor company switch back to eTrade registration
A company that ticked the investment-licence box by mistake, or that has since
been registered with the trade registry, had no way back — the flag is chosen
once, on a step onboarding never returns to.

The Company tab now carries a Registration source card for those companies.
It is a re-application rather than a settings edit, so the confirmation says
so outright: the typed registration is cleared, the company returns to pending
and the wizard reopens on the company step, while documents, owner and contact
details stay. Hidden for everyone else, and disabled while a profile change
request is under review — switching then would strand it.
2026-08-18 08:45:29 +00:00
Nathnael
d1584ee708 feat(portal): offer the investment-licence path in the onboarding wizard
A foreign company can now say it operates on an investment licence, on the
same step as its nationality and roles. The box only appears for a foreign
company, and moving the nationality answer back to Ethiopian drops it — the
API refuses both pairings.

The company step's eTrade gate now reads `manualRegistration`
(co-operative OR investment licence): the TIN lookup still runs, but finding
nothing is an expected outcome rather than a blocker, and the registration
section is typed instead. What stays keyed to `cooperative` alone is the
per-role business licence — an investor holds one, a co-operative does not —
so the licence cards and their validation are unchanged for investors.

Also carries the client plumbing for the revert endpoint the settings card
uses next.
2026-08-18 08:45:29 +00:00
Nathnael
a9763a541a feat(companies): support foreign investors onboarding on an investment licence
A foreign company licensed by the Ethiopian Investment Commission is not on
the trade registry, so eTrade holds no record for its TIN and the lookup the
company step is built around returns nothing. Those customers could not get
past onboarding at all.

They now take the same route a co-operative does: an `investorLicence` flag in
`attributes`, and `applyEtradeSourcedFields` skips the eTrade re-check for any
company `usesManualRegistration` covers, so the registration they type is
persisted as sent instead of 400'ing "no registration found for this TIN".
Unlike a co-operative they still hold a business licence per operational role,
so that requirement is untouched, and the foreign document set already asks for
the investment licence itself — no new set.

Only a foreign company may carry the flag, and never alongside the
co-operative one: the two resolve to different document sets.

`POST /companies/onboarding/revert-to-etrade` gives it back. It clears the
typed registration rather than keeping it — the wizard treats a populated
registration block as a passed lookup, so leaving it would walk the customer
straight past the eTrade step the switch exists to reach — and returns the
company to pending, since an approval granted against typed data must not
carry over to a record that now claims to be eTrade's.
2026-08-18 08:45:15 +00:00
Abubeker Yasin
83011a850a Merge pull request #1330 from Tria-plc/alpha
fix: ( backoffice ) accept phone or username in the login identifier …
2026-08-18 11:35:04 +03:00
marshal
1e67034968 Merge pull request #1329 from Tria-plc/freight_feature/usermanagement
feat(freight): offer built-train wagons per boarding yard on multi-ya…
2026-08-18 11:28:32 +03:00
Abubeker Yasin
33100a31ae fix: ( backoffice ) accept phone or username in the login identifier field 2026-08-18 11:28:10 +03:00
Marshal
c723b660e2 feat(freight): offer built-train wagons per boarding yard on multi-yard consists 2026-08-18 08:27:18 +00:00
Nathnael Wondisha
983cc02e50 Merge pull request #1328 from Tria-plc/freight/nati-2
fix: vite env issue
2026-08-18 10:32:05 +03:00
Nathnael
2f293e4ae4 fix: vite env issue 2026-08-18 07:28:42 +00:00
Abubeker Yasin
db319d25ce Merge pull request #1327 from Tria-plc/alpha
fix: ( supplementary-charges ) pay in the selected method's currency,…
2026-08-18 10:22:49 +03:00
Abubeker Yasin
a9eac93fa3 fix: ( supplementary-charges ) pay in the selected method's currency, add CAC Bank and CBE 2026-08-18 10:16:59 +03:00
Hagernesh Tadesse
8e78b335b2 Merge pull request #1326 from Tria-plc/eims-integration
Eims integration
2026-08-17 21:18:37 +03:00
Hagernesh
e67ccbb9cd fix(eims): stop sending our internal fee-basis tag as MoR's ItemList Unit
Confirmed live 2026-08-17 on INV-20260817-00008: MoR rejected the
document with a SCHEMA ERROR on ItemList[0].Unit — 'PER_CONTAINER'
(from the line's own metadata.unit) fails MoR's enum
(LTR/MTR/101/PCS/ROL/MTS/PKG/SET/KLG), its 8-char max, and its
^[A-Za-z]{3,8}$ regex all at once.

line.metadata.unit is our own fee-basis tag (PER_CONTAINER/PER_TON/
PER_ITEM — how a charge is computed) and was never a MoR unit of
measure; the mapper was reusing the same field name for two unrelated
concepts. Every line now sends the single configured
EIMS_UNIT_DEFAULT instead of guessing a per-line value that doesn't
exist in MoR's vocabulary.
2026-08-17 18:10:55 +00:00
Hagernesh
7d8ab932c2 feat(eims): implement POST /v1/bulkCancel
New endpoint: POST invoices/eims/bulk-cancel, body { items: [{invoiceId,
reasonCode, remark?}] }. Same eimsCancel permission as single cancel — a
batch-scale version of the same irreversible-at-MoR action, not a new
capability.

Same local-eligibility doctrine as single cancel: an already-cancelled or
never-registered invoice is refused right here, no HTTP call, before it
gets a seat in the batch. Only genuinely eligible invoices go into the one
/v1/bulkCancel request; every outcome (local refusal or MoR's own
per-IRN result) is reported back independently — one invoice failing
never blocks the rest.

MoR's bulk response mixes success and error shapes in the same array,
disambiguated by Status (capital, error) vs status (lowercase, success)
— matched back to our invoices by IRN. Notably the bulk success shape
carries no cancellationDate at all, unlike single cancel.

Left out of this pass: bulkRegister. It's async (returns only a
conversationId immediately, results arrive via a webhook callback we
don't have yet) and needs manual counter/previousIrn management per
the collection's own docs — a materially different reservation model
than today's single-invoice TX1/TX2 pattern. Scoping that is a
separate, bigger piece of work.
2026-08-17 17:49:33 +00:00
Hagernesh Tadesse
8ed5642544 Merge pull request #1325 from Tria-plc/eims-integration
feat(eims): bake in a starter table of Ethiopia region/zone/woreda codes
2026-08-17 20:18:03 +03:00
Hagernesh
7946c16327 feat(eims): bake in a starter table of Ethiopia region/zone/woreda codes
EIMS_BUYER_REGION_CODES/WEREDA_CODES/CITY_CODES were hand-maintained,
empty-by-default env vars — every buyer from a not-yet-seen area
crashed filing until someone found the MoR code and redeployed.
Happened three times in one afternoon (2026-08-17): Somali region,
Fafan zone, Jigjiga woreda, even the Ethiopia country code were all
unset on the triaplc.com deployment.

Ethiopia's administrative divisions are fixed reference data, not
buyer-specific config. Added ethiopia-geo-codes.ts, a static table
(source: ethiopia_administrative_hierarchy_master.csv, supplied
2026-08-17 — a representative sample, not all ~1000 real woredas)
merged in as the fallback under the existing env vars, which still
win on a name collision — for a quick correction without a redeploy,
or a buyer profile spelled differently than the table (already hit:
DB has zone "Fafen", official spelling "Fafan").

Known limitation, documented in the file: zone/woreda names aren't
always unique nationwide (e.g. "North Shewa" is both an Amhara and
an Oromia zone) and Company stores region/zone/woreda as three
independent strings with no parent linkage, so a flat name map can't
always disambiguate. Only affects the optional City field — never
blocks filing, unlike Region/Wereda.
2026-08-17 14:13:16 +00:00
Nathnael Wondisha
b73939a768 Merge pull request #1324 from Tria-plc/freight/feat/element-chat
Freight/feat/element chat
2026-08-17 15:55:37 +03:00
Nathnael
7c6c894c78 Merge branch 'freight/nati-2' into freight/feat/element-chat 2026-08-17 12:53:39 +00:00
Nathnael
ab734aecc3 feat(chat): join users to rooms on sign-in 2026-08-17 12:53:08 +00:00
Nathnael Wondisha
ab2173e026 Merge pull request #1323 from Tria-plc/freight/nati-2
Freight/nati 2
2026-08-17 15:45:27 +03:00
Nathnael
7aa4405423 Merge branch 'dev' into freight/nati-2
Conflict in ClearanceDocumentsPage: this branch migrated the page to the
pill FilterBar, dev added filters to the Select stack it replaced. Kept
the FilterBar and carried dev's additions across as a "Booked by"
(customerKind) FilterDef plus the shipping-line search placeholder; dev's
startOfDayIso/endOfDayIso went away because dateRangeParams already does
that. The Ship icon import is needed by dev's shipping-line customer cell,
which merged cleanly on its own.
2026-08-17 12:43:35 +00:00
Nathnael
7143ba1040 feat(customers): notify marketing on returned changes, name actors in history
Three gaps on the backoffice customer detail page:

- Rejecting a change request or sending it back for correction notified
  nobody. Adds CompanyNotifierService.changeRequestReturned, which pings
  the customer desk with the reviewer, the outcome and the note. Marketing
  joins that desk via customers:view + customers:get_notification in the
  role preset — grants still come from the IAM UI, the preset only sets
  the default for new environments.
- submitted_by / reviewed_by / actor_id were stored but never resolved, so
  the History tab could say what changed but never who asked or who sent
  it back. Resolves them through a shared iam-user-name util (deduped from
  the private copy in contract-document-history.service) and renders
  "Requested by" / "Sent back to marketing by" lines. The
  changes_requested badge is relabelled to match the workflow.
- "View" opened an in-page modal one document at a time. Adds
  openFileInNewTab, which opens the tab inside the click gesture and fills
  it once the authenticated fetch resolves, and an "Open all" button that
  loops over the documents table so every file lands in its own tab.
2026-08-17 12:38:06 +00:00
Abubeker Yasin
e8e136193d Merge pull request #1322 from Tria-plc/alpha
fix: ( excess-baggage ) pay in the selected method's currency and rec…
2026-08-17 15:36:37 +03:00
Abubeker Yasin
fa16087a4a fix: ( excess-baggage ) pay in the selected method's currency and record settlement 2026-08-17 15:33:43 +03:00
Hagernesh Tadesse
9eb2dc6649 Merge pull request #1321 from Tria-plc/eims-integration
Eims integration
2026-08-17 15:12:43 +03:00