50 lines
2.8 KiB
Markdown
50 lines
2.8 KiB
Markdown
# F-153 — Tech Spec
|
|
|
|
## Principles
|
|
- Associate the customer email to the order **read model** (not a per-request hack):
|
|
`OrderView.email` is resolved once by the orders repository via a `LEFT JOIN identity_users`.
|
|
- Reuse the existing SQL pattern: `orders/infrastructure/pg-order-repository.ts` `search`
|
|
already does `LEFT 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_users` is referenced only as a **SQL table name** (pre-existing in
|
|
`search`); no TypeScript import crosses the identity/orders boundary. `identity` does not
|
|
import orders; orders references `identity_users` table name (string) at infrastructure.
|
|
- NoUncheckedIndexedAccess is ON → index access returns `T | undefined`; use `!` or `?? null`
|
|
when mapping rows.
|
|
- Backward compatible: `email` is 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`: add `email: string | null;` to `OrderView` (`Order` itself 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 | null` to `OrderRow`.
|
|
- Add helper `toOrderView(row, items): OrderView = { ...toOrder(row), email: row.email, items }`.
|
|
- `SELECT o.*` → `SELECT o.*, u.email` with `LEFT JOIN identity_users u ON u.id = o.user_id`
|
|
in `findById`, `findByIdAndUserId`, `findAll`, `search`.
|
|
- Build every returned `OrderView` via `toOrderView` (so `email` is always set; `null` when the
|
|
order has no linked identity_user).
|
|
|
|
### 3. API — expose + consume email
|
|
- `orders/api/orders.routes.ts`:
|
|
- `serializeOrder`: add `email: string | null` to the param type and to the output
|
|
(`email: order.email`).
|
|
- Admin force-transition (`POST /orders/:id/transitions/admin`): replace the inline
|
|
`SELECT email FROM identity_users WHERE id = order.userId` with `const 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 when `order.email` is null).
|
|
|
|
## Testing
|
|
- New `orders/infrastructure/pg-order-repository.test.ts`: mock `pg.Pool`, assert `findById`
|
|
returns `email` from the JOIN when the linked identity_user has one, and `null` when there is
|
|
no linked user.
|
|
- Update existing `OrderView` literals in tests (`order-service`, `payments-service`,
|
|
`checkout-service`) to include `email: 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.
|