feat(F-072): completed feature
This commit is contained in:
111
work/artifacts/F-072/architect.md
Normal file
111
work/artifacts/F-072/architect.md
Normal file
@@ -0,0 +1,111 @@
|
||||
# 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
|
||||
Reference in New Issue
Block a user