2.8 KiB
2.8 KiB
F-153 — Tech Spec
Principles
- Associate the customer email to the order read model (not a per-request hack):
OrderView.emailis resolved once by the orders repository via aLEFT JOIN identity_users. - Reuse the existing SQL pattern:
orders/infrastructure/pg-order-repository.tssearchalready doesLEFT JOIN identity_users u ON u.id = o.user_id— F-153 extends that to every order read so the email is always available on the view. - Boundaries:
identity_usersis referenced only as a SQL table name (pre-existing insearch); no TypeScript import crosses the identity/orders boundary.identitydoes not import orders; orders referencesidentity_userstable name (string) at infrastructure. - NoUncheckedIndexedAccess is ON → index access returns
T | undefined; use!or?? nullwhen mapping rows. - Backward compatible:
emailis an additive field on the serialized output; no state transition, no new migration, no endpoint change.
Changes
1. Domain — OrderView carries email
orders/domain/order.ts: addemail: string | null;toOrderView(Orderitself unchanged — email is a denormalized read-model attribute, not a core domain field).
2. Infrastructure — resolve email in the repo
orders/infrastructure/pg-order-repository.ts:- Add
email: string | nulltoOrderRow. - Add helper
toOrderView(row, items): OrderView = { ...toOrder(row), email: row.email, items }. SELECT o.*→SELECT o.*, u.emailwithLEFT JOIN identity_users u ON u.id = o.user_idinfindById,findByIdAndUserId,findAll,search.- Build every returned
OrderViewviatoOrderView(soemailis always set;nullwhen the order has no linked identity_user).
- Add
3. API — expose + consume email
orders/api/orders.routes.ts:serializeOrder: addemail: string | nullto the param type and to the output (email: order.email).- Admin force-transition (
POST /orders/:id/transitions/admin): replace the inlineSELECT email FROM identity_users WHERE id = order.userIdwithconst to = order.email;(single source of truth; the LEFT JOIN already resolved it). Keep the try/catch + warn and the "El cliente no tiene email asociado" fallback (now only whenorder.emailis null).
Testing
- New
orders/infrastructure/pg-order-repository.test.ts: mockpg.Pool, assertfindByIdreturnsemailfrom the JOIN when the linked identity_user has one, andnullwhen there is no linked user. - Update existing
OrderViewliterals in tests (order-service,payments-service,checkout-service) to includeemail: null(additive field). order-status-mailer.test.ts(existing) remains green (no change to email senders).- tsc --noEmit clean; prettier + eslint clean; lint:boundaries no new violations; vitest run full suite green; verify.sh green.