feat: implement incident reporting and management system
- Add incident reporting routes and logic to handle creation, updating, and retrieval of incidents. - Implement visibility rules for users based on roles (reporters, supervisors). - Create tests for incident reporting functionality to ensure correctness and reliability. - Introduce site and area management routes, including validation for area hierarchy. - Establish user management routes with role-based access control for creating and updating users. - Add setup routes for initializing the application with an organization and admin user. - Implement shared enums and schemas for consistent validation and data handling across the application. - Create a test helper for setting up an in-memory database and API client for testing. - Add TypeScript configuration for server-side code with strict type checking and module resolution.
This commit is contained in:
1 parent
a382fa904a
commit
c69eccb5c1
47 files changed
+6297
-45
No files matched your search
@@ -0,0 +1,225 @@
|
||||
CREATE TYPE "public"."action_kind" AS ENUM('corrective', 'preventive');--> statement-breakpoint
|
||||
CREATE TYPE "public"."action_status" AS ENUM('open', 'in_progress', 'done', 'verified', 'cancelled');--> statement-breakpoint
|
||||
CREATE TYPE "public"."area_kind" AS ENUM('building', 'area', 'line', 'other');--> statement-breakpoint
|
||||
CREATE TYPE "public"."employment_type" AS ENUM('employee', 'temporary', 'contractor', 'visitor');--> statement-breakpoint
|
||||
CREATE TYPE "public"."incident_status" AS ENUM('reported', 'triage', 'investigation', 'actions', 'verification', 'closed');--> statement-breakpoint
|
||||
CREATE TYPE "public"."incident_type" AS ENUM('near_miss', 'hazard', 'injury', 'illness', 'property_damage', 'environmental');--> statement-breakpoint
|
||||
CREATE TYPE "public"."rca_method" AS ENUM('five_whys', 'fishbone', 'other');--> statement-breakpoint
|
||||
CREATE TYPE "public"."role" AS ENUM('reporter', 'supervisor', 'safety_manager', 'admin');--> statement-breakpoint
|
||||
CREATE TYPE "public"."root_cause_category" AS ENUM('people', 'process', 'equipment', 'materials', 'environment', 'management');--> statement-breakpoint
|
||||
CREATE TYPE "public"."severity" AS ENUM('low', 'medium', 'high', 'critical');--> statement-breakpoint
|
||||
CREATE TYPE "public"."treatment_level" AS ENUM('none', 'first_aid', 'medical_treatment', 'hospitalized', 'fatality');--> statement-breakpoint
|
||||
CREATE TABLE "areas" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"site_id" uuid NOT NULL,
|
||||
"parent_id" uuid,
|
||||
"name" text NOT NULL,
|
||||
"kind" "area_kind" DEFAULT 'area' NOT NULL,
|
||||
"archived_at" timestamp with time zone,
|
||||
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
|
||||
"updated_at" timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "attachments" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"incident_id" uuid NOT NULL,
|
||||
"file_name" text NOT NULL,
|
||||
"content_type" text NOT NULL,
|
||||
"size_bytes" integer NOT NULL,
|
||||
"storage_key" text NOT NULL,
|
||||
"uploaded_by_id" uuid,
|
||||
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
|
||||
CONSTRAINT "attachments_storageKey_unique" UNIQUE("storage_key")
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "audit_log" (
|
||||
"id" bigserial PRIMARY KEY NOT NULL,
|
||||
"organization_id" uuid NOT NULL,
|
||||
"actor_id" uuid,
|
||||
"entity_type" text NOT NULL,
|
||||
"entity_id" text NOT NULL,
|
||||
"action" text NOT NULL,
|
||||
"changes" jsonb,
|
||||
"created_at" timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "comments" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"incident_id" uuid NOT NULL,
|
||||
"author_id" uuid,
|
||||
"body" text NOT NULL,
|
||||
"created_at" timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "corrective_actions" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"incident_id" uuid NOT NULL,
|
||||
"root_cause_id" uuid,
|
||||
"kind" "action_kind" DEFAULT 'corrective' NOT NULL,
|
||||
"title" text NOT NULL,
|
||||
"description" text,
|
||||
"assignee_id" uuid,
|
||||
"due_date" date,
|
||||
"status" "action_status" DEFAULT 'open' NOT NULL,
|
||||
"completed_at" timestamp with time zone,
|
||||
"verified_by_id" uuid,
|
||||
"verified_at" timestamp with time zone,
|
||||
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
|
||||
"updated_at" timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "hours_worked" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"site_id" uuid NOT NULL,
|
||||
"month" date NOT NULL,
|
||||
"hours" numeric(12, 2) NOT NULL,
|
||||
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
|
||||
"updated_at" timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "incident_counters" (
|
||||
"organization_id" uuid PRIMARY KEY NOT NULL,
|
||||
"last_number" integer DEFAULT 0 NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "incidents" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"organization_id" uuid NOT NULL,
|
||||
"number" integer NOT NULL,
|
||||
"title" text NOT NULL,
|
||||
"type" "incident_type" NOT NULL,
|
||||
"severity" "severity" NOT NULL,
|
||||
"status" "incident_status" DEFAULT 'reported' NOT NULL,
|
||||
"occurred_at" timestamp with time zone NOT NULL,
|
||||
"site_id" uuid NOT NULL,
|
||||
"area_id" uuid,
|
||||
"location_detail" text,
|
||||
"description" text,
|
||||
"immediate_action" text,
|
||||
"reporter_id" uuid,
|
||||
"anonymous" boolean DEFAULT false NOT NULL,
|
||||
"closed_at" timestamp with time zone,
|
||||
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
|
||||
"updated_at" timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "injured_persons" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"incident_id" uuid NOT NULL,
|
||||
"user_id" uuid,
|
||||
"name" text NOT NULL,
|
||||
"job_title" text,
|
||||
"employment_type" "employment_type" DEFAULT 'employee' NOT NULL,
|
||||
"body_part" text,
|
||||
"nature_of_injury" text,
|
||||
"treatment" "treatment_level" DEFAULT 'none' NOT NULL,
|
||||
"days_away" integer DEFAULT 0 NOT NULL,
|
||||
"days_restricted" integer DEFAULT 0 NOT NULL,
|
||||
"privacy_case" boolean DEFAULT false NOT NULL,
|
||||
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
|
||||
"updated_at" timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "investigations" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"incident_id" uuid NOT NULL,
|
||||
"lead_id" uuid,
|
||||
"method" "rca_method" DEFAULT 'five_whys' NOT NULL,
|
||||
"summary" text,
|
||||
"started_at" timestamp with time zone DEFAULT now() NOT NULL,
|
||||
"completed_at" timestamp with time zone,
|
||||
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
|
||||
"updated_at" timestamp with time zone DEFAULT now() NOT NULL,
|
||||
CONSTRAINT "investigations_incidentId_unique" UNIQUE("incident_id")
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "organizations" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"name" text NOT NULL,
|
||||
"created_at" timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "root_causes" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"investigation_id" uuid NOT NULL,
|
||||
"category" "root_cause_category" NOT NULL,
|
||||
"description" text NOT NULL,
|
||||
"whys" jsonb DEFAULT '[]'::jsonb NOT NULL,
|
||||
"created_at" timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "sessions" (
|
||||
"id" text PRIMARY KEY NOT NULL,
|
||||
"user_id" uuid NOT NULL,
|
||||
"expires_at" timestamp with time zone NOT NULL,
|
||||
"created_at" timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "sites" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"organization_id" uuid NOT NULL,
|
||||
"name" text NOT NULL,
|
||||
"address" text,
|
||||
"timezone" text DEFAULT 'UTC' NOT NULL,
|
||||
"archived_at" timestamp with time zone,
|
||||
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
|
||||
"updated_at" timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "users" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"organization_id" uuid NOT NULL,
|
||||
"email" text NOT NULL,
|
||||
"name" text NOT NULL,
|
||||
"role" "role" DEFAULT 'reporter' NOT NULL,
|
||||
"password_hash" text,
|
||||
"active" boolean DEFAULT true NOT NULL,
|
||||
"last_login_at" timestamp with time zone,
|
||||
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
|
||||
"updated_at" timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
ALTER TABLE "areas" ADD CONSTRAINT "areas_site_id_sites_id_fk" FOREIGN KEY ("site_id") REFERENCES "public"."sites"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "areas" ADD CONSTRAINT "areas_parent_id_areas_id_fk" FOREIGN KEY ("parent_id") REFERENCES "public"."areas"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "attachments" ADD CONSTRAINT "attachments_incident_id_incidents_id_fk" FOREIGN KEY ("incident_id") REFERENCES "public"."incidents"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "attachments" ADD CONSTRAINT "attachments_uploaded_by_id_users_id_fk" FOREIGN KEY ("uploaded_by_id") REFERENCES "public"."users"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "audit_log" ADD CONSTRAINT "audit_log_organization_id_organizations_id_fk" FOREIGN KEY ("organization_id") REFERENCES "public"."organizations"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "audit_log" ADD CONSTRAINT "audit_log_actor_id_users_id_fk" FOREIGN KEY ("actor_id") REFERENCES "public"."users"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "comments" ADD CONSTRAINT "comments_incident_id_incidents_id_fk" FOREIGN KEY ("incident_id") REFERENCES "public"."incidents"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "comments" ADD CONSTRAINT "comments_author_id_users_id_fk" FOREIGN KEY ("author_id") REFERENCES "public"."users"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "corrective_actions" ADD CONSTRAINT "corrective_actions_incident_id_incidents_id_fk" FOREIGN KEY ("incident_id") REFERENCES "public"."incidents"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "corrective_actions" ADD CONSTRAINT "corrective_actions_root_cause_id_root_causes_id_fk" FOREIGN KEY ("root_cause_id") REFERENCES "public"."root_causes"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "corrective_actions" ADD CONSTRAINT "corrective_actions_assignee_id_users_id_fk" FOREIGN KEY ("assignee_id") REFERENCES "public"."users"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "corrective_actions" ADD CONSTRAINT "corrective_actions_verified_by_id_users_id_fk" FOREIGN KEY ("verified_by_id") REFERENCES "public"."users"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "hours_worked" ADD CONSTRAINT "hours_worked_site_id_sites_id_fk" FOREIGN KEY ("site_id") REFERENCES "public"."sites"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "incident_counters" ADD CONSTRAINT "incident_counters_organization_id_organizations_id_fk" FOREIGN KEY ("organization_id") REFERENCES "public"."organizations"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "incidents" ADD CONSTRAINT "incidents_organization_id_organizations_id_fk" FOREIGN KEY ("organization_id") REFERENCES "public"."organizations"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "incidents" ADD CONSTRAINT "incidents_site_id_sites_id_fk" FOREIGN KEY ("site_id") REFERENCES "public"."sites"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "incidents" ADD CONSTRAINT "incidents_area_id_areas_id_fk" FOREIGN KEY ("area_id") REFERENCES "public"."areas"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "incidents" ADD CONSTRAINT "incidents_reporter_id_users_id_fk" FOREIGN KEY ("reporter_id") REFERENCES "public"."users"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "injured_persons" ADD CONSTRAINT "injured_persons_incident_id_incidents_id_fk" FOREIGN KEY ("incident_id") REFERENCES "public"."incidents"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "injured_persons" ADD CONSTRAINT "injured_persons_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "investigations" ADD CONSTRAINT "investigations_incident_id_incidents_id_fk" FOREIGN KEY ("incident_id") REFERENCES "public"."incidents"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "investigations" ADD CONSTRAINT "investigations_lead_id_users_id_fk" FOREIGN KEY ("lead_id") REFERENCES "public"."users"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "root_causes" ADD CONSTRAINT "root_causes_investigation_id_investigations_id_fk" FOREIGN KEY ("investigation_id") REFERENCES "public"."investigations"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "sessions" ADD CONSTRAINT "sessions_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "sites" ADD CONSTRAINT "sites_organization_id_organizations_id_fk" FOREIGN KEY ("organization_id") REFERENCES "public"."organizations"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "users" ADD CONSTRAINT "users_organization_id_organizations_id_fk" FOREIGN KEY ("organization_id") REFERENCES "public"."organizations"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
|
||||
CREATE INDEX "areas_site_idx" ON "areas" USING btree ("site_id");--> statement-breakpoint
|
||||
CREATE INDEX "areas_parent_idx" ON "areas" USING btree ("parent_id");--> statement-breakpoint
|
||||
CREATE INDEX "attachments_incident_idx" ON "attachments" USING btree ("incident_id");--> statement-breakpoint
|
||||
CREATE INDEX "audit_log_entity_idx" ON "audit_log" USING btree ("organization_id","entity_type","entity_id");--> statement-breakpoint
|
||||
CREATE INDEX "comments_incident_idx" ON "comments" USING btree ("incident_id");--> statement-breakpoint
|
||||
CREATE INDEX "corrective_actions_incident_idx" ON "corrective_actions" USING btree ("incident_id");--> statement-breakpoint
|
||||
CREATE INDEX "corrective_actions_assignee_status_idx" ON "corrective_actions" USING btree ("assignee_id","status");--> statement-breakpoint
|
||||
CREATE UNIQUE INDEX "hours_worked_site_month_unique" ON "hours_worked" USING btree ("site_id","month");--> statement-breakpoint
|
||||
CREATE UNIQUE INDEX "incidents_organization_number_unique" ON "incidents" USING btree ("organization_id","number");--> statement-breakpoint
|
||||
CREATE INDEX "incidents_organization_occurred_idx" ON "incidents" USING btree ("organization_id","occurred_at");--> statement-breakpoint
|
||||
CREATE INDEX "incidents_organization_status_idx" ON "incidents" USING btree ("organization_id","status");--> statement-breakpoint
|
||||
CREATE INDEX "incidents_reporter_idx" ON "incidents" USING btree ("reporter_id");--> statement-breakpoint
|
||||
CREATE INDEX "injured_persons_incident_idx" ON "injured_persons" USING btree ("incident_id");--> statement-breakpoint
|
||||
CREATE INDEX "root_causes_investigation_idx" ON "root_causes" USING btree ("investigation_id");--> statement-breakpoint
|
||||
CREATE INDEX "sessions_user_idx" ON "sessions" USING btree ("user_id");--> statement-breakpoint
|
||||
CREATE UNIQUE INDEX "sites_organization_name_unique" ON "sites" USING btree ("organization_id","name");--> statement-breakpoint
|
||||
CREATE UNIQUE INDEX "users_email_unique" ON "users" USING btree ("email");--> statement-breakpoint
|
||||
CREATE INDEX "users_organization_idx" ON "users" USING btree ("organization_id");
|
||||
Reference in new issue
Block a user