Drop document_type table; reuse file_type enum for evidence requirements

Evidence requirements now reference the existing uploaded_files file_type
enum instead of a separate document_type reference table, so the required
and submitted document kinds share one taxonomy.

- Remove the document_type table and its relations.
- project_workstream_evidence_requirement.document_type_id ->
  file_type (file_type enum, NOT NULL).
- Extract fileType/fileSource enums into uploaded_files_enums.ts so the
  Projects module can reference fileType without an import cycle
  (uploaded_files.ts imports the Projects tables for its FK columns).
  uploaded_files.ts re-exports the enums for backwards compatibility.
- Regenerate migration 0273 (now 10 Projects tables, not 11).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Daniel Roth 2026-07-17 16:26:00 +00:00
parent 6f9097d293
commit aa0ac24868
8 changed files with 118 additions and 172 deletions

View file

@ -6,11 +6,6 @@ CREATE TABLE "contractor" (
"email_address" varchar
);
--> statement-breakpoint
CREATE TABLE "document_type" (
"id" bigserial PRIMARY KEY NOT NULL,
"name" varchar NOT NULL
);
--> statement-breakpoint
CREATE TABLE "project" (
"id" bigserial PRIMARY KEY NOT NULL,
"name" varchar NOT NULL,
@ -55,7 +50,7 @@ CREATE TABLE "project_workstream_contractor" (
CREATE TABLE "project_workstream_evidence_requirement" (
"id" bigserial PRIMARY KEY NOT NULL,
"project_workstream_id" bigint NOT NULL,
"document_type_id" bigint NOT NULL,
"file_type" "file_type" NOT NULL,
"project_workstream_stage_id" bigint,
"required" boolean DEFAULT true NOT NULL,
"naming_rule" varchar
@ -104,7 +99,6 @@ ALTER TABLE "project_workstream" ADD CONSTRAINT "project_workstream_current_stag
ALTER TABLE "project_workstream_contractor" ADD CONSTRAINT "project_workstream_contractor_project_workstream_id_project_workstream_id_fk" FOREIGN KEY ("project_workstream_id") REFERENCES "public"."project_workstream"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "project_workstream_contractor" ADD CONSTRAINT "project_workstream_contractor_contractor_id_contractor_id_fk" FOREIGN KEY ("contractor_id") REFERENCES "public"."contractor"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "project_workstream_evidence_requirement" ADD CONSTRAINT "project_workstream_evidence_requirement_project_workstream_id_project_workstream_id_fk" FOREIGN KEY ("project_workstream_id") REFERENCES "public"."project_workstream"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "project_workstream_evidence_requirement" ADD CONSTRAINT "project_workstream_evidence_requirement_document_type_id_document_type_id_fk" FOREIGN KEY ("document_type_id") REFERENCES "public"."document_type"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "project_workstream_evidence_requirement" ADD CONSTRAINT "project_workstream_evidence_requirement_project_workstream_stage_id_project_workstream_stage_id_fk" FOREIGN KEY ("project_workstream_stage_id") REFERENCES "public"."project_workstream_stage"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "project_workstream_stage" ADD CONSTRAINT "project_workstream_stage_project_workstream_id_project_workstream_id_fk" FOREIGN KEY ("project_workstream_id") REFERENCES "public"."project_workstream"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "work_order" ADD CONSTRAINT "work_order_project_workstream_contractor_id_project_workstream_contractor_id_fk" FOREIGN KEY ("project_workstream_contractor_id") REFERENCES "public"."project_workstream_contractor"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint

View file

@ -1,5 +1,5 @@
{
"id": "d7c0bcad-5b51-4018-8d6f-f07c8ba85414",
"id": "d48f1f71-fbb0-439d-983e-0a96db03c521",
"prevId": "44eb87f5-2979-43c6-a96e-ba128b3d79a0",
"version": "7",
"dialect": "postgresql",
@ -4782,31 +4782,6 @@
"checkConstraints": {},
"isRLSEnabled": false
},
"public.document_type": {
"name": "document_type",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "bigserial",
"primaryKey": true,
"notNull": true
},
"name": {
"name": "name",
"type": "varchar",
"primaryKey": false,
"notNull": true
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.project": {
"name": "project",
"schema": "",
@ -5172,9 +5147,10 @@
"primaryKey": false,
"notNull": true
},
"document_type_id": {
"name": "document_type_id",
"type": "bigint",
"file_type": {
"name": "file_type",
"type": "file_type",
"typeSchema": "public",
"primaryKey": false,
"notNull": true
},
@ -5213,19 +5189,6 @@
"onDelete": "no action",
"onUpdate": "no action"
},
"project_workstream_evidence_requirement_document_type_id_document_type_id_fk": {
"name": "project_workstream_evidence_requirement_document_type_id_document_type_id_fk",
"tableFrom": "project_workstream_evidence_requirement",
"tableTo": "document_type",
"columnsFrom": [
"document_type_id"
],
"columnsTo": [
"id"
],
"onDelete": "no action",
"onUpdate": "no action"
},
"project_workstream_evidence_requirement_project_workstream_stage_id_project_workstream_stage_id_fk": {
"name": "project_workstream_evidence_requirement_project_workstream_stage_id_project_workstream_stage_id_fk",
"tableFrom": "project_workstream_evidence_requirement",

View file

@ -1909,8 +1909,8 @@
{
"idx": 273,
"version": "7",
"when": 1784302480213,
"tag": "0273_fixed_doctor_spectrum",
"when": 1784305436995,
"tag": "0273_smiling_skullbuster",
"breakpoints": true
}
]

View file

@ -25,6 +25,7 @@ import {
import { InferModel } from "drizzle-orm";
import { property } from "../property";
import { organisation } from "../organisation";
import { fileType } from "../uploaded_files_enums";
// Reference data: the available project types (e.g. Retrofit, New Build).
export const projectType = pgTable("project_type", {
@ -146,13 +147,10 @@ export const projectWorkstreamContractor = pgTable(
}
);
// Reference data: the documents that may be requested.
export const documentType = pgTable("document_type", {
id: bigserial("id", { mode: "bigint" }).primaryKey(),
name: varchar("name").notNull(),
});
// Configuration describing which evidence is required.
// Configuration describing which evidence is required. The required document is
// expressed as a `file_type` enum value (reusing the existing uploaded_files
// taxonomy) rather than a separate document_type table, so a submitted file's
// `file_type` can be matched directly against what the requirement expects.
export const projectWorkstreamEvidenceRequirement = pgTable(
"project_workstream_evidence_requirement",
{
@ -160,9 +158,7 @@ export const projectWorkstreamEvidenceRequirement = pgTable(
projectWorkstreamId: bigint("project_workstream_id", { mode: "bigint" })
.notNull()
.references(() => projectWorkstream.id),
documentTypeId: bigint("document_type_id", { mode: "bigint" })
.notNull()
.references(() => documentType.id),
fileType: fileType("file_type").notNull(),
// Null when the requirement applies to the workstream as a whole.
projectWorkstreamStageId: bigint("project_workstream_stage_id", {
mode: "bigint",
@ -211,7 +207,6 @@ export type ProjectWorkstreamContractor = InferModel<
typeof projectWorkstreamContractor,
"select"
>;
export type DocumentType = InferModel<typeof documentType, "select">;
export type ProjectWorkstreamEvidenceRequirement = InferModel<
typeof projectWorkstreamEvidenceRequirement,
"select"

View file

@ -63,6 +63,9 @@ Project
> `pgEnum` once the values are agreed.
> - **No new `uploaded_by` column** on `uploaded_files`; the existing
> `uploaded_by` `bigint` FK → user is reused.
> - **No `document_type` table.** Evidence requirements reference the existing
> `file_type` enum instead, so the required and submitted document kinds share
> one taxonomy.
Conventions: bigint autoincrement primary keys for all new tables (and bigint FKs between them); FKs to pre-existing tables match the referenced PK's type. `timestamp` columns default to `now()`. Status-like columns were proposed as Postgres enums but are implemented as `text` for now (values TBC).
@ -207,37 +210,24 @@ Represents a contractor assigned to deliver a Project Workstream.
---
## Document Type
Reference data describing the documents that may be requested.
Examples:
- Completion Photo
- RAMS
- Insurance Certificate
- Warranty
- Handover Pack
| Column | Type | Nullable | Notes |
| ------ | ------- | -------- | ----------- |
| id | bigint | No | Primary Key |
| name | varchar | No | |
---
## Project Workstream Evidence Requirement
Configuration describing which evidence is required.
| Column | Type | Nullable | Notes |
| --------------------------- | ------- | -------- | -------------------------------------------------------------------------------------------------------------------------- |
| id | bigint | No | Primary Key |
| project_workstream_id | bigint | No | FK → Project Workstream |
| document_type_id | bigint | No | FK → Document Type |
| project_workstream_stage_id | bigint | Yes | FK → Project Workstream Stage; null when the requirement applies to the workstream as a whole rather than a specific stage |
| required | boolean | No | Default `true` |
| naming_rule | varchar | Yes | Optional regex/template |
The required document is expressed as a `file_type` **enum** value, reusing the
existing `uploaded_files.file_type` taxonomy, so a submitted file's `file_type`
can be matched directly against what the requirement expects. _(The originally
proposed separate `document_type` reference table was dropped in favour of this
reuse — see the implementation note at the top.)_
| Column | Type | Nullable | Notes |
| --------------------------- | --------- | -------- | -------------------------------------------------------------------------------------------------------------------------- |
| id | bigint | No | Primary Key |
| project_workstream_id | bigint | No | FK → Project Workstream |
| file_type | file_type | No | Postgres enum (existing `uploaded_files` `file_type`); the required document kind |
| project_workstream_stage_id | bigint | Yes | FK → Project Workstream Stage; null when the requirement applies to the workstream as a whole rather than a specific stage |
| required | boolean | No | Default `true` |
| naming_rule | varchar | Yes | Optional regex/template |
---

View file

@ -33,7 +33,6 @@ import {
projectWorkstream,
projectWorkstreamStage,
projectWorkstreamContractor,
documentType,
projectWorkstreamEvidenceRequirement,
workOrder,
} from "./projects/projects";
@ -313,10 +312,6 @@ export const projectWorkstreamContractorRelations = relations(
})
);
export const documentTypeRelations = relations(documentType, ({ many }) => ({
evidenceRequirements: many(projectWorkstreamEvidenceRequirement),
}));
export const projectWorkstreamEvidenceRequirementRelations = relations(
projectWorkstreamEvidenceRequirement,
({ one, many }) => ({
@ -324,10 +319,6 @@ export const projectWorkstreamEvidenceRequirementRelations = relations(
fields: [projectWorkstreamEvidenceRequirement.projectWorkstreamId],
references: [projectWorkstream.id],
}),
documentType: one(documentType, {
fields: [projectWorkstreamEvidenceRequirement.documentTypeId],
references: [documentType.id],
}),
stage: one(projectWorkstreamStage, {
fields: [projectWorkstreamEvidenceRequirement.projectWorkstreamStageId],
references: [projectWorkstreamStage.id],

View file

@ -1,84 +1,14 @@
import { bigint, bigserial, pgEnum, pgTable, text, timestamp } from "drizzle-orm/pg-core";
import { bigint, bigserial, pgTable, text, timestamp } from "drizzle-orm/pg-core";
import { user } from "./users";
import {
projectWorkstream,
projectWorkstreamContractor,
projectWorkstreamEvidenceRequirement,
} from "./projects/projects";
import { fileType, fileSource } from "./uploaded_files_enums";
export const fileType = pgEnum("file_type", [
// Assessment
"photo_pack",
"site_note",
"rd_sap_site_note",
"pas_2023_ventilation",
"pas_2023_condition",
"pas_significance",
"par_photo_pack",
"pas_2023_property",
"pas_2023_occupancy",
"ecmk_site_note",
"ecmk_rd_sap_site_note",
"ecmk_survey_xml",
// Contractor install documentation
// Photos
"pre_photo",
"mid_photo",
"post_photo",
"loft_hatch_photo",
"dmev_photos",
"door_undercut_photos",
"trickle_vent_photos",
// Pre-installation
"pre_installation_building_inspection",
"point_of_work_risk_assessment",
// Compliance & lodgement
"claim_of_compliance",
"mcs_compliance_certificate",
"certificate_of_conformity",
"minor_works_electrical_certificate",
"trustmark_licence_numbers",
"operative_competency",
// Ventilation
"ventilation_assessment_checklist",
"anemometer_readings",
"commissioning_records",
"part_f_ventilation_document",
// Handover & warranties
"handover_pack",
"insurance_guarantee",
"workmanship_warranty",
"g98_notification",
// Qualifications & other
"installer_qualifications",
"installer_feedback",
"contractor_other",
// MagicPlan
"magic_plan_json",
// Coordination
"improvement_option_evaluation",
"medium_term_improvement_plan",
// Design
"retrofit_design_doc",
// Audit
"ventilation_audit",
// Other
"other"
]);
export const fileSource = pgEnum("file_source", [
"pas hub",
"sharepoint",
"hubspot",
"ecmk",
"contractor",
"magic_plan",
"coordination_hub",
"audit_generator",
// Uploads originating from the Projects module.
"projects"
]);
// Re-exported so existing importers of these enums keep working.
export { fileType, fileSource };
export const uploadedFiles = pgTable(
"uploaded_files",

View file

@ -0,0 +1,83 @@
// Enums for the uploaded_files table.
//
// These live in their own module (rather than in uploaded_files.ts) so that
// other schema files — notably the Projects module — can reference `fileType`
// without creating an import cycle: uploaded_files.ts imports the Projects
// tables for its FK columns, and the Projects evidence-requirement table
// imports `fileType` from here. Keeping the enums dependency-free breaks that
// cycle. uploaded_files.ts re-exports these for backwards compatibility.
import { pgEnum } from "drizzle-orm/pg-core";
export const fileType = pgEnum("file_type", [
// Assessment
"photo_pack",
"site_note",
"rd_sap_site_note",
"pas_2023_ventilation",
"pas_2023_condition",
"pas_significance",
"par_photo_pack",
"pas_2023_property",
"pas_2023_occupancy",
"ecmk_site_note",
"ecmk_rd_sap_site_note",
"ecmk_survey_xml",
// Contractor install documentation
// Photos
"pre_photo",
"mid_photo",
"post_photo",
"loft_hatch_photo",
"dmev_photos",
"door_undercut_photos",
"trickle_vent_photos",
// Pre-installation
"pre_installation_building_inspection",
"point_of_work_risk_assessment",
// Compliance & lodgement
"claim_of_compliance",
"mcs_compliance_certificate",
"certificate_of_conformity",
"minor_works_electrical_certificate",
"trustmark_licence_numbers",
"operative_competency",
// Ventilation
"ventilation_assessment_checklist",
"anemometer_readings",
"commissioning_records",
"part_f_ventilation_document",
// Handover & warranties
"handover_pack",
"insurance_guarantee",
"workmanship_warranty",
"g98_notification",
// Qualifications & other
"installer_qualifications",
"installer_feedback",
"contractor_other",
// MagicPlan
"magic_plan_json",
// Coordination
"improvement_option_evaluation",
"medium_term_improvement_plan",
// Design
"retrofit_design_doc",
// Audit
"ventilation_audit",
// Other
"other"
]);
export const fileSource = pgEnum("file_source", [
"pas hub",
"sharepoint",
"hubspot",
"ecmk",
"contractor",
"magic_plan",
"coordination_hub",
"audit_generator",
// Uploads originating from the Projects module.
"projects"
]);