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.
Eims integration
feat(eims): register receipts, cancellations, and QR-verified invoices with MoR EIMS
Extends EIMS integration from invoice registration to the full document
set: sales/withholding receipt registration, registration cancellation,
and a signed verification QR embedded on the printed invoice/receipt
(new eims_signed_qr column, widened IRN/previous-IRN columns for the
longer cancelled-doc format). Invoice document renderer and its
Chromium-less fallback both carry the QR now; seal rendering goes
through the shared seal-markup util instead of duplicating it inline.
Also closes a wagon-loading gap unrelated to EIMS: re-check the
1×40ft/2×20ft-per-wagon TEU packing rule at loading-confirmation time,
not just at allocation time, so a later wagon swap or edited allocation
can't sneak a wagon over capacity undetected.
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>