feat(F-143): completed feature

This commit is contained in:
chattie
2026-08-22 11:43:42 +02:00
parent fb015932b2
commit 3cc51477aa
22 changed files with 1328 additions and 127 deletions

View File

@@ -1,29 +1,30 @@
# F-138Auto-sembrar fila de precio en creación de variante
# F-143 — Reporting: contracts, filters and RBAC
## Título
Auto-sembrar fila de precio en creación de variante para evitar la ventana 404 en `GET /pricing/variants/:id`.
## Estado
Diseño aprobado (ver `work/artifacts/F-143/architect.md`). Implementación backend-only.
## Contexto / Problema
- `GET /pricing/variants/:variantId` (modulo `pricing`, `PricingService.getVariantPrice``PgPricingRepository.findByVariantId`) devuelve **404** cuando `pricing_variant_prices` no tiene fila para `variant_id`.
- `CreateProductVariant.execute` (`catalog/application/variant-use-cases.ts`) solo llama a `variants.create` (inserta en `catalog_product_variants`) y **nunca** inserta en `pricing_variant_prices`.
- 3 call sites disparan `createVariant.execute`:
1. Creación de producto con variante por defecto (`POST /products`, autovariante `SKU-MV-{id}`).
2. Lazy migration en `GET /products/:id/variants` (producto legacy sin variantes → crea variante default para admin).
3. `POST /products/:id/variants` (creación explícita de variante).
- En todos los casos, la variante existe en `catalog_product_variants` pero `GET /pricing/variants/:variantId` 404ea **hasta que un admin no asocie un precio** → la carrotera/pos pueden romper ("el precio no existe").
## Producto (alcance)
El módulo `reporting` expone el **contrato compartido** de filtros de reporte y la **matriz de permisos `REPORTING_*`** como código, para que los endpoints de reporte futuros (F-144+) consuman un parser único y estén consistentes. **No genera reportes ni lectura de datos** (esas son F-144+).
## Solución
Sembrar (seed) una fila de precio por defecto **inmediatamente después de crear la variante**, con valores neutros: `net_unit_amount_cents = 0`, `vat_rate = 'general'`, `currency = 'EUR'` (default DDL). El sembrado es **idempotente** (`ON CONFLICT (variant_id) DO NOTHING`) y **best-effort**: si falla, la creación de la variante no se anula (la variante primaria es la prioridad; el precio es secundario).
## Alcance (entregable)
- Schema zod reusable `reportingFiltersSchema` (`from`, `to`, `compare`, `channel`, `storeId`, `terminalId`, `cashierId`, `paymentMethodId`, `productId`, `categoryId`, `brandId`, `customerId`, `state`, `groupBy`, `page`, `pageSize`, `sort`) con validación `from<to` y semántica `[from,to)`.
- Parser `parseReportingFilters` + helper `comparisonRange` (`none | previous_equal | previous_calendar`).
- Metadato `REPORTING_FILTER_META` (campos, modos de comparación, groupBy, paginación, `dataAvailability`) servido a `GET /reporting/filters/schema`.
- Permisos backend `REPORTING_*` basados en roles (`requireReportingPermission`) con matriz `admin/editor/pos_manager/pos_cashier/customer`.
- Rutas: `GET /reporting/filters/schema` (requiere `REPORTING_VIEW`), `GET /reporting/filters/validate` (requiere `REPORTING_SALES`).
## Alcance
- Backend: `pricing` (nuevo método `seedVariantPrice`) + `catalog` (inyección en `CreateProductVariant` + wiring build-app).
- Frontend: N/A (no hay cambios de UI).
- Migración: N/A — `pricing_variant_prices.variant_id` ya es UNIQUE/PK (lo demuestra `setVariantPrice` usando `ON CONFLICT (variant_id)`); no se requiere migración ni columna nueva.
## Fuera de alcance (F-144+)
- Lectura de datos transaccionales, agregaciones, dashboards, exportaciones, tabla de permisos en BD (F-142 §9 la marca como migración futura; hoy es role-based con los mismos call sites).
## Definición de terminado
- [x] `PricingService.seedVariantPrice(variantId)` existe + persiste fila default.
- [x] `CreateProductVariant` llama a `seedVariantPrice` tras `variants.create`.
- [x] Los 3 call sites dejan fila de precio tras crear variante.
- [x] Re-sembrar es no-op (idempotente).
- [x] Tests unitarios (sin DB) pasan; itest de AC skipped sin `TEST_DATABASE_URL`.
- [x] `npm run typecheck` 0 errores; `npm test` (targeted) verde; `verify.sh` green.
## API (contrato HTTP)
- `GET /reporting/filters/schema``{ filterSchema, comparison, dataAvailability, permissions: { role, grants } }`. Requiere `REPORTING_VIEW`.
- `GET /reporting/filters/validate?from=...&to=...&compare=...&...``{ ok, filters, comparison: { range } }`. Requiere `REPORTING_SALES`.
## Seguridad (F-143 es parte de éste)
- La matriz RBAC se impone en backend (`requireReportingPermission` sobre `CurrentUser.role`). El frontend no es autoridad.
- `customer` 0 permisos → 403 siempre.
- Futuro: migrar a tabla `backoffice_permissions` sin cambiar `requireReportingPermission(user, permission)`.
## Referencias
- Arquitectura Reporting (F-142): `docs/reporting/REPORTING_ARCHITECTURE.md` (§4§11).
- Patrones: `src/modules/pricing/api/pricing.routes.ts`, `src/shared/auth.ts`, `src/shared/http-input.ts`.