Commit Graph

56 Commits

Author SHA1 Message Date
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
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
7c6c894c78 Merge branch 'freight/nati-2' into freight/feat/element-chat 2026-08-17 12:53:39 +00:00
Hagernesh
13f7bea590 feat(eims): accept private key/cert as raw PEM env vars
EIMS_PRIVATE_KEY / EIMS_CERTIFICATE — the PEM text pasted directly,
no encode/decode step at all. Precedence: raw PEM > base64 > path.

Motivated by the base64 path hitting a DECODER::unsupported error in
a live deployment with no way to tell whether the cause was transport
truncation, double-encoding, or an actually-bad file. Two fixes for
that class of problem together:
  - the raw-PEM var removes the encode/decode step entirely, so
    there's nothing left to corrupt in transit
  - a literal \\n (two chars) is unescaped to a real newline, for
    env stores that can't hold a literal line break
  - getPrivateKey() now checks the decoded bytes look like a PEM
    header before handing them to OpenSSL, so a still-bad value fails
    with byte count + safe preview instead of an opaque decoder error
2026-08-17 11:03:22 +00:00
Hagernesh
4a4b1981cb feat(eims): allow private key/cert as inline base64 env vars
EIMS_PRIVATE_KEY_BASE64 / EIMS_CERTIFICATE_BASE64, alternative to the
existing _PATH vars. Wins over the path when set; falls back to the
file otherwise. Neither var required at boot on its own — the
either/or check moved out of the flat REQUIRED_VARS list.

Lets a dockerized deployment receive the key/cert the same way it
already receives every other EIMS_* secret (plain env var into the
container) instead of needing a host bind mount into the container
filesystem.
2026-08-17 09:14:38 +00:00
Nathnael
00bd1250ee feat: WIP element Chat intergration 2026-08-17 08:02:00 +00:00
Hagernesh
09bc7c74d7 feat(eims): derive buyer City/Country from company profile, not global config
City: EimsMapperContext.buyerCity was declared but never wired anywhere —
always null, silently, for every buyer. No dedicated city column on Company;
derives from Zone via a new EIMS_BUYER_CITY_CODES map, same lookup mechanism
as Region/Wereda but optional (an unmapped zone resolves to null rather than
throwing) — MoR has already accepted a live filing with City null.

