feat(ADM-018): completed feature
This commit is contained in:
74
work/artifacts/F-017/architect.md
Normal file
74
work/artifacts/F-017/architect.md
Normal file
@@ -0,0 +1,74 @@
|
||||
# Architect — F-017 Pricing module
|
||||
|
||||
## Feature
|
||||
F-017 adds server-side pricing and VAT calculation behind a public `PricingService`, so future cart/checkout flows never trust frontend-supplied prices.
|
||||
|
||||
## Design
|
||||
|
||||
### Module boundaries
|
||||
Create `project/src/modules/pricing/` following existing module structure:
|
||||
- `domain/price.ts` for money, VAT rate, price record and calculation result types.
|
||||
- `domain/ports.ts` for `PricingRepository` and `PricingService` contracts.
|
||||
- `domain/errors.ts` for missing price and invalid pricing command errors.
|
||||
- `application/pricing-service.ts` implementing calculations and price changes.
|
||||
- `infrastructure/pg-pricing-repository.ts` owning PostgreSQL persistence.
|
||||
- `api/pricing.routes.ts` for explicit backend endpoints.
|
||||
- `index.ts` exporting only the public pricing API.
|
||||
|
||||
Catalog remains independent. Pricing stores `variant_id` as an opaque UUID reference and does not join or import catalog internals. Future cart/checkout must depend on `PricingService`, injected at composition root.
|
||||
|
||||
### Money and VAT rules
|
||||
Use integer cents only — no floating point money.
|
||||
- Stored base price: `net_unit_amount_cents` (pre-VAT).
|
||||
- VAT rate: enum `general` = 21% and `reduced` = 10% for this slice.
|
||||
- Calculation: `netSubtotal = netUnit * quantity`; `vatAmount = round(netSubtotal * vatBps / 10000)`; `total = netSubtotal + vatAmount`.
|
||||
- Currency is fixed to `EUR` by database default and API response. Multi-currency is explicitly out of scope.
|
||||
|
||||
### Data model
|
||||
Add migration `012_pricing.js`:
|
||||
- `pricing_variant_prices`
|
||||
- `variant_id uuid primary key`
|
||||
- `net_unit_amount_cents integer not null check >= 0`
|
||||
- `vat_rate text not null check in ('general', 'reduced')`
|
||||
- `currency text not null default 'EUR' check = 'EUR'`
|
||||
- timestamps
|
||||
- `pricing_price_history`
|
||||
- append-only audit rows for every price change
|
||||
- stores previous and new net amount/rate/currency, plus timestamp
|
||||
- first price creation also writes a history row with previous fields null
|
||||
|
||||
### Public service interface
|
||||
Expose `PricingService` from module index:
|
||||
- `calculate({ variantId, quantity }): Promise<PriceCalculation>`
|
||||
- `setVariantPrice({ variantId, netUnitAmountCents, vatRate }): Promise<VariantPrice>`
|
||||
- `getVariantPrice(variantId): Promise<VariantPrice | undefined>`
|
||||
|
||||
`calculate` never accepts or uses client unit price, subtotal, VAT, discount, or total fields. That is the core rule: callers provide product identity and quantity; pricing returns truth.
|
||||
|
||||
### API slice
|
||||
Add routes:
|
||||
- `GET /pricing/variants/:variantId` — public read of current server-side price.
|
||||
- `PUT /pricing/variants/:variantId` — admin-only price change.
|
||||
- `POST /pricing/calculate` — calculation by `variantId` and `quantity`; schema strips/ignores extra client fields.
|
||||
|
||||
HTTP errors:
|
||||
- `PRICING_PRICE_NOT_FOUND` as 404 when calculating/read has no server-side price.
|
||||
- `INVALID_PRICE` as 422 for invalid commands.
|
||||
|
||||
### Tests
|
||||
- Unit tests for VAT calculation for general/reduced rates and invalid quantity/amount.
|
||||
- Unit/API test proving client-supplied price fields are ignored by schema/service and server price is used.
|
||||
- Integration test proving every price change writes a history row, including create and update.
|
||||
- Boundary test proving catalog does not reference `pricing_` tables or pricing internals.
|
||||
- Migration suite remains the safety net for up/down/idempotency.
|
||||
|
||||
## Acceptance trace
|
||||
- PricingService.calculate returns totals with VAT from persisted server price.
|
||||
- Client price ignored: calculate only uses variant id + quantity; schema strips extra price fields and tests send bogus client values.
|
||||
- Price history: repository writes append-only rows on creation and update.
|
||||
- `verify.sh`: must pass after gates.
|
||||
|
||||
## Risks / constraints
|
||||
- Do not add decimal/money dependencies; integer cents are sufficient and safer.
|
||||
- Do not couple pricing to catalog by FK or join in this slice; variant existence validation is a later orchestration concern.
|
||||
- Do not store or trust client totals anywhere.
|
||||
Reference in New Issue
Block a user