3.9 KiB
3.9 KiB
F-072 — Architect: Dynamic VAT tax rates in product edit prices tab
Current state
PricingSection.tsx (admin) has hardcoded VAT logic:
const VAT_GENERAL = 1.21; // hardcoded constant
const VAT_REDUCED = 1.10; // hardcoded constant
// calcGross: gross = net * VAT_GENERAL/VAT_REDUCED
The dropdown has two hardcoded options:
<option value="general">21% gen.</option>
<option value="reduced">10% red.</option>
But the DB tax_rates table already has 3 active rates:
- IVA Superreducido: 4% (applies_to:
super-reduced) - IVA Reducido: 10% (applies_to:
reduced) - IVA General: 21% (applies_to:
general)
The 4% rate is missing from the dropdown and calculation.
Goal
Replace hardcoded VAT constants and dropdown with dynamic fetch of active tax rates from GET /admin/tax-rates. Also add super-reduced support to the backend so the 4% rate can be used.
Scope IN
project/src/modules/pricing/domain/price.ts— addsuper-reducedtoVatRatetype andVAT_BASIS_POINTSproject/src/modules/pricing/api/pricing.routes.ts— accept 'super-reduced' invatRateSchemaproject/migrations/031_vat_super_reduced.js— update CHECK constraints onpricing_variant_prices.vat_rateandpricing_price_historycolumns to include 'super-reduced'project/apps/admin/src/features/products/components/sections/PricingSection.tsx— fetch active tax rates fromGET /admin/tax-rates, populate dropdown dynamically, use fetched rate percentages for calculationsproject/apps/admin/src/lib/api-client.ts— extendVariantPrice.vatRatetype to includesuper-reduced
Scope OUT
- Tax rates admin page (
/tax-rates) — no changes needed, already supports activate/deactivate - Backend pricing service — already generic, only needs domain type update
- Storefront/frontend — no changes
Design
Backend: add super-reduced
price.ts:
export type VatRate = 'general' | 'reduced' | 'super-reduced';
export const VAT_BASIS_POINTS: Record<VatRate, number> = {
general: 2100, // 21%
reduced: 1000, // 10%
'super-reduced': 400, // 4%
};
pricing.routes.ts — vatRateSchema:
const vatRateSchema = z.enum(['general', 'reduced', 'super-reduced']);
Migration 031
Update CHECK constraints to:
CHECK (vat_rate IN ('general', 'reduced', 'super-reduced'))
PricingSection.tsx
- Add
TaxRate[]state for active tax rates useEffecton mount: fetchtaxApi.list(), filteractive === true- Replace
VAT_GENERAL/VAT_REDUCEDconstants with dynamic lookup:const rate = activeRates.find(r => r.appliesTo === vatRate[variantId]); const multiplier = 1 + (rate?.ratePercent ?? 21) / 100; const grossCents = Math.round(netCents * multiplier); - Dropdown maps
appliesTo→ option value, displaysname + ratePercent%:{activeRates.map(r => ( <option key={r.appliesTo} value={r.appliesTo}> {r.name} ({r.ratePercent}%) </option> ))} setVatRatestoresappliesTokey (e.g.'super-reduced') as the vat rate value- Default: use first active rate if none selected
Admin api-client
Extend VariantPrice.vatRate type to include 'super-reduced':
vatRate: 'general' | 'reduced' | 'super-reduced';
Risk
- Low risk: adding a new allowed value to an existing enum + updating a CHECK constraint; no data migration needed (existing data already uses 'general' or 'reduced')
- Risk mitigation: migration is additive (adds 'super-reduced' to allowed values, doesn't remove any); rollback simply reverts constraint
Verification
npx tsc --noEmiton all projectsnpx eslinton changed files- Apply migration 031 to dev DB
- Backend rebuilt and restarted
- API test:
PATCH /pricing/variants/:idwithvatRate: 'super-reduced'→ HTTP 200 - Admin product edit → pricing tab → dropdown shows all 3 active rates
./scripts/verify.shgreen