4.6 KiB
4.6 KiB
F-143 — Architect design
Goal
Close work/artifacts/F-138 is done. F-143 establishes the shared reporting filter contract + RBAC foundation so F-144+ (sales/products/customers reports) share one parser, one response envelope, and one permission check. Per F-142 §3 ("cálculos viven en backend, en un módulo reporting") and §6 ("Filtros son un contrato común y reproducible"). No report data queries in this ticket (F-144+).
Approach (chosen)
- New
reportingmodule undersrc/modules/reporting/, wired inbuild-app.ts(composition root). No DB schema reads in F-143 →ReportingRoutesDepsneeds onlyauthenticate(nopool), mirroring how a thin backoffice module would mount. Registered insideif (deps.pool)alongside other backoffice modules so it sharescombinedAuth. - Filter schema as code: zod
reportingFiltersSchemainapplication/filters.ts, pure types indomain/filters.ts, route inapi/reporting.routes.ts, re-exports inindex.ts— exactly the pricing-module layering (api→application→domain→shared). - RBAC role-based today: codebase has only
Role-based gates (requireRoleinsrc/shared/auth.ts); there is no permission table. F-142 §9 proposesREPORTING_*perms. I implement them as a role→permission map (REPORTING_ROLE_PERMISSIONS) +requireReportingPermission(user, perm). This satisfies "backend REPORTING permissions" with zero migration (noALTER TABLE), and the helper signature is stable for the future table migration. (A future ticket migrates the map tobackoffice_permissions; call sites unchanged.)
Why not a permission table in F-143?
- Orquestra gates block
backlog/features.jsonedits by hand; a permission-table ticket would need its own migration + feature. F-142 §5 lists the data-model corrections as a separate prerequisite bucket. F-143 = contracts/permissions code only. Role-based map is the documented interim (F-142 §9 "compatibilidad inicial: admin puede ver todo; editor y roles POS necesitan asignación explícita").
Decisions
- Range semantics
[from,to)inclusive-start/exclusive-end (F-142 §6) → stored inREPORTING_FILTER_META.comparison.rangeBounds. Validated by zodrefine(from < to). - Repeatable UUID arrays accept single value via
z.preprocess(Fastifyquerystringyields a string for?x=aand an array for?x=a&x=b). Avoids 400 on the common single-filter case. - Two routes, two distinct permissions so RBAC is exercised end-to-end:
GET /reporting/filters/schema→REPORTING_VIEW(any backoffice introspects the contract).GET /reporting/filters/validate→REPORTING_SALES(validates an actual filter payload + computescomparisonRange). This routes thecomparisonhelper through HTTP so AC3/AC4/AC5 are integration-covered, not just unit.
dataAvailabilityis metadata only (F-142 §4 baseline). No metrics computed; the baseline is hardcoded truth so clients don't render0for unavailable metrics (F-142 §10: never convert unavailable→0).comparisonRangereturnsComparisonRange|null;none→null.previous_equal= exact-duration shift;previous_calendar= UTC-aligned prior window by spanned calendar days (documented approximation).
R1/R2 boundary justification
reporting/imports ONLYshared/auth,shared/errors,shared/http-input,shared/swagger+zod— no other module. ✓ R1 (check-module-boundaries clean by construction).src/app/build-app.tsimportsregisterReportingRoutes+ReportingRoutesDepstype frommodules/reporting/index.js— R2 public-index only. ✓- reporting does NOT import pricing/catalog etc.
Files (to be created)
src/modules/reporting/domain/filters.ts— pure types.src/modules/reporting/domain/permissions.ts—ReportingPermission,REPORTING_ROLE_PERMISSIONS,requireReportingPermission.src/modules/reporting/application/filters.ts— zod schema, parser,comparisonRange,REPORTING_FILTER_META.src/modules/reporting/api/reporting.routes.ts—registerReportingRoutes.src/modules/reporting/index.ts— public surface.src/modules/reporting/tests/filters.test.ts,tests/permissions.test.ts,api/reporting.routes.test.ts.- EDIT
src/app/build-app.ts— import + register reporting insideif (deps.pool).
Risks
- Querystring array parsing: mitigated by
z.preprocess(single↔array). - No pool in tests: route tests build a minimal Fastify +
registerReportingRoutesdirectly (mirrorsecurity.routes.test.ts), mockauthenticate→ no DB. ✓ - zod
.datetime({offset:true})needs zod ≥3.11; repo already usesz.uuid()/z.coerce(≥3.23) → safe.