41 lines
4.6 KiB
Markdown
41 lines
4.6 KiB
Markdown
# 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 `reporting` module** under `src/modules/reporting/`, wired in `build-app.ts` (composition root). No DB schema reads in F-143 → `ReportingRoutesDeps` needs only `authenticate` (no `pool`), mirroring how a thin backoffice module would mount. Registered inside `if (deps.pool)` alongside other backoffice modules so it shares `combinedAuth`.
|
|
- **Filter schema as code:** zod `reportingFiltersSchema` in `application/filters.ts`, pure types in `domain/filters.ts`, route in `api/reporting.routes.ts`, re-exports in `index.ts` — exactly the pricing-module layering (api→application→domain→shared).
|
|
- **RBAC role-based today:** codebase has only `Role`-based gates (`requireRole` in `src/shared/auth.ts`); there is **no permission table**. F-142 §9 proposes `REPORTING_*` perms. I implement them as a **role→permission map** (`REPORTING_ROLE_PERMISSIONS`) + `requireReportingPermission(user, perm)`. This satisfies "backend REPORTING permissions" with **zero migration** (no `ALTER TABLE`), and the helper signature is stable for the future table migration. (A future ticket migrates the map to `backoffice_permissions`; call sites unchanged.)
|
|
|
|
## Why not a permission table in F-143?
|
|
- Orquestra gates block `backlog/features.json` edits 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
|
|
1. **Range semantics `[from,to)` inclusive-start/exclusive-end** (F-142 §6) → stored in `REPORTING_FILTER_META.comparison.rangeBounds`. Validated by zod `refine(from < to)`.
|
|
2. **Repeatable UUID arrays accept single value** via `z.preprocess` (Fastify `querystring` yields a string for `?x=a` and an array for `?x=a&x=b`). Avoids 400 on the common single-filter case.
|
|
3. **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 + computes `comparisonRange`). This routes the `comparison` helper through HTTP so AC3/AC4/AC5 are integration-covered, not just unit.
|
|
4. **`dataAvailability` is metadata only** (F-142 §4 baseline). No metrics computed; the baseline is hardcoded truth so clients don't render `0` for unavailable metrics (F-142 §10: never convert unavailable→0).
|
|
5. **`comparisonRange`** returns `ComparisonRange|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 ONLY `shared/auth`, `shared/errors`, `shared/http-input`, `shared/swagger` + `zod` — **no other module**. ✓ R1 (check-module-boundaries clean by construction).
|
|
- `src/app/build-app.ts` imports `registerReportingRoutes` + `ReportingRoutesDeps` type from `modules/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 inside `if (deps.pool)`.
|
|
|
|
## Risks
|
|
- Querystring array parsing: mitigated by `z.preprocess` (single↔array).
|
|
- No pool in tests: route tests build a minimal Fastify + `registerReportingRoutes` directly (mirror `security.routes.test.ts`), mock `authenticate` → no DB. ✓
|
|
- zod `.datetime({offset:true})` needs zod ≥3.11; repo already uses `z.uuid()`/`z.coerce` (≥3.23) → safe.
|