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

3.1 KiB

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 cartpricing 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).