Files
mercadodevida/work/artifacts/F-138/architect.md
2026-08-22 10:22:25 +02:00

30 lines
3.1 KiB
Markdown

# F-138 — Architect note (design)
## What
F-138 — close the `GET /pricing/variants/:id` 404 race by seeding a default `pricing_variant_prices` row immediately after `CreateProductVariant` persists a variant. Pricing owns its table (F-153 precedent for cross-module table read; for cross-module **writes** the repo uses service injection — same pattern `cart` already uses on `pricing` in `build-app.ts`).
## Why
`variants.create` only inserts `catalog_product_variants`; it never inserts `pricing_variant_prices`. Three call sites create variants (product autovariant, lazy GET, explicit POST) — all 404 on the pricing GET until an admin prices them. Customer/cart flows that read a price right after variant creation break.
## Approach
1. `PricingRepository` + `PricingService` gain `seedVariantPrice(variantId): Promise<void>`.
2. `PgPricingRepository.seedVariantPrice`: `INSERT pricing_variant_prices(variant_id, net_unit_amount_cents, offer_cents, cost_cents, vat_rate) VALUES ($1, 0, NULL, NULL, 'general') ON CONFLICT (variant_id) DO NOTHING` — same column set as `setVariantPrice` (`currency`/`created_at`/`updated_at` come from DDL defaults). Idempotent + no injection (`variantId` is a UUID produced by `variants.create`).
3. `CreateProductVariant` ctor += `pricing: PricingService` (type-only import of pricing **public index** — R1 legal); after `variants.create` returns, best-effort `await this.pricing.seedVariantPrice(variant.id)` (try/catch: variant already persisted; seed failure must not fail creation).
4. `catalog.routes.ts`: `CatalogRoutesDeps += pricing`; wire `new CreateProductVariant(repository, variants, pricing)`. Single constructor → all 3 call sites seeded.
5. `build-app.ts`: hoist `const pricing = createPricingService(deps.pool)` above the `registerCatalogRoutes` block so it can be passed in; pricing routes + cart keep using it (unchanged). `createPricingService` already imported.
## Alternatives considered
- (a) Raw SQL insert from `CreateProductVariant` via `deps.pool` — REJECTED: catalog app layer shouldn't own pricing table writes (layering; reviewer would flag). Service injection keeps ownership clean.
- (b) Seed in `PgProductVariantRepository.create` — REJECTED: couples catalog infra to pricing schema; breaks single-responsibility.
- (c) `PricingService` injection (CHOSEN) — matches the established `cart``pricing` cross-module write pattern; testable with a fake.
## Risks
- build-app ordering: hoisting `pricing` const is a pure construction reorder — safe (`deps.pool` available, createPricingService has no side effects).
- Seed failure: made best-effort so variant creation never regresses (AC3).
- tsc `strict` + `noUncheckedIndexedAccess`: not relevant here (no array indexing in new code).
## Tests
- `catalog/tests/variant-use-cases.test.ts` (unit, runnable): fakes for ProductRepository, ProductVariantRepository, PricingService — asserts seed-called, no-seed-when-product-missing, seed-failure-doesn't-break-creation.
- `catalog.itest.ts` AC itest (skipIf !hasDb): POST variant → GET `/pricing/variants/:id` 200 + defaults.
- No migration (variant_id UNIQUE/PK already).