@ftisindia/create-app 0.1.5 → 0.1.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/template/.env.example +25 -0
- package/template/README.md +51 -0
- package/template/_gitignore +6 -0
- package/template/_package.json +6 -0
- package/template/docs/FORMS.md +169 -0
- package/template/docs/FORMS_CHECKLIST.md +61 -0
- package/template/docs/REPORTS.md +246 -0
- package/template/docs/REPORTS_CHECKLIST.md +97 -0
- package/template/prisma/migrations/20260612000000_add_form_builder/migration.sql +147 -0
- package/template/prisma/migrations/20260613000000_add_report_builder/migration.sql +129 -0
- package/template/prisma/schema.prisma +285 -0
- package/template/scripts/export-openapi.ts +85 -0
- package/template/scripts/gen-form.mjs +149 -0
- package/template/scripts/push-form.ts +124 -0
- package/template/src/app.module.ts +29 -8
- package/template/src/common/dto/membership-response.dto.ts +1 -0
- package/template/src/common/dto/role-summary.dto.ts +3 -3
- package/template/src/common/dto/user-summary.dto.ts +3 -3
- package/template/src/config/env.validation.ts +25 -0
- package/template/src/config/forms.config.ts +12 -0
- package/template/src/config/index.ts +2 -0
- package/template/src/config/openapi.ts +12 -0
- package/template/src/config/reports-secret.ts +15 -0
- package/template/src/config/reports.config.ts +16 -0
- package/template/src/main.ts +3 -12
- package/template/src/modules/access-control/dto/access-control-response.dto.ts +3 -0
- package/template/src/modules/access-control/dto/current-access-control-response.dto.ts +5 -1
- package/template/src/modules/access-control/types/permission-key.ts +27 -0
- package/template/src/modules/access-control/types/route-permission-registry.ts +183 -0
- package/template/src/modules/audit/dto/audit-response.dto.ts +7 -3
- package/template/src/modules/auth/auth.module.ts +3 -1
- package/template/src/modules/auth/dto/auth-response.dto.ts +1 -1
- package/template/src/modules/forms/application/services/file-gc.service.ts +85 -0
- package/template/src/modules/forms/application/services/forms-definitions.service.ts +137 -0
- package/template/src/modules/forms/application/services/forms-error.mapper.ts +64 -0
- package/template/src/modules/forms/application/services/forms-export.service.ts +210 -0
- package/template/src/modules/forms/application/services/forms-files.service.ts +164 -0
- package/template/src/modules/forms/application/services/forms-public.service.ts +49 -0
- package/template/src/modules/forms/application/services/forms-settings-reader.service.ts +53 -0
- package/template/src/modules/forms/application/services/forms-submissions.service.ts +103 -0
- package/template/src/modules/forms/application/services/handlers/authenticate.action.ts +37 -0
- package/template/src/modules/forms/application/services/handlers/logging-email.handler.ts +22 -0
- package/template/src/modules/forms/application/services/handlers/send-confirmation-email.action.ts +40 -0
- package/template/src/modules/forms/application/services/handlers/webhook.handler.ts +41 -0
- package/template/src/modules/forms/application/services/outbox-dispatcher.service.ts +109 -0
- package/template/src/modules/forms/dto/create-form-definition.dto.ts +12 -0
- package/template/src/modules/forms/dto/data-source-response.dto.ts +19 -0
- package/template/src/modules/forms/dto/export-submissions-query.dto.ts +33 -0
- package/template/src/modules/forms/dto/file-upload-response.dto.ts +24 -0
- package/template/src/modules/forms/dto/form-definition-response.dto.ts +50 -0
- package/template/src/modules/forms/dto/form-render-response.dto.ts +17 -0
- package/template/src/modules/forms/dto/list-form-definitions-query.dto.ts +10 -0
- package/template/src/modules/forms/dto/list-submissions-query.dto.ts +10 -0
- package/template/src/modules/forms/dto/public-submit-form.dto.ts +24 -0
- package/template/src/modules/forms/dto/set-public-access.dto.ts +8 -0
- package/template/src/modules/forms/dto/submission-response.dto.ts +99 -0
- package/template/src/modules/forms/dto/submit-form.dto.ts +50 -0
- package/template/src/modules/forms/dto/update-form-definition.dto.ts +12 -0
- package/template/src/modules/forms/dto/upload-file-query.dto.ts +33 -0
- package/template/src/modules/forms/dto/validate-submission.dto.ts +22 -0
- package/template/src/modules/forms/examples/abstract-submission.form.json +80 -0
- package/template/src/modules/forms/examples/login.form.json +24 -0
- package/template/src/modules/forms/examples/registration.form.json +44 -0
- package/template/src/modules/forms/forms.module.ts +226 -0
- package/template/src/modules/forms/forms.tokens.ts +6 -0
- package/template/src/modules/forms/infrastructure/audit-sink.adapter.ts +30 -0
- package/template/src/modules/forms/infrastructure/casl-forms-authorization.ts +31 -0
- package/template/src/modules/forms/infrastructure/prisma-tx-runner.ts +17 -0
- package/template/src/modules/forms/infrastructure/registry/form-extension.decorators.ts +17 -0
- package/template/src/modules/forms/infrastructure/registry/registry-bootstrap.service.ts +82 -0
- package/template/src/modules/forms/infrastructure/request-forms-context.ts +60 -0
- package/template/src/modules/forms/infrastructure/schema-check/forms-schema-check.service.ts +76 -0
- package/template/src/modules/forms/infrastructure/storage/local-disk-storage.adapter.ts +43 -0
- package/template/src/modules/forms/infrastructure/stores/index.ts +5 -0
- package/template/src/modules/forms/infrastructure/stores/prisma-action-log.store.ts +37 -0
- package/template/src/modules/forms/infrastructure/stores/prisma-file.store.ts +108 -0
- package/template/src/modules/forms/infrastructure/stores/prisma-form-definition.store.ts +147 -0
- package/template/src/modules/forms/infrastructure/stores/prisma-outbox.store.ts +133 -0
- package/template/src/modules/forms/infrastructure/stores/prisma-submission.store.ts +164 -0
- package/template/src/modules/forms/presentation/forms-data-sources.controller.ts +58 -0
- package/template/src/modules/forms/presentation/forms-definitions.controller.ts +191 -0
- package/template/src/modules/forms/presentation/forms-files.controller.ts +79 -0
- package/template/src/modules/forms/presentation/forms-submissions.controller.ts +154 -0
- package/template/src/modules/forms/presentation/forms-upload.interceptor.ts +33 -0
- package/template/src/modules/forms/presentation/public-forms.controller.ts +51 -0
- package/template/src/modules/invitations/dto/invitation-response.dto.ts +4 -0
- package/template/src/modules/organisations/dto/organisation-response.dto.ts +1 -0
- package/template/src/modules/reports/application/services/reports-actions.service.ts +54 -0
- package/template/src/modules/reports/application/services/reports-definitions.service.ts +66 -0
- package/template/src/modules/reports/application/services/reports-error.mapper.ts +97 -0
- package/template/src/modules/reports/application/services/reports-export-dispatcher.service.ts +124 -0
- package/template/src/modules/reports/application/services/reports-exports.service.ts +74 -0
- package/template/src/modules/reports/application/services/reports-queries.service.ts +35 -0
- package/template/src/modules/reports/application/services/reports-settings-reader.service.ts +49 -0
- package/template/src/modules/reports/application/services/reports-views.service.ts +79 -0
- package/template/src/modules/reports/dto/action-result-response.dto.ts +21 -0
- package/template/src/modules/reports/dto/create-report-definition.dto.ts +86 -0
- package/template/src/modules/reports/dto/create-saved-view.dto.ts +26 -0
- package/template/src/modules/reports/dto/execute-action.dto.ts +71 -0
- package/template/src/modules/reports/dto/export-job-response.dto.ts +60 -0
- package/template/src/modules/reports/dto/export-request.dto.ts +34 -0
- package/template/src/modules/reports/dto/list-reports-query.dto.ts +10 -0
- package/template/src/modules/reports/dto/list-views-query.dto.ts +17 -0
- package/template/src/modules/reports/dto/prepare-action-response.dto.ts +14 -0
- package/template/src/modules/reports/dto/prepare-action.dto.ts +27 -0
- package/template/src/modules/reports/dto/query-response.dto.ts +64 -0
- package/template/src/modules/reports/dto/query-spec.dto.ts +120 -0
- package/template/src/modules/reports/dto/report-definition-response.dto.ts +64 -0
- package/template/src/modules/reports/dto/report-meta-query.dto.ts +16 -0
- package/template/src/modules/reports/dto/report-meta-response.dto.ts +113 -0
- package/template/src/modules/reports/dto/saved-view-response.dto.ts +66 -0
- package/template/src/modules/reports/dto/update-report-definition.dto.ts +9 -0
- package/template/src/modules/reports/dto/update-saved-view.dto.ts +27 -0
- package/template/src/modules/reports/examples/abstract-review-board.report.json +54 -0
- package/template/src/modules/reports/examples/org-members.report.json +55 -0
- package/template/src/modules/reports/infrastructure/audit-sink.adapter.ts +31 -0
- package/template/src/modules/reports/infrastructure/casl-reports-authorization.ts +39 -0
- package/template/src/modules/reports/infrastructure/forms-adapter/form-report-source.adapter.ts +292 -0
- package/template/src/modules/reports/infrastructure/forms-adapter/form-row-actions.ts +171 -0
- package/template/src/modules/reports/infrastructure/forms-adapter/forms-bridge-bootstrap.service.ts +32 -0
- package/template/src/modules/reports/infrastructure/prisma-catalog.adapter.ts +95 -0
- package/template/src/modules/reports/infrastructure/prisma-query-executor.ts +103 -0
- package/template/src/modules/reports/infrastructure/prisma-snapshot-runner.ts +47 -0
- package/template/src/modules/reports/infrastructure/prisma-tx-runner.ts +18 -0
- package/template/src/modules/reports/infrastructure/registry/registry-bootstrap.service.ts +61 -0
- package/template/src/modules/reports/infrastructure/registry/report-extension.decorators.ts +14 -0
- package/template/src/modules/reports/infrastructure/reports-job-queue.adapter.ts +28 -0
- package/template/src/modules/reports/infrastructure/request-reports-context.ts +42 -0
- package/template/src/modules/reports/infrastructure/schema-check/reports-schema-check.service.ts +116 -0
- package/template/src/modules/reports/infrastructure/storage/local-disk-export-storage.adapter.ts +79 -0
- package/template/src/modules/reports/infrastructure/stores/index.ts +5 -0
- package/template/src/modules/reports/infrastructure/stores/prisma-bulk-action-run.store.ts +89 -0
- package/template/src/modules/reports/infrastructure/stores/prisma-export-job.store.ts +93 -0
- package/template/src/modules/reports/infrastructure/stores/prisma-report-definition.store.ts +171 -0
- package/template/src/modules/reports/infrastructure/stores/prisma-row-tag.store.ts +110 -0
- package/template/src/modules/reports/infrastructure/stores/prisma-saved-view.store.ts +144 -0
- package/template/src/modules/reports/presentation/reports-actions.controller.ts +83 -0
- package/template/src/modules/reports/presentation/reports-definitions.controller.ts +156 -0
- package/template/src/modules/reports/presentation/reports-export-jobs.controller.ts +61 -0
- package/template/src/modules/reports/presentation/reports-export.controller.ts +76 -0
- package/template/src/modules/reports/presentation/reports-query.controller.ts +52 -0
- package/template/src/modules/reports/presentation/reports-views.controller.ts +140 -0
- package/template/src/modules/reports/reports-forms.module.ts +33 -0
- package/template/src/modules/reports/reports.module.ts +335 -0
- package/template/src/modules/reports/reports.tokens.ts +11 -0
- package/template/src/modules/reports/sources/org-members.source.ts +112 -0
- package/template/src/modules/settings/types/setting-definitions.ts +94 -0
- package/template/test/forms-definitions.e2e-spec.ts +394 -0
- package/template/test/forms-export.e2e-spec.ts +390 -0
- package/template/test/forms-files.e2e-spec.ts +345 -0
- package/template/test/forms-outbox.e2e-spec.ts +309 -0
- package/template/test/forms-permission-sync.spec.ts +27 -0
- package/template/test/forms-public.e2e-spec.ts +269 -0
- package/template/test/forms-schema-check.e2e-spec.ts +65 -0
- package/template/test/forms-submissions.e2e-spec.ts +500 -0
- package/template/test/forms-webhooks.e2e-spec.ts +261 -0
- package/template/test/reports-advanced.e2e-spec.ts +368 -0
- package/template/test/reports-permission-sync.spec.ts +30 -0
- package/template/test/reports-query.e2e-spec.ts +350 -0
- package/template/test/reports-tiers.e2e-spec.ts +257 -0
- package/template/test/route-registry.validator.spec.ts +22 -0
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# Reports Module Completion Checklist
|
|
2
|
+
|
|
3
|
+
The report builder counts as DONE only when every box below holds, on top of
|
|
4
|
+
the generic [`MODULE_COMPLETION_CHECKLIST.md`](./MODULE_COMPLETION_CHECKLIST.md).
|
|
5
|
+
Section references are to `report-builder/report-builder-design.md` (Rev 2).
|
|
6
|
+
|
|
7
|
+
## Boundary & the standalone guarantee
|
|
8
|
+
|
|
9
|
+
- [ ] The engine package (`@ftisindia/report-builder`) compiles and unit-tests **without** the template AND **without** `@ftisindia/form-builder` installed (§3.2/§13); only `src/modules/reports` imports template services.
|
|
10
|
+
- [ ] The engine's boundary lint passes: no `@nestjs/*`, `@prisma/*`, `@casl/*`, `@ftisindia/form-builder`, or template imports anywhere in the core; engine errors are engine-typed and mapped to HTTP exceptions only in the glue (`reports-error.mapper.ts`).
|
|
11
|
+
- [ ] **`ReportsModule` imports no FormsModule.** It runs over custom sources with its OWN async-export worker + file storage (no forms outbox / UploadedFile). Form-backed sources and the delegated `editSubmission`/`updateStatus` verbs live in the **optional** `ReportsFormsModule` bridge (`reports-forms.module.ts` + `infrastructure/forms-adapter/`), the ONLY artifact aware of both engines. Dropping the bridge from `app.module.ts` leaves reports fully functional.
|
|
12
|
+
|
|
13
|
+
## Schema ownership
|
|
14
|
+
|
|
15
|
+
- [ ] Boot-time schema check proven both ways: green on a freshly migrated app; clear fail-fast message (naming `REPORTS_ENGINE_SCHEMA_VERSION` and the snippet path) on a stale schema.
|
|
16
|
+
- [ ] The check also verifies the two `ReportSavedView` partial unique indexes and prints the raw SQL when they are missing (finding #5).
|
|
17
|
+
- [ ] `prisma/reports.prisma` in the engine matches the models in the app's `schema.prisma`; upgrades follow copy-snippet → `prisma migrate dev`.
|
|
18
|
+
|
|
19
|
+
## Permissions & registry
|
|
20
|
+
|
|
21
|
+
- [ ] All 7 report permission keys (+ `formSubmissions.update`) exist in `permissionKeys`, are seeded, and `test/reports-permission-sync.spec.ts` passes (engine ↔ template set equality).
|
|
22
|
+
- [ ] Every report route is in `routePermissionRegistry` (the boot-time registry validator passes); `reports.export` is enforced independently of `reports.read`.
|
|
23
|
+
- [ ] Attach-time gating proven: saving/publishing a definition wiring a row action whose `requiredPermissions` the author lacks is rejected (§6.1); the same keys are re-checked at execute time.
|
|
24
|
+
|
|
25
|
+
## The SQL boundary (§2.1)
|
|
26
|
+
|
|
27
|
+
- [ ] A definition column carrying an `sql` property is rejected by the meta-schema; SQL-shaped strings in `path`/`columnId` are rejected by the save lint (`SQL_IN_DEFINITION`).
|
|
28
|
+
- [ ] `$row.*` paths resolve only against the physical columns the source manifest exposes.
|
|
29
|
+
- [ ] Compiled statements carry user values exclusively as bind parameters (compiler snapshot tests assert no value ever lands in SQL text).
|
|
30
|
+
|
|
31
|
+
## Query semantics (§4/§5)
|
|
32
|
+
|
|
33
|
+
- [ ] Keyset pagination proven at depth: walking pages by cursor is constant-cost; `OFFSET` appears nowhere; the rowId tiebreaker is always appended.
|
|
34
|
+
- [ ] Cursor integrity: tampered cursors, and valid cursors replayed against a different sort/filter/version, are rejected with the restart message.
|
|
35
|
+
- [ ] Typed operators: `contains` on a number column is a 400; enum values outside the declared set are a 400.
|
|
36
|
+
- [ ] Counts: default none; `estimated` uses the planner; `exact-capped` scans at most cap+1 rows (the subquery-LIMIT shape, finding #2).
|
|
37
|
+
- [ ] Org predicate compiles first on every statement; a source with `rowScope` filters rows for the scoped user (cross-org reads return empty/404, never data).
|
|
38
|
+
- [ ] The statement budget fires as a typed error (408), not a hung connection.
|
|
39
|
+
|
|
40
|
+
## Tiers & publish lint (§5.2/§8)
|
|
41
|
+
|
|
42
|
+
- [ ] `live` tier publish fails when the source exceeds `reports.liveTierMaxRows`.
|
|
43
|
+
- [ ] `indexed` tier publish fails on missing generated columns/indexes WITH the exact suggestion SQL (`GENERATED ALWAYS AS`, `COMMENT ON COLUMN`, `CREATE INDEX`, `pg_trgm` extension when searchable); applying the snippet verbatim makes the same publish succeed (§8.1 round-trip proven by test).
|
|
44
|
+
- [ ] Plan lint records plan hashes into `compiled` at publish; queries after publish use the physical `rb_*` columns (not raw JSONB).
|
|
45
|
+
- [ ] `materialized` tier requires the declared relation to exist; responses carry `meta.freshAsOf`.
|
|
46
|
+
|
|
47
|
+
## Row actions & bulk (§6)
|
|
48
|
+
|
|
49
|
+
- [ ] Mutations delegate to the owning domain — grep proves the reports module performs no UPDATE/DELETE on source tables; `editSubmission` re-validates through the form engine against the stamped `formVersion` (an invalid patch is rejected by the FORM engine, not the grid).
|
|
50
|
+
- [ ] byFilter without a token is rejected; prepare→execute happy path works; drift beyond tolerance → 409 with `currentCount`; expired token → 410.
|
|
51
|
+
- [ ] Idempotency: a retried execute with the same key returns the recorded outcome (`replayed: true`) without re-running.
|
|
52
|
+
- [ ] byIds capped; cross-org ids resolve to nothing (tenancy).
|
|
53
|
+
|
|
54
|
+
## Tags (§7)
|
|
55
|
+
|
|
56
|
+
- [ ] `manageTags` gated by `reportTags.manage`; tags normalized; the org vocabulary setting enforced when non-empty; `label:*` single-valued replacement works.
|
|
57
|
+
- [ ] `$tags` hydration and `hasTag`/`hasAnyTag` filters proven against the `ReportRowTag` table.
|
|
58
|
+
|
|
59
|
+
## Exports (§9)
|
|
60
|
+
|
|
61
|
+
- [ ] Sync export streams with `Content-Disposition` and the right MIME; async export creates the job + outbox row in ONE transaction, runs on the worker, lands an org-bound `UploadedFile`, and is pollable.
|
|
62
|
+
- [ ] Both run inside one `REPEATABLE READ` snapshot; the duration bound rejects over-long exports with the tier guidance.
|
|
63
|
+
- [ ] **Every** export writes an audit row with report key, version, filter snapshot, row count, and the exact column list; `exportable: false` columns never appear even when explicitly requested.
|
|
64
|
+
- [ ] XLSX output opens (structurally valid OOXML; CSV carries the formula-injection guard).
|
|
65
|
+
|
|
66
|
+
## Saved views (§4)
|
|
67
|
+
|
|
68
|
+
- [ ] Shared-view create/update requires `reports.update`; personal views are invisible to other users; duplicate names rejected per the partial uniques.
|
|
69
|
+
- [ ] Publishing a version that drops/retypes a referenced column turns the view `degraded` (named columns), not a 500.
|
|
70
|
+
|
|
71
|
+
## Envelope & docs
|
|
72
|
+
|
|
73
|
+
- [ ] Engine error codes map to the documented HTTP statuses through `reports-error.mapper.ts` only; Swagger documents every route (the frontend contract flows from OpenAPI).
|
|
74
|
+
- [ ] `docs/REPORTS.md` matches reality; examples in `src/modules/reports/examples/` publish and serve.
|
|
75
|
+
|
|
76
|
+
## Test coverage (current state — keep honest)
|
|
77
|
+
|
|
78
|
+
**Engine unit tests** (`packages/report-builder`, vitest): compiler SQL shapes
|
|
79
|
+
(keyset OR-ladder, null surrogates, collation, bind ordering), cursor/token
|
|
80
|
+
roundtrip + tamper, typed-operator rejection, count subquery, meta-schema +
|
|
81
|
+
linter (incl. the §2.1 plain-path manifest validation), generated-column
|
|
82
|
+
naming/casting, catalog/plan/tier lint, suggestion-SQL emission, CSV/XLSX,
|
|
83
|
+
and all six services over in-memory port fakes.
|
|
84
|
+
|
|
85
|
+
**Template e2e** (`test/reports-query.e2e-spec.ts`, `test/reports-advanced.e2e-spec.ts`,
|
|
86
|
+
real Postgres over the org-members CUSTOM source): publish→query, keyset
|
|
87
|
+
paging, typed filter rejection, meta, permission gating, saved views +
|
|
88
|
+
compatibility, tags, sync CSV export; byFilter prepare/execute **token
|
|
89
|
+
protocol + drift (409) + idempotent replay**, the reports-owned **async
|
|
90
|
+
export worker + download**, **XLSX zip validity**, the **PII-egress audit
|
|
91
|
+
row**, **shared-view partial-unique** (409), and **indexed-tier publish
|
|
92
|
+
rejection with the `CREATE INDEX` migration snippet**.
|
|
93
|
+
|
|
94
|
+
**Not yet covered by e2e (unit-proven only — flag before claiming complete):**
|
|
95
|
+
the `materialized` tier (needs a real materialized view), the indexed-tier
|
|
96
|
+
publish *success* round-trip after applying the suggested migration, and the
|
|
97
|
+
boot schema-check *failure* path (the success path is proven by every boot).
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
-- CreateEnum
|
|
2
|
+
CREATE TYPE "FormDefinitionStatus" AS ENUM ('DRAFT', 'PUBLISHED', 'ARCHIVED');
|
|
3
|
+
|
|
4
|
+
-- CreateEnum
|
|
5
|
+
CREATE TYPE "SubmissionStatus" AS ENUM ('DRAFT', 'SUBMITTED');
|
|
6
|
+
|
|
7
|
+
-- CreateEnum
|
|
8
|
+
CREATE TYPE "FormActionStatus" AS ENUM ('OK', 'ERROR');
|
|
9
|
+
|
|
10
|
+
-- CreateEnum
|
|
11
|
+
CREATE TYPE "OutboxStatus" AS ENUM ('PENDING', 'PROCESSING', 'DONE', 'FAILED');
|
|
12
|
+
|
|
13
|
+
-- CreateEnum
|
|
14
|
+
CREATE TYPE "FileStatus" AS ENUM ('TEMPORARY', 'SCANNING', 'CLEAN', 'INFECTED', 'LINKED');
|
|
15
|
+
|
|
16
|
+
-- CreateTable
|
|
17
|
+
CREATE TABLE "FormDefinition" (
|
|
18
|
+
"id" TEXT NOT NULL,
|
|
19
|
+
"orgId" TEXT NOT NULL,
|
|
20
|
+
"key" TEXT NOT NULL,
|
|
21
|
+
"version" INTEGER NOT NULL,
|
|
22
|
+
"status" "FormDefinitionStatus" NOT NULL DEFAULT 'DRAFT',
|
|
23
|
+
"schema" JSONB NOT NULL,
|
|
24
|
+
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
25
|
+
"updatedAt" TIMESTAMP(3) NOT NULL,
|
|
26
|
+
|
|
27
|
+
CONSTRAINT "FormDefinition_pkey" PRIMARY KEY ("id")
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
-- CreateTable
|
|
31
|
+
CREATE TABLE "FormSubmission" (
|
|
32
|
+
"id" TEXT NOT NULL,
|
|
33
|
+
"orgId" TEXT NOT NULL,
|
|
34
|
+
"formKey" TEXT NOT NULL,
|
|
35
|
+
"formVersion" INTEGER NOT NULL,
|
|
36
|
+
"data" JSONB NOT NULL,
|
|
37
|
+
"status" "SubmissionStatus" NOT NULL DEFAULT 'DRAFT',
|
|
38
|
+
"locked" BOOLEAN NOT NULL DEFAULT false,
|
|
39
|
+
"createdBy" TEXT,
|
|
40
|
+
"ipAddress" TEXT,
|
|
41
|
+
"userAgent" TEXT,
|
|
42
|
+
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
43
|
+
"updatedAt" TIMESTAMP(3) NOT NULL,
|
|
44
|
+
|
|
45
|
+
CONSTRAINT "FormSubmission_pkey" PRIMARY KEY ("id")
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
-- CreateTable
|
|
49
|
+
CREATE TABLE "FormActionLog" (
|
|
50
|
+
"id" TEXT NOT NULL,
|
|
51
|
+
"orgId" TEXT NOT NULL,
|
|
52
|
+
"submissionId" TEXT,
|
|
53
|
+
"formKey" TEXT NOT NULL,
|
|
54
|
+
"formVersion" INTEGER,
|
|
55
|
+
"action" TEXT NOT NULL,
|
|
56
|
+
"status" "FormActionStatus" NOT NULL,
|
|
57
|
+
"input" JSONB,
|
|
58
|
+
"output" JSONB,
|
|
59
|
+
"errorCode" TEXT,
|
|
60
|
+
"errorMessage" TEXT,
|
|
61
|
+
"actorId" TEXT,
|
|
62
|
+
"requestId" TEXT,
|
|
63
|
+
"durationMs" INTEGER,
|
|
64
|
+
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
65
|
+
|
|
66
|
+
CONSTRAINT "FormActionLog_pkey" PRIMARY KEY ("id")
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
-- CreateTable
|
|
70
|
+
CREATE TABLE "FormOutboxJob" (
|
|
71
|
+
"id" TEXT NOT NULL,
|
|
72
|
+
"type" TEXT NOT NULL,
|
|
73
|
+
"payload" JSONB NOT NULL,
|
|
74
|
+
"status" "OutboxStatus" NOT NULL DEFAULT 'PENDING',
|
|
75
|
+
"attempts" INTEGER NOT NULL DEFAULT 0,
|
|
76
|
+
"maxAttempts" INTEGER NOT NULL DEFAULT 8,
|
|
77
|
+
"idempotencyKey" TEXT,
|
|
78
|
+
"lastError" TEXT,
|
|
79
|
+
"runAfter" TIMESTAMP(3),
|
|
80
|
+
"orgId" TEXT,
|
|
81
|
+
"actorUserId" TEXT,
|
|
82
|
+
"originRequestId" TEXT,
|
|
83
|
+
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
84
|
+
"updatedAt" TIMESTAMP(3) NOT NULL,
|
|
85
|
+
|
|
86
|
+
CONSTRAINT "FormOutboxJob_pkey" PRIMARY KEY ("id")
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
-- CreateTable
|
|
90
|
+
CREATE TABLE "UploadedFile" (
|
|
91
|
+
"id" TEXT NOT NULL,
|
|
92
|
+
"storageKey" TEXT NOT NULL,
|
|
93
|
+
"originalName" TEXT NOT NULL,
|
|
94
|
+
"mimeType" TEXT NOT NULL,
|
|
95
|
+
"size" INTEGER NOT NULL,
|
|
96
|
+
"checksum" TEXT NOT NULL,
|
|
97
|
+
"ownerId" TEXT NOT NULL,
|
|
98
|
+
"orgId" TEXT NOT NULL,
|
|
99
|
+
"status" "FileStatus" NOT NULL DEFAULT 'TEMPORARY',
|
|
100
|
+
"submissionId" TEXT,
|
|
101
|
+
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
102
|
+
|
|
103
|
+
CONSTRAINT "UploadedFile_pkey" PRIMARY KEY ("id")
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
-- CreateIndex
|
|
107
|
+
CREATE INDEX "FormDefinition_orgId_idx" ON "FormDefinition"("orgId");
|
|
108
|
+
|
|
109
|
+
-- CreateIndex
|
|
110
|
+
CREATE INDEX "FormDefinition_orgId_status_idx" ON "FormDefinition"("orgId", "status");
|
|
111
|
+
|
|
112
|
+
-- CreateIndex
|
|
113
|
+
CREATE UNIQUE INDEX "FormDefinition_orgId_key_version_key" ON "FormDefinition"("orgId", "key", "version");
|
|
114
|
+
|
|
115
|
+
-- CreateIndex
|
|
116
|
+
CREATE INDEX "FormSubmission_orgId_formKey_status_idx" ON "FormSubmission"("orgId", "formKey", "status");
|
|
117
|
+
|
|
118
|
+
-- CreateIndex
|
|
119
|
+
CREATE INDEX "FormSubmission_orgId_createdAt_idx" ON "FormSubmission"("orgId", "createdAt");
|
|
120
|
+
|
|
121
|
+
-- CreateIndex
|
|
122
|
+
CREATE INDEX "FormSubmission_orgId_formKey_ipAddress_createdAt_idx" ON "FormSubmission"("orgId", "formKey", "ipAddress", "createdAt");
|
|
123
|
+
|
|
124
|
+
-- CreateIndex
|
|
125
|
+
CREATE INDEX "FormActionLog_orgId_createdAt_idx" ON "FormActionLog"("orgId", "createdAt");
|
|
126
|
+
|
|
127
|
+
-- CreateIndex
|
|
128
|
+
CREATE INDEX "FormActionLog_orgId_formKey_action_status_idx" ON "FormActionLog"("orgId", "formKey", "action", "status");
|
|
129
|
+
|
|
130
|
+
-- CreateIndex
|
|
131
|
+
CREATE UNIQUE INDEX "FormOutboxJob_idempotencyKey_key" ON "FormOutboxJob"("idempotencyKey");
|
|
132
|
+
|
|
133
|
+
-- CreateIndex
|
|
134
|
+
CREATE INDEX "FormOutboxJob_status_runAfter_idx" ON "FormOutboxJob"("status", "runAfter");
|
|
135
|
+
|
|
136
|
+
-- CreateIndex
|
|
137
|
+
CREATE UNIQUE INDEX "UploadedFile_storageKey_key" ON "UploadedFile"("storageKey");
|
|
138
|
+
|
|
139
|
+
-- CreateIndex
|
|
140
|
+
CREATE INDEX "UploadedFile_orgId_status_idx" ON "UploadedFile"("orgId", "status");
|
|
141
|
+
|
|
142
|
+
-- CreateIndex
|
|
143
|
+
CREATE INDEX "UploadedFile_ownerId_status_idx" ON "UploadedFile"("ownerId", "status");
|
|
144
|
+
|
|
145
|
+
-- CreateIndex
|
|
146
|
+
CREATE INDEX "UploadedFile_status_createdAt_idx" ON "UploadedFile"("status", "createdAt");
|
|
147
|
+
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
-- CreateEnum
|
|
2
|
+
CREATE TYPE "ReportDefinitionStatus" AS ENUM ('DRAFT', 'PUBLISHED', 'ARCHIVED');
|
|
3
|
+
|
|
4
|
+
-- CreateEnum
|
|
5
|
+
CREATE TYPE "ReportExportStatus" AS ENUM ('PENDING', 'RUNNING', 'DONE', 'FAILED');
|
|
6
|
+
|
|
7
|
+
-- CreateEnum
|
|
8
|
+
CREATE TYPE "ReportActionRunStatus" AS ENUM ('RUNNING', 'DONE', 'FAILED');
|
|
9
|
+
|
|
10
|
+
-- CreateTable
|
|
11
|
+
CREATE TABLE "ReportDefinition" (
|
|
12
|
+
"id" TEXT NOT NULL,
|
|
13
|
+
"orgId" TEXT NOT NULL,
|
|
14
|
+
"key" TEXT NOT NULL,
|
|
15
|
+
"version" INTEGER NOT NULL,
|
|
16
|
+
"status" "ReportDefinitionStatus" NOT NULL DEFAULT 'DRAFT',
|
|
17
|
+
"schema" JSONB NOT NULL,
|
|
18
|
+
"compiled" JSONB,
|
|
19
|
+
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
20
|
+
"updatedAt" TIMESTAMP(3) NOT NULL,
|
|
21
|
+
|
|
22
|
+
CONSTRAINT "ReportDefinition_pkey" PRIMARY KEY ("id")
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
-- CreateTable
|
|
26
|
+
CREATE TABLE "ReportSavedView" (
|
|
27
|
+
"id" TEXT NOT NULL,
|
|
28
|
+
"orgId" TEXT NOT NULL,
|
|
29
|
+
"reportKey" TEXT NOT NULL,
|
|
30
|
+
"reportVersion" INTEGER NOT NULL,
|
|
31
|
+
"name" TEXT NOT NULL,
|
|
32
|
+
"spec" JSONB NOT NULL,
|
|
33
|
+
"ownerId" TEXT,
|
|
34
|
+
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
35
|
+
|
|
36
|
+
CONSTRAINT "ReportSavedView_pkey" PRIMARY KEY ("id")
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
-- CreateTable
|
|
40
|
+
CREATE TABLE "ReportExportJob" (
|
|
41
|
+
"id" TEXT NOT NULL,
|
|
42
|
+
"orgId" TEXT NOT NULL,
|
|
43
|
+
"reportKey" TEXT NOT NULL,
|
|
44
|
+
"reportVersion" INTEGER NOT NULL,
|
|
45
|
+
"spec" JSONB NOT NULL,
|
|
46
|
+
"asOf" TIMESTAMP(3),
|
|
47
|
+
"status" "ReportExportStatus" NOT NULL DEFAULT 'PENDING',
|
|
48
|
+
"fileId" TEXT,
|
|
49
|
+
"rowCount" INTEGER,
|
|
50
|
+
"error" TEXT,
|
|
51
|
+
"requestedBy" TEXT NOT NULL,
|
|
52
|
+
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
53
|
+
"updatedAt" TIMESTAMP(3) NOT NULL,
|
|
54
|
+
|
|
55
|
+
CONSTRAINT "ReportExportJob_pkey" PRIMARY KEY ("id")
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
-- CreateTable
|
|
59
|
+
CREATE TABLE "ReportBulkActionRun" (
|
|
60
|
+
"id" TEXT NOT NULL,
|
|
61
|
+
"orgId" TEXT NOT NULL,
|
|
62
|
+
"reportKey" TEXT NOT NULL,
|
|
63
|
+
"reportVersion" INTEGER NOT NULL,
|
|
64
|
+
"action" TEXT NOT NULL,
|
|
65
|
+
"idempotencyKey" TEXT NOT NULL,
|
|
66
|
+
"status" "ReportActionRunStatus" NOT NULL DEFAULT 'RUNNING',
|
|
67
|
+
"result" JSONB,
|
|
68
|
+
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
69
|
+
"updatedAt" TIMESTAMP(3) NOT NULL,
|
|
70
|
+
|
|
71
|
+
CONSTRAINT "ReportBulkActionRun_pkey" PRIMARY KEY ("id")
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
-- CreateTable
|
|
75
|
+
CREATE TABLE "ReportRowTag" (
|
|
76
|
+
"id" TEXT NOT NULL,
|
|
77
|
+
"orgId" TEXT NOT NULL,
|
|
78
|
+
"sourceKind" TEXT NOT NULL,
|
|
79
|
+
"sourceKey" TEXT NOT NULL,
|
|
80
|
+
"rowId" TEXT NOT NULL,
|
|
81
|
+
"tag" TEXT NOT NULL,
|
|
82
|
+
"createdBy" TEXT NOT NULL,
|
|
83
|
+
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
84
|
+
|
|
85
|
+
CONSTRAINT "ReportRowTag_pkey" PRIMARY KEY ("id")
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
-- CreateIndex
|
|
89
|
+
CREATE UNIQUE INDEX "ReportDefinition_orgId_key_version_key" ON "ReportDefinition"("orgId", "key", "version");
|
|
90
|
+
|
|
91
|
+
-- CreateIndex
|
|
92
|
+
CREATE INDEX "ReportDefinition_orgId_idx" ON "ReportDefinition"("orgId");
|
|
93
|
+
|
|
94
|
+
-- CreateIndex
|
|
95
|
+
CREATE INDEX "ReportDefinition_orgId_status_idx" ON "ReportDefinition"("orgId", "status");
|
|
96
|
+
|
|
97
|
+
-- CreateIndex
|
|
98
|
+
CREATE INDEX "ReportSavedView_orgId_reportKey_idx" ON "ReportSavedView"("orgId", "reportKey");
|
|
99
|
+
|
|
100
|
+
-- CreateIndex
|
|
101
|
+
CREATE INDEX "ReportExportJob_orgId_status_idx" ON "ReportExportJob"("orgId", "status");
|
|
102
|
+
|
|
103
|
+
-- CreateIndex
|
|
104
|
+
CREATE INDEX "ReportExportJob_orgId_createdAt_idx" ON "ReportExportJob"("orgId", "createdAt");
|
|
105
|
+
|
|
106
|
+
-- CreateIndex
|
|
107
|
+
CREATE UNIQUE INDEX "ReportBulkActionRun_orgId_idempotencyKey_key" ON "ReportBulkActionRun"("orgId", "idempotencyKey");
|
|
108
|
+
|
|
109
|
+
-- CreateIndex
|
|
110
|
+
CREATE INDEX "ReportBulkActionRun_orgId_reportKey_action_idx" ON "ReportBulkActionRun"("orgId", "reportKey", "action");
|
|
111
|
+
|
|
112
|
+
-- CreateIndex
|
|
113
|
+
CREATE UNIQUE INDEX "ReportRowTag_orgId_sourceKind_sourceKey_rowId_tag_key" ON "ReportRowTag"("orgId", "sourceKind", "sourceKey", "rowId", "tag");
|
|
114
|
+
|
|
115
|
+
-- CreateIndex
|
|
116
|
+
CREATE INDEX "ReportRowTag_orgId_sourceKind_sourceKey_tag_idx" ON "ReportRowTag"("orgId", "sourceKind", "sourceKey", "tag");
|
|
117
|
+
|
|
118
|
+
-- CreateIndex
|
|
119
|
+
CREATE INDEX "ReportRowTag_orgId_sourceKind_sourceKey_rowId_idx" ON "ReportRowTag"("orgId", "sourceKind", "sourceKey", "rowId");
|
|
120
|
+
|
|
121
|
+
-- Partial unique indexes for saved-view names (report design §12, finding #5).
|
|
122
|
+
-- Postgres treats NULLs as distinct, so a plain unique over nullable "ownerId"
|
|
123
|
+
-- would not deduplicate SHARED views. Prisma cannot express partial indexes:
|
|
124
|
+
-- if a later `prisma migrate dev` proposes dropping these, DELETE those DROP
|
|
125
|
+
-- lines — the reports boot check fails fast when they are missing.
|
|
126
|
+
CREATE UNIQUE INDEX "report_view_shared_uq"
|
|
127
|
+
ON "ReportSavedView" ("orgId", "reportKey", "name") WHERE "ownerId" IS NULL;
|
|
128
|
+
CREATE UNIQUE INDEX "report_view_personal_uq"
|
|
129
|
+
ON "ReportSavedView" ("orgId", "reportKey", "ownerId", "name") WHERE "ownerId" IS NOT NULL;
|
|
@@ -297,3 +297,288 @@ model AuditLog {
|
|
|
297
297
|
@@index([targetType, targetId])
|
|
298
298
|
@@index([createdAt])
|
|
299
299
|
}
|
|
300
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
301
|
+
// @ftisindia/form-builder — canonical Prisma model snippet (ENGINE_SCHEMA_VERSION 1)
|
|
302
|
+
//
|
|
303
|
+
// The APP owns schema.prisma and the migration history (ecosystem guide §10.1).
|
|
304
|
+
// Copy these models into your app's prisma/schema.prisma verbatim and run
|
|
305
|
+
// `prisma migrate dev`. The glue module's boot-time check verifies the live
|
|
306
|
+
// database matches this snippet's tables/columns and fails fast otherwise.
|
|
307
|
+
// Conventions follow the FTIS template: uuid() ids, UPPER_CASE enum values,
|
|
308
|
+
// org-leading hot indexes, explicit orgId on every engine table.
|
|
309
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
310
|
+
|
|
311
|
+
enum FormDefinitionStatus {
|
|
312
|
+
DRAFT
|
|
313
|
+
PUBLISHED
|
|
314
|
+
ARCHIVED
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
enum SubmissionStatus {
|
|
318
|
+
DRAFT
|
|
319
|
+
SUBMITTED
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
enum FormActionStatus {
|
|
323
|
+
OK
|
|
324
|
+
ERROR
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
enum OutboxStatus {
|
|
328
|
+
PENDING
|
|
329
|
+
PROCESSING
|
|
330
|
+
DONE
|
|
331
|
+
FAILED
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
enum FileStatus {
|
|
335
|
+
TEMPORARY
|
|
336
|
+
SCANNING
|
|
337
|
+
CLEAN
|
|
338
|
+
INFECTED
|
|
339
|
+
LINKED
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
model FormDefinition {
|
|
343
|
+
id String @id @default(uuid())
|
|
344
|
+
orgId String
|
|
345
|
+
key String
|
|
346
|
+
// Bumped on every published change; published rows are immutable.
|
|
347
|
+
version Int
|
|
348
|
+
status FormDefinitionStatus @default(DRAFT)
|
|
349
|
+
// The full FormDefinition document (JSONB).
|
|
350
|
+
schema Json
|
|
351
|
+
|
|
352
|
+
createdAt DateTime @default(now())
|
|
353
|
+
updatedAt DateTime @updatedAt
|
|
354
|
+
|
|
355
|
+
@@unique([orgId, key, version])
|
|
356
|
+
@@index([orgId])
|
|
357
|
+
@@index([orgId, status])
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
model FormSubmission {
|
|
361
|
+
id String @id @default(uuid())
|
|
362
|
+
orgId String
|
|
363
|
+
formKey String
|
|
364
|
+
// The exact definition version the data was captured under. Non-negotiable.
|
|
365
|
+
formVersion Int
|
|
366
|
+
// Nested submission payload (JSONB) — submissions are documents, not rows.
|
|
367
|
+
data Json
|
|
368
|
+
status SubmissionStatus @default(DRAFT)
|
|
369
|
+
// Set by the lockEditing action; locked submissions reject further edits.
|
|
370
|
+
locked Boolean @default(false)
|
|
371
|
+
|
|
372
|
+
createdBy String?
|
|
373
|
+
// Stamped only on anonymous/public submissions (abuse-control Tier 1).
|
|
374
|
+
ipAddress String?
|
|
375
|
+
userAgent String?
|
|
376
|
+
|
|
377
|
+
createdAt DateTime @default(now())
|
|
378
|
+
updatedAt DateTime @updatedAt
|
|
379
|
+
|
|
380
|
+
@@index([orgId, formKey, status])
|
|
381
|
+
@@index([orgId, createdAt])
|
|
382
|
+
@@index([orgId, formKey, ipAddress, createdAt])
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
model FormActionLog {
|
|
386
|
+
id String @id @default(uuid())
|
|
387
|
+
orgId String
|
|
388
|
+
submissionId String?
|
|
389
|
+
formKey String
|
|
390
|
+
formVersion Int?
|
|
391
|
+
action String
|
|
392
|
+
status FormActionStatus
|
|
393
|
+
// Redacted by the engine before write — sensitive fields never land here.
|
|
394
|
+
input Json?
|
|
395
|
+
output Json?
|
|
396
|
+
errorCode String?
|
|
397
|
+
errorMessage String?
|
|
398
|
+
actorId String?
|
|
399
|
+
requestId String?
|
|
400
|
+
durationMs Int?
|
|
401
|
+
createdAt DateTime @default(now())
|
|
402
|
+
|
|
403
|
+
@@index([orgId, createdAt])
|
|
404
|
+
@@index([orgId, formKey, action, status])
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
model FormOutboxJob {
|
|
408
|
+
id String @id @default(uuid())
|
|
409
|
+
type String
|
|
410
|
+
payload Json
|
|
411
|
+
status OutboxStatus @default(PENDING)
|
|
412
|
+
|
|
413
|
+
attempts Int @default(0)
|
|
414
|
+
maxAttempts Int @default(8)
|
|
415
|
+
// Prevents double-send on retry (e.g. submissionId:type:action).
|
|
416
|
+
idempotencyKey String? @unique
|
|
417
|
+
lastError String?
|
|
418
|
+
runAfter DateTime?
|
|
419
|
+
|
|
420
|
+
// Worker-context restoration + audit correlation (ecosystem guide §3).
|
|
421
|
+
orgId String?
|
|
422
|
+
actorUserId String?
|
|
423
|
+
originRequestId String?
|
|
424
|
+
|
|
425
|
+
createdAt DateTime @default(now())
|
|
426
|
+
updatedAt DateTime @updatedAt
|
|
427
|
+
|
|
428
|
+
@@index([status, runAfter])
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
model UploadedFile {
|
|
432
|
+
id String @id @default(uuid())
|
|
433
|
+
storageKey String @unique
|
|
434
|
+
originalName String
|
|
435
|
+
// SERVER-VERIFIED (sniffed) MIME type, never the client-sent Content-Type.
|
|
436
|
+
mimeType String
|
|
437
|
+
size Int
|
|
438
|
+
// sha256 hex.
|
|
439
|
+
checksum String
|
|
440
|
+
// Two-factor ownership: uploader identity AND tenant scope (§10).
|
|
441
|
+
ownerId String
|
|
442
|
+
orgId String
|
|
443
|
+
|
|
444
|
+
status FileStatus @default(TEMPORARY)
|
|
445
|
+
submissionId String?
|
|
446
|
+
|
|
447
|
+
createdAt DateTime @default(now())
|
|
448
|
+
|
|
449
|
+
@@index([orgId, status])
|
|
450
|
+
@@index([ownerId, status])
|
|
451
|
+
@@index([status, createdAt])
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
455
|
+
// Report builder (@ftisindia/report-builder) — engine tables
|
|
456
|
+
// (REPORTS_ENGINE_SCHEMA_VERSION 1). Copied from the engine's canonical
|
|
457
|
+
// snippet (@ftisindia/report-builder/prisma/reports.prisma); the glue
|
|
458
|
+
// module's boot check verifies the live database matches it. Sources stay
|
|
459
|
+
// where they live — these are the engine's OWN tables only (report design §12).
|
|
460
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
461
|
+
|
|
462
|
+
enum ReportDefinitionStatus {
|
|
463
|
+
DRAFT
|
|
464
|
+
PUBLISHED
|
|
465
|
+
ARCHIVED
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
enum ReportExportStatus {
|
|
469
|
+
PENDING
|
|
470
|
+
RUNNING
|
|
471
|
+
DONE
|
|
472
|
+
FAILED
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
enum ReportActionRunStatus {
|
|
476
|
+
RUNNING
|
|
477
|
+
DONE
|
|
478
|
+
FAILED
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
model ReportDefinition {
|
|
482
|
+
id String @id @default(uuid())
|
|
483
|
+
orgId String
|
|
484
|
+
key String
|
|
485
|
+
// Bumped on every published change; published rows are immutable.
|
|
486
|
+
version Int
|
|
487
|
+
status ReportDefinitionStatus @default(DRAFT)
|
|
488
|
+
// The full ReportDefinition document (JSONB).
|
|
489
|
+
schema Json
|
|
490
|
+
// Set at publish: logical→physical column map + plan hashes (report design §5.2/§8.1).
|
|
491
|
+
compiled Json?
|
|
492
|
+
|
|
493
|
+
createdAt DateTime @default(now())
|
|
494
|
+
updatedAt DateTime @updatedAt
|
|
495
|
+
|
|
496
|
+
@@unique([orgId, key, version])
|
|
497
|
+
@@index([orgId])
|
|
498
|
+
@@index([orgId, status])
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
model ReportSavedView {
|
|
502
|
+
id String @id @default(uuid())
|
|
503
|
+
orgId String
|
|
504
|
+
reportKey String
|
|
505
|
+
// The definition version the view was authored against (report design §4).
|
|
506
|
+
reportVersion Int
|
|
507
|
+
name String
|
|
508
|
+
// The saved QuerySpec (JSONB).
|
|
509
|
+
spec Json
|
|
510
|
+
// null ⇒ shared with the whole org (creation gated by reports.update).
|
|
511
|
+
ownerId String?
|
|
512
|
+
createdAt DateTime @default(now())
|
|
513
|
+
|
|
514
|
+
@@index([orgId, reportKey])
|
|
515
|
+
// Uniqueness lives in PARTIAL UNIQUE INDEXES shipped as raw SQL inside the
|
|
516
|
+
// add_report_builder migration — Postgres treats NULLs as distinct, so a
|
|
517
|
+
// @@unique over nullable ownerId would NOT deduplicate shared views (report
|
|
518
|
+
// design §12, finding #5). If a later `prisma migrate dev` proposes
|
|
519
|
+
// DROP INDEX "report_view_shared_uq" / "report_view_personal_uq", DELETE
|
|
520
|
+
// those lines — the reports boot check fails fast if they go missing.
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
model ReportExportJob {
|
|
524
|
+
id String @id @default(uuid())
|
|
525
|
+
orgId String
|
|
526
|
+
reportKey String
|
|
527
|
+
// The version the export ran against (report design §9, finding #1).
|
|
528
|
+
reportVersion Int
|
|
529
|
+
// FULL export spec {spec, format, columns} — replayable, auditable (not a hash).
|
|
530
|
+
spec Json
|
|
531
|
+
// REPEATABLE READ snapshot timestamp, set when the job runs (finding #8).
|
|
532
|
+
asOf DateTime?
|
|
533
|
+
status ReportExportStatus @default(PENDING)
|
|
534
|
+
// UploadedFile id once the async export lands in file storage.
|
|
535
|
+
fileId String?
|
|
536
|
+
rowCount Int?
|
|
537
|
+
error String?
|
|
538
|
+
requestedBy String
|
|
539
|
+
|
|
540
|
+
createdAt DateTime @default(now())
|
|
541
|
+
updatedAt DateTime @updatedAt
|
|
542
|
+
|
|
543
|
+
@@index([orgId, status])
|
|
544
|
+
@@index([orgId, createdAt])
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
model ReportBulkActionRun {
|
|
548
|
+
id String @id @default(uuid())
|
|
549
|
+
orgId String
|
|
550
|
+
reportKey String
|
|
551
|
+
reportVersion Int
|
|
552
|
+
action String
|
|
553
|
+
// Client-supplied; a retried execute with the same key returns the recorded
|
|
554
|
+
// outcome instead of re-running (report design §6.3, finding #7).
|
|
555
|
+
idempotencyKey String
|
|
556
|
+
status ReportActionRunStatus @default(RUNNING)
|
|
557
|
+
result Json?
|
|
558
|
+
|
|
559
|
+
createdAt DateTime @default(now())
|
|
560
|
+
updatedAt DateTime @updatedAt
|
|
561
|
+
|
|
562
|
+
@@unique([orgId, idempotencyKey])
|
|
563
|
+
@@index([orgId, reportKey, action])
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
model ReportRowTag {
|
|
567
|
+
id String @id @default(uuid())
|
|
568
|
+
orgId String
|
|
569
|
+
// 'custom' | 'form' | any registered source-provider kind.
|
|
570
|
+
sourceKind String
|
|
571
|
+
// Custom source key or form key.
|
|
572
|
+
sourceKey String
|
|
573
|
+
// The source's declared, STABLE row identity (report design §7).
|
|
574
|
+
rowId String
|
|
575
|
+
// Normalized: lowercased, trimmed. Labels are single-valued by convention
|
|
576
|
+
// ("label:approved") — one table, one mechanism.
|
|
577
|
+
tag String
|
|
578
|
+
createdBy String
|
|
579
|
+
createdAt DateTime @default(now())
|
|
580
|
+
|
|
581
|
+
@@unique([orgId, sourceKind, sourceKey, rowId, tag])
|
|
582
|
+
@@index([orgId, sourceKind, sourceKey, tag])
|
|
583
|
+
@@index([orgId, sourceKind, sourceKey, rowId])
|
|
584
|
+
}
|