Country: previously a single flat EIMS_BUYER_COUNTRY_CODE applied to every
buyer regardless of Company.country. Now reads company.country, resolved via
a new EIMS_BUYER_COUNTRY_CODES name-to-code map; the flat env var becomes a
domestic-only fallback (applies only when country is empty/Ethiopia), so an
unmapped foreign buyer fails locally instead of silently filing as Ethiopia.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-17 07:43:04 +00:00
Hagernesh
2d4da8110b eims integration master test complete 2026-08-12 14:12:23 +00:00
marshalyordanos
35e5404b41 Refactor code structure for improved readability and maintainability 2026-08-11 19:36:00 +03:00
Hagernesh
8d3dfa4113 fix(eims): map buyer Wereda to a MoR code too, fail locally if unmapped
BuyerDetails.Wereda had the same problem Region did: companies.woreda holds
names ("Yeka") MoR has no confirmed regex for, but every Wereda value MoR has
actually shown us (seller "12"/"13", the collection's "574") is 1-3 digits
like Region. Precautionary, not confirmed -- but the fix is identical either
way: resolve through EIMS_BUYER_WEREDA_CODES and refuse to file rather than
send a guessed code.

Generalises the Region resolver (resolveRegionCode -> resolveLocationCode) to
cover both fields instead of duplicating it.

No code was invented for "Yeka" -- EIMS_BUYER_WEREDA_CODES ships empty, so
this buyer now fails locally (new stop) instead of silently sending a name
that was never verified against MoR's schema.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-08 07:51:41 +00:00
Hagernesh
aec4f3d654 fix(eims): match MoR's payload rules found by live rejections
Three live attempts turned six guesses into facts. Each fix below is the
gateway's own words, not a reading of the collection.

DocumentNumber and InvoiceCounter move differently, because MoR constrains
them differently. The counter must not skip -- "Invoice counter is not
correct. expected : 1" -- so a definitively refused document hands it back.
The document number must not repeat, so the attempt burns it. Both stay spent
after an ambiguous result, where MoR may have stored the document.

NatureOfSupplies is normalised to MoR's exact lowercase constant and rejected
outright if it is neither 'goods' nor 'service'; its schema branches on this
as a oneOf, so "Service" invalidated the whole ItemList.

Buyer region resolves through a name->code map and now FAILS locally when
unmapped. MoR validates Region against ^[0-9]{1,3}$ on both the seller and
buyer sides, so a name can never be sent and a guessed code on a tax document
is worse than refusing to file.

Seller phone, email, region and wereda are checked against MoR's own regexes
before anything is sent, so a placeholder like "_" fails locally instead of
costing a request and a counter.

EIMS_TAX_CODE stays required and unset in .env.example: the choice between
VAT0 (zero-rated) and VATEX (exempt) is a tax position awaiting finance, and
MoR's enum is recorded there for whoever decides.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-08 06:57:50 +00:00
Hagernesh
2e26936bf1 fix(eims): retimestamp the EIMS migration to 3330000000000
3300000000000 collided with BookingWagonCancellations after the rebase.
3320000000000 is also unavailable: BulkContractTemplates3320000000000 is
already recorded in freight.migrations on the shared dev database from a
branch not present in this checkout, so checking only src/migrations is not
sufficient.

3330000000000 is unique across src/migrations and greater than the current
maximum timestamp recorded in freight.migrations.

Rename the migration file and class. The migration has no explicit name field
and no other code references its previous identity.

Verify migration discovery through the actual runtime path:
scripts/migrate.js loads compiled dist/migrations/*.js migrations, while
application boot does not run migrations automatically. Confirm the renamed
migration is present in dist.

For controlled dev verification, remove its migration-history row and run
pnpm migration:run again. The migration is discovered and applied under
3330000000000; its idempotent DDL produces no schema changes where the EIMS
schema already exists.
2026-08-08 04:56:57 +00:00
Nathnael Wondisha
55e0c6fb0b Merge pull request #1168 from Tria-plc/freight/nati-2
fix: rm iam migration from migrate
2026-08-08 01:10:56 +03:00
Nathnael
d44c3f3a38 fix: rm iam migration from migrate 2026-08-07 22:10:05 +00:00
Hagernesh
67573d0835 feat(eims): file issued invoices on a cron sweep, off by default
Invoices are produced by the freight workflow rather than by a person, so the
production path for filing is a sweep, not the manual endpoint.

A @Cron picks the oldest never-submitted invoice and hands it to the existing
EimsInvoiceRegistrationService -- no registration logic is duplicated, and the
durable reservation still decides whether the submission may proceed. Sweeping
rather than hooking the eleven places an invoice can be created or issued keeps
the workflow untouched, puts the HTTP call outside the invoice transaction by
construction, and lets a crash or restart be picked up on the next tick.

invoices.eims_status is the queue; nothing new is persisted. Only NOT_SUBMITTED
is eligible: UNKNOWN is never retried automatically because the document may
already be filed, and FAILED waits for an explicit retry policy. The tick also
refuses to start while eims_system_state holds an in-flight submission or a
block, and only one invoice is filed per tick so a misconfiguration costs one
rejected document rather than a burst.

Requires both EIMS_ENABLED and EIMS_AUTO_SUBMIT; the second defaults to false
so authentication can be live long before filing is. Logs carry the invoice
number, status and IRN only.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-07 15:08:40 +00:00
Hagernesh
2e7ef40d9e feat(eims): take the source system from the access token
MoR stamps systemNumber and systemType into the access token it issues for
the authenticating credentials, which makes the token the authority on them.
Registration now reads both from there instead of from configuration, so the
SourceSystem block cannot drift from what the gateway believes we are.

EimsAuthService decodes the token payload after login, requires both claims
to be non-empty, and exposes them through getSessionContext(). The token is
decoded but never verified -- it is MoR's, signed with MoR's key -- and is
kept out of the log line, which names only the system it identified.

EIMS_SYSTEM_NUMBER and EIMS_SYSTEM_TYPE become optional expectations rather
than inputs: when set they are compared against the claims and a mismatch
fails fast, so neither side silently wins. Neither is required to register
any more.

Registration and manual resolution both resolve the session before touching
the state row, which is keyed by the system number: a login failure now
costs nothing because no counter has been reserved yet.

Test fixtures move to eims-test-fixtures.ts. They previously lived in
eims-auth.service.spec.ts, which made jest execute that suite again inside
every importing spec.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-07 15:08:40 +00:00
Hagernesh
7573019038 feat(eims): register invoices with MoR EIMS and persist the outcome
Add manual single-invoice registration, verification and reconciliation.
Nothing submits automatically; invoice creation is untouched.

Sequencing uses a durable reservation. The counter is consumed and the
holder recorded in a committed transaction before the request leaves the
process, and the HTTP call runs outside every transaction. A counter is
therefore never reused once an attempt begins, a crash mid-flight leaves the
reservation standing instead of inviting a blind resubmission, and an
ambiguous result blocks the whole system number rather than one invoice --
PreviousIrn is unknown, so any later document would chain to a stale IRN.

Deterministic rejections (400/406/401/403) mark the invoice FAILED and clear
the block. Timeouts and 5xx mark it UNKNOWN and keep it. Since /v1/verify
takes an IRN we never received in that case, POST :id/eims/resolve is the
exit: record the IRN confirmed in the MoR portal, or discard. A recorded IRN
is verified against the gateway first and refused unless EIMS reports it
against this invoice's document number.

Business and tax configuration is validated locally before anything is
locked, allocated or sent, so a missing tax code fails naming the exact
environment variables instead of at the gateway. No tax value is defaulted.

Filing gets its own permission (invoices:eims_register) rather than riding
on invoices:export -- registration is irreversible at MoR and must not
follow from the right to download a PDF.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-07 15:08:40 +00:00
Hagernesh
2644d5e52d feat(eims): add invoice mapper and signed EIMS transport
Map EDR invoices onto the MoR EIMS /v1/register document and add the
cryptographic transport needed to talk to core.mor.gov.et.

Mapper: DTOs mirror the supplied Postman collection section by section.
Tax is resolved per line via a caller-supplied resolver and throws when
unresolved -- the app models no tax at all (invoice.taxAmount is always 0,
invoice_lines and the rate catalogue carry no fiscal columns), so a
zero-rated default would assert a tax position the codebase cannot support.
Seller identity, document number, counters and previous IRN are passed in
explicitly; the mapper stays pure.

Transport: config, credential loading, RSA-SHA512 signing and /auth/login
with an in-memory token cache. Signing reproduces the process that produced
a working live token -- compact JSON of the inner request only, exact UTF-8
bytes, base64 signature, and base64 of the certificate file's exact bytes
with no parsing or re-encoding. Concurrent callers share one login via an
in-flight promise. Refresh is deliberately unimplemented: the collection
shows an unsigned refresh body but also ships unsigned examples of calls
that do require signing, so an expired token re-logs in instead.

Errors normalise to EimsApiException carrying only the gateway's own error
fields; secrets, signature, certificate and tokens never reach logs.
Key and certificate file patterns are gitignored.

Nothing calls EIMS automatically and no invoice entity, migration or UI is
touched.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-07 15:08:40 +00:00
Nathnael
595c165820 feat: implement audit logs 2026-08-05 14:17:00 +00:00
ghost2023
41f2fd8333 fix: extension ensure 2026-08-05 11:10:52 +03:00
ghost2023
321f3612ec refactor(freight-api): split migration histories between IAM and freight 2026-08-05 10:26:25 +03:00
Marshal
56697d8fc5 implement exchange settings management and fallback rate handling 2026-08-04 11:22:47 +00:00
Nathnael
784f6ac6f2 Merge branch 'dev' into update-freight-migrations 2026-08-01 10:59:41 +00:00
Nathnael
dfdb79301d chore(freight-api): upgrade api-common and iamapi-common packages 2026-08-01 10:50:40 +00:00
Nathnael
dcda8d7d37 feat(freight-api): drop fan claim from fayda verification
esignet userinfo carries no national id number. keep sub/name/email/
phone/address, remove fanClaims config and the hard-fail gate that
would've blocked every real verification.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-29 06:57:27 +00:00
Yonas Tewabe
36f8fe2a7e Merge branch 'dev' into update-freight-migrations 2026-07-24 14:04:11 +03:00
yonastewabe
16a7aebadd update migration to before deployment in freight api 2026-07-24 14:01:21 +03:00
Nathnael
6420c72e89 feat(auth): implement staff-triggered password-reset links 2026-07-20 11:20:50 +00:00
Marshal
87a95b37bf enhance train scheduling and booking management 2026-07-07 21:38:45 +00:00
natib21
b218179d72 :wqMerge branch 'freight/feature/first_mile_invoice' of github.com:Tria-plc/edr-platform into freight/feature/first_mile_invoice 2026-07-03 09:10:31 +00:00
natib21
e4b0c73c63 fix fayda 2026-07-03 09:10:05 +00:00
hagiye
5af45c2643 Customer Truck Assignment and Portal Delivary Approval 2026-07-02 15:54:16 +03:00
hagiye
129448e437 frist mile receive to warehouse and last mile truck arrival integration 2026-07-02 01:08:37 +03:00
Marshal
f51c015814 Enhance payment module 2026-06-30 03:13:18 +00:00
Marshal
9674c9394d Update logging configuration to use TYPEORM_LOGGING environment variable 2026-06-30 02:52:19 +00:00
Marshal
4a0085ef57 feat: refactor exchange rate handling by integrating new ExchangeService and removing CbeExchangeService 2026-06-20 09:22:21 +00:00
Hagernesh
270e39edd6 fix: post-merge cleanup after freight/develop merge
- Remove orphaned container publish route (service method dropped on develop)
- turbo.json concurrency as string "12" (parse + 11 persistent-task limit)
- database.config: synchronize false (migrations only; fixes weight_limit_rules boot crash)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 23:04:48 +00:00
marshal
0aed9824ae cac payemnt integration and cbe rate exchange webscrabing 2026-06-15 14:54:20 +03:00
marshal
484151d909 payments 2026-06-14 15:59:52 +03:00
marshal
52c0639c8f Merge branch 'freight/develop' of github.com:Tria-plc/edr-platform into freight_feature/payments 2026-06-14 02:27:56 +03:00
marshal
87b0ce6339 Replace wagon/locomotive readiness with yard tracking, assign unassigned bookings from origin-yard fleet, standardize rates on USD with CBE ETB conversion, and update fleet/scheduling UI 2026-06-14 02:13:08 +03:00
yaschalew
12b6406d23 fix conflict 2026-06-13 10:45:26 +03:00
marshal
10b011de8f merge conflict 2026-06-10 16:45:45 +03:00
marshal
5774d7db9d booking operations and trains scheduling also allocations 2026-06-10 00:48:32 +03:00
Eyosiyas Girma
7c5e7408b0 Merge branch 'freight/develop' into freight/feature/telebirr-integration 2026-06-08 16:35:18 +03:00
Eyosiyas
3a66242f8b working on dmoney 2026-06-08 16:29:20 +03:00
Eyosiyas
a1940c4901 telebirr out in the payment 2026-06-08 11:49:50 +03:00
ghost2023
4994e19d57 signup update 2026-06-06 11:45:53 +03:00
Michael Abebe
a39366867a fix(freight): iam module import fix 2026-06-06 10:43:33 +03:00
Abubeker Yasin
1d11cbda4d feat: ( payment ) create payment microservice 2026-06-05 21:29:55 +03:00