booking-status-breakdown (dedupes the same 'status per port/train/
cargo/contract' ask across 4 dashboards), train-schedule-status,
train-turnaround, wagon-teu-utilization, loaded-capacity,
global-logistics-wagons.
Dropped freight-weight-variance from this batch: the schema has no
'charged weight' distinct from VGM/actual, so a charged-vs-actual
variance report isn't buildable without a product decision on what
'charged' means here.
wagon-fleet-status, wagon-status-duration, wagon-requests,
locomotive-fleet-status. First batch off the ITLMS dashboard spec —
fleet data (wagons/locomotives/transfer-requests) needed no schema
work, just resolvers. No frontend changes: catalog is server-driven.
Sorting by a column with no explicit sortExpr fell back to the bare
select alias unquoted. Postgres folds unquoted identifiers to lowercase,
so any camelCase alias (utilizationPct, bookedTons) 42703'd. Quote the
fallback to match the case TypeORM's addSelect actually emitted.
Nuke the 17 hand-written raw-SQL reports (no pagination, hard LIMITs) and
the reports module built around them. Replace with a resolver contract:
a report declares columns/filters/permission and a TypeORM QueryBuilder;
ReportRunnerService applies filtering, a whitelisted sort, offset/limit
paging, and a COUNT(*) FROM (query) wrapper for the total (getCount() is
wrong for GROUP BY). ReportExportService re-runs the same resolver
unpaginated for xlsx (exceljs) and pdf (existing PdfRenderService, now
landscape-capable) exports.
Ships with 4 reports: bookings-list, revenue-by-customer,
aging-receivables, contract-utilization. Catalog + per-report permission
checks live in the controller; adding a report is one new definitions/
file plus a REPORT_KEYS entry, no frontend change.
Each report key (REPORT_KEYS) mints its own edr_freight_app:reports:<key>:view
permission, derived the same way rule-engine resource permissions are.
reports:view stays the section master key; STAFF_DASHBOARD_KEYS grants every
report key alongside it so existing presets don't regress on reseed.
- 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.
Cross-checked our RSA-SHA512 signing and raw-bytes certificate encoding
against MoR's own "Guide to Generating and Using Certificate for E-Invoicing"
(supplied today). Both were previously documented as our best inference from
the Postman collection; the guide names SHA512withRSA explicitly (PKCS#1v1.5,
matching Node's createSign default) and its own worked example certificate is
byte-for-byte the same Subject:/Issuer: + 3-cert PEM chain text-file format
ours is. No behavior change -- the comment now says confirmed, not assumed.
Field order, section names, date format and the {request, signature,
certificate} envelope in the guide's worked example all match our mapper
exactly (order doesn't matter per the guide, but it's a further concordance
check). The one guide/live disagreement -- its example shows "NatureOfSupplies":
"Goods" where our actual 400 SCHEMA ERROR demanded lowercase "goods"/"service"
-- is left as-is: the live, machine-generated schema error outranks a static
doc example that may predate a schema change.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@WebSocketServer() only wires `server` once the WS adapter attaches to a
running HTTP listener. It never does under NestFactory.createApplicationContext
(scripts, one-off jobs) -- confirmed live tonight, when the EIMS self-test
registration's failure alert crashed with "Cannot read properties of null
(reading 'to')" instead of just logging that no socket was available.
The registration result itself was unaffected (postSigned already resolved,
the EimsApiException was correctly re-thrown), but the crash happened inside
an await'd call in the same chain -- in a context where it wasn't caught, it
would have masked whatever result the caller actually cared about.
Both push methods now skip and log at debug level when no server is attached,
since the notification row is already persisted by the time they're called --
a missing socket just means "no live push this time", not a reason to lose
the caller's own outcome. `server` drops its `!` non-null assertion to match.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Emit the request line as raw JSON on stdout (level/time/logger as fields)
instead of through Nest's console logger, whose prefix made it unparsable.
Collect data points via logCtx at the flow chokepoints: BaseRepository
writes (status changes, creates, deletes), invoice transitions, payment
intent lifecycle + outbound payment-service calls, booking/contract entry
state, review-note reasons, signatures and OTP verify outcomes.
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>
- 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.
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.
SourcedField rendered read-only whenever a value existed, so the input a
customer had just typed into turned into a badge as soon as the step saved and
they navigated back — and dropped out of requiredKeys at the same time. It now
locks on ownership instead: a Fayda verification owns what its claims filled,
everything else stays an editable, prefilled input.
The representation step follows the same rule and asks in the right order:
- The power-of-attorney question collapses to its answer once given, with a
button back to it (none for a freight forwarder, whose answer is forced).
- A foreign company picks how to prove the person outright — Fayda or a
passport — rather than being shown both at once.
- The representative's own fields appear only once the person is established,
and only for what the verification did not supply; what it did supply is
already on the panel above and is no longer repeated beneath it.
- Dropped the freight-forwarder lecture and the DARS blurb; the badge and the
upload field's own help text already say both.
VAT numbers accept any non-blank value. A foreign tax authority's carries
letters and dashes and a co-operative's follows neither pattern, so the 10-11
digit rule only ever rejected numbers we had no business judging.
confirmScheduleLoading's assertExportBookingsReceived checked every
wagon-assigned export booking for a warehouse GRN, with no exception for
DIRECT_TO_TRAIN (manual truck-straight-to-wagon) bookings — the one
call site that lacked the carve-out already applied everywhere else GRN
is checked (export-received-gate, booking-journey, carriage acceptance).
Warehouse-routed export cargo still requires GRN; direct handovers rely
on the carriage acceptance sheet instead.
fayda-identity covers the two subjects, the passport alternative and the
eTrade owner comparison in place of ~40 general-manager assertions.
poa-delegation drops the poaSameAsOwner waiver case and asserts the opposite:
a forwarder is refused without a delegation letter however it represents
itself.
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.
Replaces the owner/general-manager/PoA trio with a single identity whose
subject is the PoA when the company declares one and the owner otherwise.
- Drop the general manager everywhere: entity columns, DTOs, required-field
list, self-service attributes, gm* identity handling.
- New explicit poaDeclared answer ("yes"/"no") replaces poaSameAsOwner. A
DARS delegation letter is required iff it is "yes"; a freight forwarder is
forced to "yes" server-side and gets no waiver.
- Owner name/email/phone become typeable and required, prefilled from the
eTrade lookup, never falling back to the authenticated account.
- Store eTrade's manager separately (etradeManagerName/Phone) and expose
ownerMatchesEtrade so backoffice compares the asserted owner against the
licence instead of against itself.
- Foreign companies satisfy the identity with Fayda or a passport number, on
whichever subject is verifying (poaPassportNumber added).
- Write companies.email/phone from the owner unconditionally, so a company
without a Fayda-verified owner still has a notification address.
The director position held no warehouse permissions at all. It now
carries the same warehouse set as the chief tier: dashboard, warehouse
/yard/zone CRUD, allocation and fee rule CRUD, the inventory operation
set, inspection reports, interchange documents and fee invoices.
Unlike the dispatcher, the director owns the allocation and fee rules
themselves. The positions seeder backfills existing environments on
boot.
feat(train-builder): update permissions checks for train actions in UI
feat(contracts): enhance contract view and shipment request pages with new features
test(shipment-preview): add tests for customs clearing logic in booking preview
style(contract-sign-bar): create CSS for fixed sign bar layout