112 lines
3.9 KiB
Markdown
112 lines
3.9 KiB
Markdown
# F-072 — Architect: Dynamic VAT tax rates in product edit prices tab
|
|
|
|
## Current state
|
|
|
|
`PricingSection.tsx` (admin) has **hardcoded** VAT logic:
|
|
```ts
|
|
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**:
|
|
```tsx
|
|
<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` — add `super-reduced` to `VatRate` type and `VAT_BASIS_POINTS`
|
|
- `project/src/modules/pricing/api/pricing.routes.ts` — accept 'super-reduced' in `vatRateSchema`
|
|
- `project/migrations/031_vat_super_reduced.js` — update CHECK constraints on `pricing_variant_prices.vat_rate` and `pricing_price_history` columns to include 'super-reduced'
|
|
- `project/apps/admin/src/features/products/components/sections/PricingSection.tsx` — fetch active tax rates from `GET /admin/tax-rates`, populate dropdown dynamically, use fetched rate percentages for calculations
|
|
- `project/apps/admin/src/lib/api-client.ts` — extend `VariantPrice.vatRate` type to include `super-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`:
|
|
```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`:
|
|
```ts
|
|
const vatRateSchema = z.enum(['general', 'reduced', 'super-reduced']);
|
|
```
|
|
|
|
### Migration 031
|
|
|
|
Update CHECK constraints to:
|
|
```sql
|
|
CHECK (vat_rate IN ('general', 'reduced', 'super-reduced'))
|
|
```
|
|
|
|
### PricingSection.tsx
|
|
|
|
1. Add `TaxRate[]` state for active tax rates
|
|
2. `useEffect` on mount: fetch `taxApi.list()`, filter `active === true`
|
|
3. Replace `VAT_GENERAL/VAT_REDUCED` constants with dynamic lookup:
|
|
```ts
|
|
const rate = activeRates.find(r => r.appliesTo === vatRate[variantId]);
|
|
const multiplier = 1 + (rate?.ratePercent ?? 21) / 100;
|
|
const grossCents = Math.round(netCents * multiplier);
|
|
```
|
|
4. Dropdown maps `appliesTo` → option value, displays `name + ratePercent%`:
|
|
```tsx
|
|
{activeRates.map(r => (
|
|
<option key={r.appliesTo} value={r.appliesTo}>
|
|
{r.name} ({r.ratePercent}%)
|
|
</option>
|
|
))}
|
|
```
|
|
5. `setVatRate` stores `appliesTo` key (e.g. `'super-reduced'`) as the vat rate value
|
|
6. Default: use first active rate if none selected
|
|
|
|
### Admin api-client
|
|
|
|
Extend `VariantPrice.vatRate` type to include `'super-reduced'`:
|
|
```ts
|
|
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 --noEmit` on all projects
|
|
- `npx eslint` on changed files
|
|
- Apply migration 031 to dev DB
|
|
- Backend rebuilt and restarted
|
|
- API test: `PATCH /pricing/variants/:id` with `vatRate: 'super-reduced'` → HTTP 200
|
|
- Admin product edit → pricing tab → dropdown shows all 3 active rates
|
|
- `./scripts/verify.sh` green
|