57 lines
3.3 KiB
Markdown
57 lines
3.3 KiB
Markdown
# F-153 — Implementer Evidence
|
|
|
|
## Build
|
|
Feature: F-153 — Orders: associate the linked customer's email to the order read
|
|
model and display it (customer email was missing despite the user having an email).
|
|
|
|
### Changes (production)
|
|
- `src/modules/orders/domain/order.ts` — added `email?: string | null` to `OrderView`
|
|
(read model). `Order` core aggregate is untouched (email is a denormalized read attr).
|
|
- `src/modules/orders/infrastructure/pg-order-repository.ts`:
|
|
- added `email: string | null` to `OrderRow`;
|
|
- added `toOrderView(row, items)` helper;
|
|
- `findById`, `findByIdAndUserId`, `findAll`, `search` now `SELECT o.*, u.email` with
|
|
`LEFT JOIN identity_users u ON u.id = o.user_id` (reuses the existing `search` join pattern);
|
|
- every `OrderView` is built via `toOrderView`, so `email` is always populated
|
|
(`string | null`; `null` when the order has no linked identity_user).
|
|
- `src/modules/orders/api/orders.routes.ts`:
|
|
- `serializeOrder` now emits `email: order.email ?? null` (detail + list);
|
|
- admin force-transition (`POST /orders/:id/transitions/admin`) consumes
|
|
`order.email` (the associated email) and no longer issues a separate inline
|
|
`SELECT email FROM identity_users` lookup → no N+1, single source of truth.
|
|
|
|
### Changes (tests)
|
|
- new `src/modules/orders/infrastructure/pg-order-repository.test.ts`:
|
|
- `findById` resolves `email` via the `identity_users` join (asserts the query contains
|
|
`identity_users` + `u.email` and returns the row's email);
|
|
- returns `null` when the order has no linked identity_user;
|
|
- returns `undefined` when the order does not exist.
|
|
- existing `OrderView` literals (`order-service`, `payments-service`, `checkout-service`)
|
|
needed no change (`email` is optional on the view; fixtures omit it → serialized as `null`).
|
|
|
|
### Boundary / compatibility notes
|
|
- `identity_users` is referenced only as a SQL table name (pre-existing pattern in `search`);
|
|
no TypeScript import crosses the identity↔orders boundary → `lint:boundaries` unchanged.
|
|
- No migration (`identity_users.email` already `citext NOT NULL UNIQUE`).
|
|
- Additive API field; no state-machine / endpoint / contract change.
|
|
|
|
## Evidence (commands + results)
|
|
- `npx tsc --noEmit` → **0 errors** (`TSC_EXIT:0`).
|
|
- `npx vitest run` → **200 passed | 56 skipped (50 files)**; new F-153 suite
|
|
`orders/infrastructure/pg-order-repository.test.ts` → **3 passed**.
|
|
- `npx eslint` (touched files) → **0 errors / 0 warnings** (`ESLINT_EXIT:0`).
|
|
- `npx prettier --check` on touched files → only **pre-existing baseline warnings**
|
|
(identical to `HEAD` for both `pg-order-repository.ts` and `orders.routes.ts`; my added
|
|
lines are short / long string literals accepted by Prettier; no new reformatting).
|
|
- `npm run lint:boundaries` → **1 violation, pre-existing** (`security/routes` R1);
|
|
**0 new violations** from F-153.
|
|
- `git diff --check` → **clean** (no whitespace errors).
|
|
- `./scripts/verify.sh` → **exit 0** (backlog 270 features valid; runtime-status advance to
|
|
build/running with F-153 in_progress — see below).
|
|
|
|
## Decisions
|
|
- Resolved via the read model (Option A in ADR) rather than route-layer lookups (Option B)
|
|
because `orders` already joins `identity_users` in SQL for `search`; generalizing that join
|
|
to all order reads gives the email as a first-class read attribute and removes the
|
|
per-request lookup from the admin transition.
|