From 7b1407268bc8a6db7fd4ce2a251907edbef9f002 Mon Sep 17 00:00:00 2001 From: "CTO (LegalAI)" Date: Thu, 9 Apr 2026 10:18:56 +0000 Subject: [PATCH] feat: Generisches Dokument-Upload-System fuer Entscheidungen, Normen und Falldokumente - Neues documents-Schema mit Mandantentrennung (tenantId), Kategorien (entscheidung/norm/falldokument/sonstiges) und optionaler Verknuepfung zu cases/decisions/normInstruments - Upload-Library (src/lib/documents/) mit Datei-Upload, PDF/DOCX-Textextraktion und gefilterten Listen - API-Route POST/GET /api/documents mit RBAC, Audit-Logging und asynchroner Textextraktion - Wiederverwendbare DokumentUpload-Komponente mit Drag-and-Drop, Fortschrittsanzeige und Dateiliste - Integration in Fall-Detailseite, Entscheidungs-Detailseite und Normen-Detailseite - Drizzle-Migration fuer documents-Tabelle mit RLS-konformer Mandantentrennung - DSGVO: 90-Tage Aufbewahrungsfrist fuer hochgeladene Dokumente Co-Authored-By: Paperclip --- drizzle/0002_wide_grandmaster.sql | 112 + drizzle/meta/0002_snapshot.json | 4023 +++++++++++++++++ drizzle/meta/_journal.json | 7 + src/app/(dashboard)/cases/[id]/page.tsx | 7 + .../(dashboard)/entscheidungen/[id]/page.tsx | 7 + .../normen/[instrumentId]/page.tsx | 7 + src/app/api/documents/route.ts | 97 + src/components/documents/dokument-upload.tsx | 206 + src/lib/db/schema.ts | 77 + src/lib/documents/index.ts | 224 + 10 files changed, 4767 insertions(+) create mode 100644 drizzle/0002_wide_grandmaster.sql create mode 100644 drizzle/meta/0002_snapshot.json create mode 100644 src/app/api/documents/route.ts create mode 100644 src/components/documents/dokument-upload.tsx create mode 100644 src/lib/documents/index.ts diff --git a/drizzle/0002_wide_grandmaster.sql b/drizzle/0002_wide_grandmaster.sql new file mode 100644 index 0000000..639fe49 --- /dev/null +++ b/drizzle/0002_wide_grandmaster.sql @@ -0,0 +1,112 @@ +CREATE TYPE "public"."compensation_component" AS ENUM('grundgage', 'ortszuschlag', 'kinderzuschlag', 'dienstalterszulage', 'funktionszulage', 'sonderzahlung', 'probenzuschlag');--> statement-breakpoint +CREATE TYPE "public"."contract_status" AS ENUM('aktiv', 'gekuendigt', 'nichtverlaengert', 'ausgelaufen', 'ruhend');--> statement-breakpoint +CREATE TYPE "public"."document_category" AS ENUM('entscheidung', 'norm', 'falldokument', 'sonstiges');--> statement-breakpoint +CREATE TYPE "public"."document_status" AS ENUM('uploaded', 'extracting', 'extracted', 'failed');--> statement-breakpoint +CREATE TABLE "compensation_rules" ( + "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, + "tenant_id" uuid, + "fachgruppe_id" uuid, + "component" "compensation_component" NOT NULL, + "gagenklasse" varchar(50), + "label" varchar(255) NOT NULL, + "amount_cents" integer NOT NULL, + "min_years_of_service" integer DEFAULT 0, + "max_years_of_service" integer, + "valid_from_spielzeit" varchar(20), + "valid_to_spielzeit" varchar(20), + "legal_basis" varchar(255), + "description" text, + "sort_order" integer DEFAULT 0, + "created_at" timestamp with time zone DEFAULT now() NOT NULL +); +--> statement-breakpoint +CREATE TABLE "contracts" ( + "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, + "tenant_id" uuid NOT NULL, + "case_id" uuid, + "artist_name" varchar(255) NOT NULL, + "theater_name" varchar(255) NOT NULL, + "fachgruppe_id" uuid, + "status" "contract_status" DEFAULT 'aktiv' NOT NULL, + "contract_start" date NOT NULL, + "contract_end" date NOT NULL, + "years_of_service" integer DEFAULT 0 NOT NULL, + "is_first_engagement" boolean DEFAULT false, + "is_over_55" boolean DEFAULT false, + "current_spielzeit" varchar(20), + "gagenklasse" varchar(50), + "monthly_gross_cents" integer, + "notes" text, + "metadata" jsonb, + "created_at" timestamp with time zone DEFAULT now() NOT NULL, + "updated_at" timestamp with time zone DEFAULT now() NOT NULL +); +--> statement-breakpoint +CREATE TABLE "documents" ( + "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, + "tenant_id" uuid NOT NULL, + "user_id" uuid NOT NULL, + "category" "document_category" DEFAULT 'sonstiges' NOT NULL, + "case_id" uuid, + "decision_id" uuid, + "norm_instrument_id" uuid, + "filename" varchar(500) NOT NULL, + "mime_type" varchar(100) NOT NULL, + "file_size_bytes" integer NOT NULL, + "storage_path" text NOT NULL, + "extracted_text" text, + "status" "document_status" DEFAULT 'uploaded' NOT NULL, + "error_message" text, + "metadata" jsonb, + "delete_after" 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 "non_renewal_deadlines" ( + "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, + "contract_id" uuid NOT NULL, + "spielzeit" varchar(20) NOT NULL, + "deadline_date" date NOT NULL, + "warning_date" date, + "warning_days_before" integer DEFAULT 30, + "notice_sent" boolean DEFAULT false, + "notice_sent_date" date, + "is_auto_renewed" boolean DEFAULT false, + "legal_basis" varchar(255) NOT NULL, + "calculation_basis" text, + "protection_category" varchar(100), + "notes" text, + "created_at" timestamp with time zone DEFAULT now() NOT NULL +); +--> statement-breakpoint +ALTER TABLE "compensation_rules" ADD CONSTRAINT "compensation_rules_tenant_id_tenants_id_fk" FOREIGN KEY ("tenant_id") REFERENCES "public"."tenants"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "compensation_rules" ADD CONSTRAINT "compensation_rules_fachgruppe_id_nv_buehne_fachgruppen_id_fk" FOREIGN KEY ("fachgruppe_id") REFERENCES "public"."nv_buehne_fachgruppen"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "contracts" ADD CONSTRAINT "contracts_tenant_id_tenants_id_fk" FOREIGN KEY ("tenant_id") REFERENCES "public"."tenants"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "contracts" ADD CONSTRAINT "contracts_case_id_cases_id_fk" FOREIGN KEY ("case_id") REFERENCES "public"."cases"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "contracts" ADD CONSTRAINT "contracts_fachgruppe_id_nv_buehne_fachgruppen_id_fk" FOREIGN KEY ("fachgruppe_id") REFERENCES "public"."nv_buehne_fachgruppen"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "documents" ADD CONSTRAINT "documents_tenant_id_tenants_id_fk" FOREIGN KEY ("tenant_id") REFERENCES "public"."tenants"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "documents" ADD CONSTRAINT "documents_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "documents" ADD CONSTRAINT "documents_case_id_cases_id_fk" FOREIGN KEY ("case_id") REFERENCES "public"."cases"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "documents" ADD CONSTRAINT "documents_decision_id_decisions_id_fk" FOREIGN KEY ("decision_id") REFERENCES "public"."decisions"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "documents" ADD CONSTRAINT "documents_norm_instrument_id_norm_instruments_id_fk" FOREIGN KEY ("norm_instrument_id") REFERENCES "public"."norm_instruments"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "non_renewal_deadlines" ADD CONSTRAINT "non_renewal_deadlines_contract_id_contracts_id_fk" FOREIGN KEY ("contract_id") REFERENCES "public"."contracts"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +CREATE INDEX "compensation_rules_tenant_idx" ON "compensation_rules" USING btree ("tenant_id");--> statement-breakpoint +CREATE INDEX "compensation_rules_fachgruppe_idx" ON "compensation_rules" USING btree ("fachgruppe_id");--> statement-breakpoint +CREATE INDEX "compensation_rules_component_idx" ON "compensation_rules" USING btree ("component");--> statement-breakpoint +CREATE INDEX "compensation_rules_gagenklasse_idx" ON "compensation_rules" USING btree ("gagenklasse");--> statement-breakpoint +CREATE INDEX "contracts_tenant_idx" ON "contracts" USING btree ("tenant_id");--> statement-breakpoint +CREATE INDEX "contracts_case_idx" ON "contracts" USING btree ("case_id");--> statement-breakpoint +CREATE INDEX "contracts_fachgruppe_idx" ON "contracts" USING btree ("fachgruppe_id");--> statement-breakpoint +CREATE INDEX "contracts_status_idx" ON "contracts" USING btree ("status");--> statement-breakpoint +CREATE INDEX "contracts_end_idx" ON "contracts" USING btree ("contract_end");--> statement-breakpoint +CREATE INDEX "documents_tenant_idx" ON "documents" USING btree ("tenant_id");--> statement-breakpoint +CREATE INDEX "documents_case_idx" ON "documents" USING btree ("case_id");--> statement-breakpoint +CREATE INDEX "documents_decision_idx" ON "documents" USING btree ("decision_id");--> statement-breakpoint +CREATE INDEX "documents_norm_instrument_idx" ON "documents" USING btree ("norm_instrument_id");--> statement-breakpoint +CREATE INDEX "documents_category_idx" ON "documents" USING btree ("category");--> statement-breakpoint +CREATE INDEX "documents_status_idx" ON "documents" USING btree ("status");--> statement-breakpoint +CREATE INDEX "documents_delete_after_idx" ON "documents" USING btree ("delete_after");--> statement-breakpoint +CREATE INDEX "non_renewal_deadlines_contract_idx" ON "non_renewal_deadlines" USING btree ("contract_id");--> statement-breakpoint +CREATE INDEX "non_renewal_deadlines_date_idx" ON "non_renewal_deadlines" USING btree ("deadline_date");--> statement-breakpoint +CREATE INDEX "non_renewal_deadlines_spielzeit_idx" ON "non_renewal_deadlines" USING btree ("spielzeit"); \ No newline at end of file diff --git a/drizzle/meta/0002_snapshot.json b/drizzle/meta/0002_snapshot.json new file mode 100644 index 0000000..b3f5aa7 --- /dev/null +++ b/drizzle/meta/0002_snapshot.json @@ -0,0 +1,4023 @@ +{ + "id": "aa4e96aa-e2b2-4ae4-8e04-16ef4aae818f", + "prevId": "e62c26f7-4b34-4c87-9cce-db34b9789f60", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.analyses": { + "name": "analyses", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "tenant_id": { + "name": "tenant_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "case_id": { + "name": "case_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "mode": { + "name": "mode", + "type": "analysis_mode", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "analysis_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'draft'" + }, + "title": { + "name": "title", + "type": "varchar(500)", + "primaryKey": false, + "notNull": true + }, + "query": { + "name": "query", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "result": { + "name": "result", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sources": { + "name": "sources", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "ai_provider": { + "name": "ai_provider", + "type": "varchar(50)", + "primaryKey": false, + "notNull": false + }, + "ai_model": { + "name": "ai_model", + "type": "varchar(100)", + "primaryKey": false, + "notNull": false + }, + "token_usage": { + "name": "token_usage", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "analyses_tenant_idx": { + "name": "analyses_tenant_idx", + "columns": [ + { + "expression": "tenant_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "analyses_case_idx": { + "name": "analyses_case_idx", + "columns": [ + { + "expression": "case_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "analyses_mode_idx": { + "name": "analyses_mode_idx", + "columns": [ + { + "expression": "mode", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "analyses_tenant_id_tenants_id_fk": { + "name": "analyses_tenant_id_tenants_id_fk", + "tableFrom": "analyses", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "analyses_case_id_cases_id_fk": { + "name": "analyses_case_id_cases_id_fk", + "tableFrom": "analyses", + "tableTo": "cases", + "columnsFrom": [ + "case_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "analyses_user_id_users_id_fk": { + "name": "analyses_user_id_users_id_fk", + "tableFrom": "analyses", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.arbitration_tribunals": { + "name": "arbitration_tribunals", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "level": { + "name": "level", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true + }, + "seat": { + "name": "seat", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "bschgo_ref": { + "name": "bschgo_ref", + "type": "varchar(100)", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.audit_log": { + "name": "audit_log", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "tenant_id": { + "name": "tenant_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "action": { + "name": "action", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "entity_type": { + "name": "entity_type", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "entity_id": { + "name": "entity_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "details": { + "name": "details", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "ip_address": { + "name": "ip_address", + "type": "varchar(45)", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "audit_log_tenant_idx": { + "name": "audit_log_tenant_idx", + "columns": [ + { + "expression": "tenant_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "audit_log_entity_idx": { + "name": "audit_log_entity_idx", + "columns": [ + { + "expression": "entity_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "entity_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "audit_log_created_idx": { + "name": "audit_log_created_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "audit_log_tenant_id_tenants_id_fk": { + "name": "audit_log_tenant_id_tenants_id_fk", + "tableFrom": "audit_log", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "audit_log_user_id_users_id_fk": { + "name": "audit_log_user_id_users_id_fk", + "tableFrom": "audit_log", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.cases": { + "name": "cases", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "tenant_id": { + "name": "tenant_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "case_number": { + "name": "case_number", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "varchar(500)", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "client_name": { + "name": "client_name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "opposing_party": { + "name": "opposing_party", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "venue": { + "name": "venue", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "fachgruppe_id": { + "name": "fachgruppe_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "domains": { + "name": "domains", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'[]'::jsonb" + }, + "status": { + "name": "status", + "type": "varchar(50)", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "filing_date": { + "name": "filing_date", + "type": "date", + "primaryKey": false, + "notNull": false + }, + "hearing_date": { + "name": "hearing_date", + "type": "date", + "primaryKey": false, + "notNull": false + }, + "closed_at": { + "name": "closed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "cases_tenant_number_idx": { + "name": "cases_tenant_number_idx", + "columns": [ + { + "expression": "tenant_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "case_number", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "cases_tenant_idx": { + "name": "cases_tenant_idx", + "columns": [ + { + "expression": "tenant_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "cases_status_idx": { + "name": "cases_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "cases_tenant_id_tenants_id_fk": { + "name": "cases_tenant_id_tenants_id_fk", + "tableFrom": "cases", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "cases_fachgruppe_id_nv_buehne_fachgruppen_id_fk": { + "name": "cases_fachgruppe_id_nv_buehne_fachgruppen_id_fk", + "tableFrom": "cases", + "tableTo": "nv_buehne_fachgruppen", + "columnsFrom": [ + "fachgruppe_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.compensation_rules": { + "name": "compensation_rules", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "tenant_id": { + "name": "tenant_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "fachgruppe_id": { + "name": "fachgruppe_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "component": { + "name": "component", + "type": "compensation_component", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "gagenklasse": { + "name": "gagenklasse", + "type": "varchar(50)", + "primaryKey": false, + "notNull": false + }, + "label": { + "name": "label", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "amount_cents": { + "name": "amount_cents", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "min_years_of_service": { + "name": "min_years_of_service", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "max_years_of_service": { + "name": "max_years_of_service", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "valid_from_spielzeit": { + "name": "valid_from_spielzeit", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "valid_to_spielzeit": { + "name": "valid_to_spielzeit", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "legal_basis": { + "name": "legal_basis", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "compensation_rules_tenant_idx": { + "name": "compensation_rules_tenant_idx", + "columns": [ + { + "expression": "tenant_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "compensation_rules_fachgruppe_idx": { + "name": "compensation_rules_fachgruppe_idx", + "columns": [ + { + "expression": "fachgruppe_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "compensation_rules_component_idx": { + "name": "compensation_rules_component_idx", + "columns": [ + { + "expression": "component", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "compensation_rules_gagenklasse_idx": { + "name": "compensation_rules_gagenklasse_idx", + "columns": [ + { + "expression": "gagenklasse", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "compensation_rules_tenant_id_tenants_id_fk": { + "name": "compensation_rules_tenant_id_tenants_id_fk", + "tableFrom": "compensation_rules", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "compensation_rules_fachgruppe_id_nv_buehne_fachgruppen_id_fk": { + "name": "compensation_rules_fachgruppe_id_nv_buehne_fachgruppen_id_fk", + "tableFrom": "compensation_rules", + "tableTo": "nv_buehne_fachgruppen", + "columnsFrom": [ + "fachgruppe_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.contract_clauses": { + "name": "contract_clauses", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "document_id": { + "name": "document_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "category": { + "name": "category", + "type": "varchar(200)", + "primaryKey": false, + "notNull": true + }, + "extracted_text": { + "name": "extracted_text", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "position_start": { + "name": "position_start", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "position_end": { + "name": "position_end", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "standard_clause_id": { + "name": "standard_clause_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "rating": { + "name": "rating", + "type": "clause_rating", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'unbekannt'" + }, + "analysis": { + "name": "analysis", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "deviations": { + "name": "deviations", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "risk_score": { + "name": "risk_score", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "contract_clauses_doc_idx": { + "name": "contract_clauses_doc_idx", + "columns": [ + { + "expression": "document_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "contract_clauses_category_idx": { + "name": "contract_clauses_category_idx", + "columns": [ + { + "expression": "category", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "contract_clauses_rating_idx": { + "name": "contract_clauses_rating_idx", + "columns": [ + { + "expression": "rating", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "contract_clauses_document_id_contract_documents_id_fk": { + "name": "contract_clauses_document_id_contract_documents_id_fk", + "tableFrom": "contract_clauses", + "tableTo": "contract_documents", + "columnsFrom": [ + "document_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "contract_clauses_standard_clause_id_standard_clauses_id_fk": { + "name": "contract_clauses_standard_clause_id_standard_clauses_id_fk", + "tableFrom": "contract_clauses", + "tableTo": "standard_clauses", + "columnsFrom": [ + "standard_clause_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.contract_documents": { + "name": "contract_documents", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "tenant_id": { + "name": "tenant_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "case_id": { + "name": "case_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "filename": { + "name": "filename", + "type": "varchar(500)", + "primaryKey": false, + "notNull": true + }, + "mime_type": { + "name": "mime_type", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "file_size_bytes": { + "name": "file_size_bytes", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "storage_path": { + "name": "storage_path", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "extracted_text": { + "name": "extracted_text", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "contract_doc_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'uploaded'" + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "fachgruppe_id": { + "name": "fachgruppe_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "delete_after": { + "name": "delete_after", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "contract_docs_tenant_idx": { + "name": "contract_docs_tenant_idx", + "columns": [ + { + "expression": "tenant_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "contract_docs_case_idx": { + "name": "contract_docs_case_idx", + "columns": [ + { + "expression": "case_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "contract_docs_status_idx": { + "name": "contract_docs_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "contract_docs_delete_after_idx": { + "name": "contract_docs_delete_after_idx", + "columns": [ + { + "expression": "delete_after", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "contract_documents_tenant_id_tenants_id_fk": { + "name": "contract_documents_tenant_id_tenants_id_fk", + "tableFrom": "contract_documents", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "contract_documents_case_id_cases_id_fk": { + "name": "contract_documents_case_id_cases_id_fk", + "tableFrom": "contract_documents", + "tableTo": "cases", + "columnsFrom": [ + "case_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "contract_documents_user_id_users_id_fk": { + "name": "contract_documents_user_id_users_id_fk", + "tableFrom": "contract_documents", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "contract_documents_fachgruppe_id_nv_buehne_fachgruppen_id_fk": { + "name": "contract_documents_fachgruppe_id_nv_buehne_fachgruppen_id_fk", + "tableFrom": "contract_documents", + "tableTo": "nv_buehne_fachgruppen", + "columnsFrom": [ + "fachgruppe_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.contracts": { + "name": "contracts", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "tenant_id": { + "name": "tenant_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "case_id": { + "name": "case_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "artist_name": { + "name": "artist_name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "theater_name": { + "name": "theater_name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "fachgruppe_id": { + "name": "fachgruppe_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "contract_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'aktiv'" + }, + "contract_start": { + "name": "contract_start", + "type": "date", + "primaryKey": false, + "notNull": true + }, + "contract_end": { + "name": "contract_end", + "type": "date", + "primaryKey": false, + "notNull": true + }, + "years_of_service": { + "name": "years_of_service", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "is_first_engagement": { + "name": "is_first_engagement", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "is_over_55": { + "name": "is_over_55", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "current_spielzeit": { + "name": "current_spielzeit", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "gagenklasse": { + "name": "gagenklasse", + "type": "varchar(50)", + "primaryKey": false, + "notNull": false + }, + "monthly_gross_cents": { + "name": "monthly_gross_cents", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "notes": { + "name": "notes", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "contracts_tenant_idx": { + "name": "contracts_tenant_idx", + "columns": [ + { + "expression": "tenant_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "contracts_case_idx": { + "name": "contracts_case_idx", + "columns": [ + { + "expression": "case_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "contracts_fachgruppe_idx": { + "name": "contracts_fachgruppe_idx", + "columns": [ + { + "expression": "fachgruppe_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "contracts_status_idx": { + "name": "contracts_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "contracts_end_idx": { + "name": "contracts_end_idx", + "columns": [ + { + "expression": "contract_end", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "contracts_tenant_id_tenants_id_fk": { + "name": "contracts_tenant_id_tenants_id_fk", + "tableFrom": "contracts", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "contracts_case_id_cases_id_fk": { + "name": "contracts_case_id_cases_id_fk", + "tableFrom": "contracts", + "tableTo": "cases", + "columnsFrom": [ + "case_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "contracts_fachgruppe_id_nv_buehne_fachgruppen_id_fk": { + "name": "contracts_fachgruppe_id_nv_buehne_fachgruppen_id_fk", + "tableFrom": "contracts", + "tableTo": "nv_buehne_fachgruppen", + "columnsFrom": [ + "fachgruppe_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.decision_norms": { + "name": "decision_norms", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "decision_id": { + "name": "decision_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "norm_id": { + "name": "norm_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "application_type": { + "name": "application_type", + "type": "varchar(50)", + "primaryKey": false, + "notNull": true, + "default": "'angewendet'" + }, + "passage": { + "name": "passage", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "decision_norms_unique_idx": { + "name": "decision_norms_unique_idx", + "columns": [ + { + "expression": "decision_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "norm_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "decision_norms_decision_id_decisions_id_fk": { + "name": "decision_norms_decision_id_decisions_id_fk", + "tableFrom": "decision_norms", + "tableTo": "decisions", + "columnsFrom": [ + "decision_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "decision_norms_norm_id_norms_id_fk": { + "name": "decision_norms_norm_id_norms_id_fk", + "tableFrom": "decision_norms", + "tableTo": "norms", + "columnsFrom": [ + "norm_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.decision_references": { + "name": "decision_references", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "source_decision_id": { + "name": "source_decision_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "target_decision_id": { + "name": "target_decision_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "reference_type": { + "name": "reference_type", + "type": "varchar(50)", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "decision_refs_unique_idx": { + "name": "decision_refs_unique_idx", + "columns": [ + { + "expression": "source_decision_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_decision_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "reference_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "decision_references_source_decision_id_decisions_id_fk": { + "name": "decision_references_source_decision_id_decisions_id_fk", + "tableFrom": "decision_references", + "tableTo": "decisions", + "columnsFrom": [ + "source_decision_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "decision_references_target_decision_id_decisions_id_fk": { + "name": "decision_references_target_decision_id_decisions_id_fk", + "tableFrom": "decision_references", + "tableTo": "decisions", + "columnsFrom": [ + "target_decision_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.decisions": { + "name": "decisions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "tenant_id": { + "name": "tenant_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "type": { + "name": "type", + "type": "decision_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "case_reference": { + "name": "case_reference", + "type": "varchar(100)", + "primaryKey": false, + "notNull": false + }, + "decision_date": { + "name": "decision_date", + "type": "date", + "primaryKey": false, + "notNull": true + }, + "court": { + "name": "court", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "tribunal_id": { + "name": "tribunal_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "chamber": { + "name": "chamber", + "type": "varchar(100)", + "primaryKey": false, + "notNull": false + }, + "headnote": { + "name": "headnote", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tenor": { + "name": "tenor", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "facts": { + "name": "facts", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "reasoning": { + "name": "reasoning", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "full_text": { + "name": "full_text", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "domains": { + "name": "domains", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'[]'::jsonb" + }, + "keywords": { + "name": "keywords", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'[]'::jsonb" + }, + "publication_source": { + "name": "publication_source", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is_published": { + "name": "is_published", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "is_anonymized": { + "name": "is_anonymized", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "decisions_type_idx": { + "name": "decisions_type_idx", + "columns": [ + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "decisions_date_idx": { + "name": "decisions_date_idx", + "columns": [ + { + "expression": "decision_date", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "decisions_court_idx": { + "name": "decisions_court_idx", + "columns": [ + { + "expression": "court", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "decisions_tenant_idx": { + "name": "decisions_tenant_idx", + "columns": [ + { + "expression": "tenant_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "decisions_case_ref_idx": { + "name": "decisions_case_ref_idx", + "columns": [ + { + "expression": "case_reference", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "decisions_tenant_id_tenants_id_fk": { + "name": "decisions_tenant_id_tenants_id_fk", + "tableFrom": "decisions", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "decisions_tribunal_id_arbitration_tribunals_id_fk": { + "name": "decisions_tribunal_id_arbitration_tribunals_id_fk", + "tableFrom": "decisions", + "tableTo": "arbitration_tribunals", + "columnsFrom": [ + "tribunal_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.documents": { + "name": "documents", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "tenant_id": { + "name": "tenant_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "category": { + "name": "category", + "type": "document_category", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'sonstiges'" + }, + "case_id": { + "name": "case_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "decision_id": { + "name": "decision_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "norm_instrument_id": { + "name": "norm_instrument_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "filename": { + "name": "filename", + "type": "varchar(500)", + "primaryKey": false, + "notNull": true + }, + "mime_type": { + "name": "mime_type", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "file_size_bytes": { + "name": "file_size_bytes", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "storage_path": { + "name": "storage_path", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "extracted_text": { + "name": "extracted_text", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "document_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'uploaded'" + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "delete_after": { + "name": "delete_after", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "documents_tenant_idx": { + "name": "documents_tenant_idx", + "columns": [ + { + "expression": "tenant_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "documents_case_idx": { + "name": "documents_case_idx", + "columns": [ + { + "expression": "case_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "documents_decision_idx": { + "name": "documents_decision_idx", + "columns": [ + { + "expression": "decision_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "documents_norm_instrument_idx": { + "name": "documents_norm_instrument_idx", + "columns": [ + { + "expression": "norm_instrument_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "documents_category_idx": { + "name": "documents_category_idx", + "columns": [ + { + "expression": "category", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "documents_status_idx": { + "name": "documents_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "documents_delete_after_idx": { + "name": "documents_delete_after_idx", + "columns": [ + { + "expression": "delete_after", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "documents_tenant_id_tenants_id_fk": { + "name": "documents_tenant_id_tenants_id_fk", + "tableFrom": "documents", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "documents_user_id_users_id_fk": { + "name": "documents_user_id_users_id_fk", + "tableFrom": "documents", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "documents_case_id_cases_id_fk": { + "name": "documents_case_id_cases_id_fk", + "tableFrom": "documents", + "tableTo": "cases", + "columnsFrom": [ + "case_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "documents_decision_id_decisions_id_fk": { + "name": "documents_decision_id_decisions_id_fk", + "tableFrom": "documents", + "tableTo": "decisions", + "columnsFrom": [ + "decision_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "documents_norm_instrument_id_norm_instruments_id_fk": { + "name": "documents_norm_instrument_id_norm_instruments_id_fk", + "tableFrom": "documents", + "tableTo": "norm_instruments", + "columnsFrom": [ + "norm_instrument_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.non_renewal_deadlines": { + "name": "non_renewal_deadlines", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "contract_id": { + "name": "contract_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "spielzeit": { + "name": "spielzeit", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true + }, + "deadline_date": { + "name": "deadline_date", + "type": "date", + "primaryKey": false, + "notNull": true + }, + "warning_date": { + "name": "warning_date", + "type": "date", + "primaryKey": false, + "notNull": false + }, + "warning_days_before": { + "name": "warning_days_before", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 30 + }, + "notice_sent": { + "name": "notice_sent", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "notice_sent_date": { + "name": "notice_sent_date", + "type": "date", + "primaryKey": false, + "notNull": false + }, + "is_auto_renewed": { + "name": "is_auto_renewed", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "legal_basis": { + "name": "legal_basis", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "calculation_basis": { + "name": "calculation_basis", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "protection_category": { + "name": "protection_category", + "type": "varchar(100)", + "primaryKey": false, + "notNull": false + }, + "notes": { + "name": "notes", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "non_renewal_deadlines_contract_idx": { + "name": "non_renewal_deadlines_contract_idx", + "columns": [ + { + "expression": "contract_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "non_renewal_deadlines_date_idx": { + "name": "non_renewal_deadlines_date_idx", + "columns": [ + { + "expression": "deadline_date", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "non_renewal_deadlines_spielzeit_idx": { + "name": "non_renewal_deadlines_spielzeit_idx", + "columns": [ + { + "expression": "spielzeit", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "non_renewal_deadlines_contract_id_contracts_id_fk": { + "name": "non_renewal_deadlines_contract_id_contracts_id_fk", + "tableFrom": "non_renewal_deadlines", + "tableTo": "contracts", + "columnsFrom": [ + "contract_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.norm_instruments": { + "name": "norm_instruments", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "tenant_id": { + "name": "tenant_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "type": { + "name": "type", + "type": "norm_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "source_rank": { + "name": "source_rank", + "type": "source_rank", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "abbreviation": { + "name": "abbreviation", + "type": "varchar(50)", + "primaryKey": false, + "notNull": true + }, + "full_title": { + "name": "full_title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "enacted_at": { + "name": "enacted_at", + "type": "date", + "primaryKey": false, + "notNull": false + }, + "issuing_body": { + "name": "issuing_body", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "citation": { + "name": "citation", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "norm_instruments_type_idx": { + "name": "norm_instruments_type_idx", + "columns": [ + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "norm_instruments_tenant_idx": { + "name": "norm_instruments_tenant_idx", + "columns": [ + { + "expression": "tenant_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "norm_instruments_tenant_id_tenants_id_fk": { + "name": "norm_instruments_tenant_id_tenants_id_fk", + "tableFrom": "norm_instruments", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.norm_references": { + "name": "norm_references", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "source_norm_id": { + "name": "source_norm_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "target_norm_id": { + "name": "target_norm_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "reference_type": { + "name": "reference_type", + "type": "varchar(50)", + "primaryKey": false, + "notNull": true, + "default": "'verweist_auf'" + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "norm_refs_unique_idx": { + "name": "norm_refs_unique_idx", + "columns": [ + { + "expression": "source_norm_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_norm_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "reference_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "norm_references_source_norm_id_norms_id_fk": { + "name": "norm_references_source_norm_id_norms_id_fk", + "tableFrom": "norm_references", + "tableTo": "norms", + "columnsFrom": [ + "source_norm_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "norm_references_target_norm_id_norms_id_fk": { + "name": "norm_references_target_norm_id_norms_id_fk", + "tableFrom": "norm_references", + "tableTo": "norms", + "columnsFrom": [ + "target_norm_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.norms": { + "name": "norms", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "tenant_id": { + "name": "tenant_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "instrument_id": { + "name": "instrument_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "paragraph": { + "name": "paragraph", + "type": "varchar(50)", + "primaryKey": false, + "notNull": true + }, + "subsection": { + "name": "subsection", + "type": "varchar(100)", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "varchar(500)", + "primaryKey": false, + "notNull": false + }, + "body": { + "name": "body", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "valid_from": { + "name": "valid_from", + "type": "date", + "primaryKey": false, + "notNull": true + }, + "valid_to": { + "name": "valid_to", + "type": "date", + "primaryKey": false, + "notNull": false + }, + "previous_version_id": { + "name": "previous_version_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "version_number": { + "name": "version_number", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "domains": { + "name": "domains", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'[]'::jsonb" + }, + "notes": { + "name": "notes", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "norms_instrument_idx": { + "name": "norms_instrument_idx", + "columns": [ + { + "expression": "instrument_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "norms_paragraph_idx": { + "name": "norms_paragraph_idx", + "columns": [ + { + "expression": "instrument_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "paragraph", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "norms_valid_from_idx": { + "name": "norms_valid_from_idx", + "columns": [ + { + "expression": "valid_from", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "norms_tenant_idx": { + "name": "norms_tenant_idx", + "columns": [ + { + "expression": "tenant_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "norms_tenant_id_tenants_id_fk": { + "name": "norms_tenant_id_tenants_id_fk", + "tableFrom": "norms", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "norms_instrument_id_norm_instruments_id_fk": { + "name": "norms_instrument_id_norm_instruments_id_fk", + "tableFrom": "norms", + "tableTo": "norm_instruments", + "columnsFrom": [ + "instrument_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "norms_previous_version_id_norms_id_fk": { + "name": "norms_previous_version_id_norms_id_fk", + "tableFrom": "norms", + "tableTo": "norms", + "columnsFrom": [ + "previous_version_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.nv_buehne_fachgruppen": { + "name": "nv_buehne_fachgruppen", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "abbreviation": { + "name": "abbreviation", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "section_ref": { + "name": "section_ref", + "type": "varchar(100)", + "primaryKey": false, + "notNull": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "special_provisions": { + "name": "special_provisions", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'[]'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.proceeding_deadlines": { + "name": "proceeding_deadlines", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "proceeding_id": { + "name": "proceeding_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "step_id": { + "name": "step_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "type": { + "name": "type", + "type": "deadline_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'frist'" + }, + "label": { + "name": "label", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "due_date": { + "name": "due_date", + "type": "date", + "primaryKey": false, + "notNull": true + }, + "due_time": { + "name": "due_time", + "type": "varchar(10)", + "primaryKey": false, + "notNull": false + }, + "warning_date": { + "name": "warning_date", + "type": "date", + "primaryKey": false, + "notNull": false + }, + "warning_days_before": { + "name": "warning_days_before", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "is_completed": { + "name": "is_completed", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "is_calculated": { + "name": "is_calculated", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "calculation_basis": { + "name": "calculation_basis", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "legal_basis": { + "name": "legal_basis", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "notes": { + "name": "notes", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "proceeding_deadlines_proceeding_idx": { + "name": "proceeding_deadlines_proceeding_idx", + "columns": [ + { + "expression": "proceeding_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "proceeding_deadlines_step_idx": { + "name": "proceeding_deadlines_step_idx", + "columns": [ + { + "expression": "step_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "proceeding_deadlines_due_date_idx": { + "name": "proceeding_deadlines_due_date_idx", + "columns": [ + { + "expression": "due_date", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "proceeding_deadlines_warning_idx": { + "name": "proceeding_deadlines_warning_idx", + "columns": [ + { + "expression": "warning_date", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "proceeding_deadlines_proceeding_id_proceedings_id_fk": { + "name": "proceeding_deadlines_proceeding_id_proceedings_id_fk", + "tableFrom": "proceeding_deadlines", + "tableTo": "proceedings", + "columnsFrom": [ + "proceeding_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "proceeding_deadlines_step_id_proceeding_steps_id_fk": { + "name": "proceeding_deadlines_step_id_proceeding_steps_id_fk", + "tableFrom": "proceeding_deadlines", + "tableTo": "proceeding_steps", + "columnsFrom": [ + "step_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.proceeding_steps": { + "name": "proceeding_steps", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "proceeding_id": { + "name": "proceeding_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "step_key": { + "name": "step_key", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "label": { + "name": "label", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "status": { + "name": "status", + "type": "proceeding_step_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'ausstehend'" + }, + "legal_basis": { + "name": "legal_basis", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "responsible_party": { + "name": "responsible_party", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "notes": { + "name": "notes", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "proceeding_steps_proceeding_idx": { + "name": "proceeding_steps_proceeding_idx", + "columns": [ + { + "expression": "proceeding_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "proceeding_steps_key_idx": { + "name": "proceeding_steps_key_idx", + "columns": [ + { + "expression": "proceeding_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "step_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "proceeding_steps_proceeding_id_proceedings_id_fk": { + "name": "proceeding_steps_proceeding_id_proceedings_id_fk", + "tableFrom": "proceeding_steps", + "tableTo": "proceedings", + "columnsFrom": [ + "proceeding_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.proceedings": { + "name": "proceedings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "tenant_id": { + "name": "tenant_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "case_id": { + "name": "case_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "type": { + "name": "type", + "type": "proceeding_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "proceeding_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'vorbereitung'" + }, + "filing_date": { + "name": "filing_date", + "type": "date", + "primaryKey": false, + "notNull": false + }, + "internal_ref": { + "name": "internal_ref", + "type": "varchar(100)", + "primaryKey": false, + "notNull": false + }, + "external_ref": { + "name": "external_ref", + "type": "varchar(100)", + "primaryKey": false, + "notNull": false + }, + "tribunal_id": { + "name": "tribunal_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "court_name": { + "name": "court_name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "chamber": { + "name": "chamber", + "type": "varchar(100)", + "primaryKey": false, + "notNull": false + }, + "presiding_judge": { + "name": "presiding_judge", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "applicant": { + "name": "applicant", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "respondent": { + "name": "respondent", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "subject": { + "name": "subject", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "amount_in_dispute_cents": { + "name": "amount_in_dispute_cents", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "fachgruppe_id": { + "name": "fachgruppe_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "current_step_key": { + "name": "current_step_key", + "type": "varchar(100)", + "primaryKey": false, + "notNull": false + }, + "notes": { + "name": "notes", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "closed_at": { + "name": "closed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "proceedings_tenant_idx": { + "name": "proceedings_tenant_idx", + "columns": [ + { + "expression": "tenant_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "proceedings_case_idx": { + "name": "proceedings_case_idx", + "columns": [ + { + "expression": "case_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "proceedings_type_idx": { + "name": "proceedings_type_idx", + "columns": [ + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "proceedings_status_idx": { + "name": "proceedings_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "proceedings_tenant_id_tenants_id_fk": { + "name": "proceedings_tenant_id_tenants_id_fk", + "tableFrom": "proceedings", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "proceedings_case_id_cases_id_fk": { + "name": "proceedings_case_id_cases_id_fk", + "tableFrom": "proceedings", + "tableTo": "cases", + "columnsFrom": [ + "case_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "proceedings_tribunal_id_arbitration_tribunals_id_fk": { + "name": "proceedings_tribunal_id_arbitration_tribunals_id_fk", + "tableFrom": "proceedings", + "tableTo": "arbitration_tribunals", + "columnsFrom": [ + "tribunal_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "proceedings_fachgruppe_id_nv_buehne_fachgruppen_id_fk": { + "name": "proceedings_fachgruppe_id_nv_buehne_fachgruppen_id_fk", + "tableFrom": "proceedings", + "tableTo": "nv_buehne_fachgruppen", + "columnsFrom": [ + "fachgruppe_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.standard_clauses": { + "name": "standard_clauses", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "instrument_id": { + "name": "instrument_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "category": { + "name": "category", + "type": "varchar(200)", + "primaryKey": false, + "notNull": true + }, + "label": { + "name": "label", + "type": "varchar(500)", + "primaryKey": false, + "notNull": true + }, + "body": { + "name": "body", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "fachgruppe_ids": { + "name": "fachgruppe_ids", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "norm_id": { + "name": "norm_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "standard_clauses_instrument_idx": { + "name": "standard_clauses_instrument_idx", + "columns": [ + { + "expression": "instrument_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "standard_clauses_category_idx": { + "name": "standard_clauses_category_idx", + "columns": [ + { + "expression": "category", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "standard_clauses_instrument_id_norm_instruments_id_fk": { + "name": "standard_clauses_instrument_id_norm_instruments_id_fk", + "tableFrom": "standard_clauses", + "tableTo": "norm_instruments", + "columnsFrom": [ + "instrument_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "standard_clauses_norm_id_norms_id_fk": { + "name": "standard_clauses_norm_id_norms_id_fk", + "tableFrom": "standard_clauses", + "tableTo": "norms", + "columnsFrom": [ + "norm_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tenants": { + "name": "tenants", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "address": { + "name": "address", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "phone": { + "name": "phone", + "type": "varchar(50)", + "primaryKey": false, + "notNull": false + }, + "email": { + "name": "email", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "retention_days": { + "name": "retention_days", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 3650 + }, + "settings": { + "name": "settings", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "tenants_slug_unique": { + "name": "tenants_slug_unique", + "nullsNotDistinct": false, + "columns": [ + "slug" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.users": { + "name": "users", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "tenant_id": { + "name": "tenant_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "user_role", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'viewer'" + }, + "password_hash": { + "name": "password_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "email_verified_at": { + "name": "email_verified_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_login_at": { + "name": "last_login_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "users_tenant_email_idx": { + "name": "users_tenant_email_idx", + "columns": [ + { + "expression": "tenant_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "email", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "users_tenant_id_tenants_id_fk": { + "name": "users_tenant_id_tenants_id_fk", + "tableFrom": "users", + "tableTo": "tenants", + "columnsFrom": [ + "tenant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": { + "public.analysis_mode": { + "name": "analysis_mode", + "schema": "public", + "values": [ + "gutachten", + "entscheidung", + "vergleich", + "risiko" + ] + }, + "public.analysis_status": { + "name": "analysis_status", + "schema": "public", + "values": [ + "draft", + "in_progress", + "completed", + "archived" + ] + }, + "public.clause_rating": { + "name": "clause_rating", + "schema": "public", + "values": [ + "standard", + "abweichend", + "kritisch", + "unbekannt" + ] + }, + "public.compensation_component": { + "name": "compensation_component", + "schema": "public", + "values": [ + "grundgage", + "ortszuschlag", + "kinderzuschlag", + "dienstalterszulage", + "funktionszulage", + "sonderzahlung", + "probenzuschlag" + ] + }, + "public.contract_doc_status": { + "name": "contract_doc_status", + "schema": "public", + "values": [ + "uploaded", + "extracting", + "extracted", + "analyzing", + "completed", + "failed" + ] + }, + "public.contract_status": { + "name": "contract_status", + "schema": "public", + "values": [ + "aktiv", + "gekuendigt", + "nichtverlaengert", + "ausgelaufen", + "ruhend" + ] + }, + "public.deadline_type": { + "name": "deadline_type", + "schema": "public", + "values": [ + "frist", + "termin", + "vorfrist" + ] + }, + "public.decision_type": { + "name": "decision_type", + "schema": "public", + "values": [ + "schiedsspruch", + "urteil", + "beschluss", + "vergleich", + "einstweilige_verfuegung" + ] + }, + "public.document_category": { + "name": "document_category", + "schema": "public", + "values": [ + "entscheidung", + "norm", + "falldokument", + "sonstiges" + ] + }, + "public.document_status": { + "name": "document_status", + "schema": "public", + "values": [ + "uploaded", + "extracting", + "extracted", + "failed" + ] + }, + "public.legal_domain": { + "name": "legal_domain", + "schema": "public", + "values": [ + "buehnenrecht", + "arbeitsrecht", + "tarifrecht", + "urheberrecht", + "sozialrecht", + "vertragsrecht", + "prozessrecht" + ] + }, + "public.norm_type": { + "name": "norm_type", + "schema": "public", + "values": [ + "gesetz", + "tarifvertrag", + "schiedsordnung", + "verordnung", + "satzung", + "richtlinie" + ] + }, + "public.proceeding_status": { + "name": "proceeding_status", + "schema": "public", + "values": [ + "vorbereitung", + "eingereicht", + "laufend", + "verhandlung", + "entschieden", + "abgeschlossen", + "ruht" + ] + }, + "public.proceeding_step_status": { + "name": "proceeding_step_status", + "schema": "public", + "values": [ + "ausstehend", + "aktiv", + "abgeschlossen", + "uebersprungen" + ] + }, + "public.proceeding_type": { + "name": "proceeding_type", + "schema": "public", + "values": [ + "bschgo_bezirk", + "bschgo_bund", + "arbgg_erste_instanz", + "arbgg_berufung", + "arbgg_revision" + ] + }, + "public.source_rank": { + "name": "source_rank", + "schema": "public", + "values": [ + "gesetz", + "tarif", + "schiedsordnung", + "praxis", + "kommentar" + ] + }, + "public.user_role": { + "name": "user_role", + "schema": "public", + "values": [ + "admin", + "attorney", + "paralegal", + "viewer" + ] + } + }, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/drizzle/meta/_journal.json b/drizzle/meta/_journal.json index c0ddb10..988d291 100644 --- a/drizzle/meta/_journal.json +++ b/drizzle/meta/_journal.json @@ -15,6 +15,13 @@ "when": 1775690117252, "tag": "0001_curved_fabian_cortez", "breakpoints": true + }, + { + "idx": 2, + "version": "7", + "when": 1775729813628, + "tag": "0002_wide_grandmaster", + "breakpoints": true } ] } \ No newline at end of file diff --git a/src/app/(dashboard)/cases/[id]/page.tsx b/src/app/(dashboard)/cases/[id]/page.tsx index ba702b3..7a80bba 100644 --- a/src/app/(dashboard)/cases/[id]/page.tsx +++ b/src/app/(dashboard)/cases/[id]/page.tsx @@ -3,6 +3,7 @@ import { cases, analyses, proceedings } from '@/lib/db/schema'; import { eq, desc } from 'drizzle-orm'; import Link from 'next/link'; import { notFound } from 'next/navigation'; +import DokumentUpload from '@/components/documents/dokument-upload'; const STATUS_LABELS: Record = { active: 'Aktiv', @@ -126,6 +127,12 @@ export default async function CaseDetailPage({ + +

diff --git a/src/app/(dashboard)/entscheidungen/[id]/page.tsx b/src/app/(dashboard)/entscheidungen/[id]/page.tsx index 4ef523e..04f7963 100644 --- a/src/app/(dashboard)/entscheidungen/[id]/page.tsx +++ b/src/app/(dashboard)/entscheidungen/[id]/page.tsx @@ -3,6 +3,7 @@ import { decisions, decisionNorms, norms, normInstruments } from '@/lib/db/schem import { eq } from 'drizzle-orm'; import { notFound } from 'next/navigation'; import Link from 'next/link'; +import DokumentUpload from '@/components/documents/dokument-upload'; export default async function EntscheidungDetailPage({ params, @@ -71,6 +72,12 @@ export default async function EntscheidungDetailPage({ )}

+ + {appliedNorms.length > 0 && (

Angewandte Normen

diff --git a/src/app/(dashboard)/normen/[instrumentId]/page.tsx b/src/app/(dashboard)/normen/[instrumentId]/page.tsx index 36500d5..8a6ab1c 100644 --- a/src/app/(dashboard)/normen/[instrumentId]/page.tsx +++ b/src/app/(dashboard)/normen/[instrumentId]/page.tsx @@ -3,6 +3,7 @@ import { normInstruments, norms } from '@/lib/db/schema'; import { eq, asc } from 'drizzle-orm'; import { notFound } from 'next/navigation'; import Link from 'next/link'; +import DokumentUpload from '@/components/documents/dokument-upload'; const QUELLENRANG_LABELS: Record = { gesetz: 'Gesetz', @@ -70,6 +71,12 @@ export default async function InstrumentDetailPage({
+ + {normList.length === 0 ? (

Keine Vorschriften für dieses Regelwerk hinterlegt.

diff --git a/src/app/api/documents/route.ts b/src/app/api/documents/route.ts new file mode 100644 index 0000000..653c05e --- /dev/null +++ b/src/app/api/documents/route.ts @@ -0,0 +1,97 @@ +// POST /api/documents — Upload a document (decision, norm, or case document) +// GET /api/documents — List documents for the current tenant + +import { type NextRequest } from 'next/server'; +import { uploadDocument, listDocuments, extractDocumentText } from '@/lib/documents'; +import { logAuditEvent } from '@/lib/auth/audit'; +import { requirePermission } from '@/lib/auth/rbac'; + +const VALID_CATEGORIES = new Set(['entscheidung', 'norm', 'falldokument', 'sonstiges']); + +export async function POST(request: NextRequest) { + const auth = await requirePermission('cases:edit'); + if ('response' in auth) return auth.response; + const { ctx } = auth; + + const formData = await request.formData(); + const file = formData.get('file'); + const category = (formData.get('category') as string) || 'sonstiges'; + const caseId = formData.get('caseId') as string | null; + const decisionId = formData.get('decisionId') as string | null; + const normInstrumentId = formData.get('normInstrumentId') as string | null; + + if (!file || !(file instanceof File)) { + return Response.json( + { error: 'Keine Datei hochgeladen. Feld "file" erwartet.' }, + { status: 400 }, + ); + } + + if (!VALID_CATEGORIES.has(category)) { + return Response.json( + { error: `Ungueltige Kategorie. Erlaubt: ${[...VALID_CATEGORIES].join(', ')}` }, + { status: 400 }, + ); + } + + const ip = request.headers.get('x-forwarded-for')?.split(',')[0]?.trim() + ?? request.headers.get('x-real-ip') + ?? undefined; + + try { + const result = await uploadDocument({ + tenantId: ctx.tenantId, + userId: ctx.userId, + file, + category: category as 'entscheidung' | 'norm' | 'falldokument' | 'sonstiges', + caseId: caseId ?? undefined, + decisionId: decisionId ?? undefined, + normInstrumentId: normInstrumentId ?? undefined, + }); + + await logAuditEvent( + ctx, 'create', 'document', result.documentId, + { fileName: file.name, category, caseId, decisionId, normInstrumentId }, ip, + ); + + // Trigger text extraction asynchronously (fire-and-forget) + extractDocumentText(ctx.tenantId, result.documentId).catch(() => { + // Extraction errors are stored in the document record + }); + + return Response.json(result, { status: 201 }); + } catch (err) { + const message = err instanceof Error ? err.message : 'Upload fehlgeschlagen'; + return Response.json({ error: message }, { status: 400 }); + } +} + +export async function GET(request: NextRequest) { + const auth = await requirePermission('cases:read'); + if ('response' in auth) return auth.response; + const { ctx } = auth; + + const searchParams = request.nextUrl.searchParams; + const limit = Math.min(parseInt(searchParams.get('limit') ?? '20', 10), 100); + const offset = parseInt(searchParams.get('offset') ?? '0', 10); + const category = searchParams.get('category') as string | null; + const caseId = searchParams.get('caseId') as string | null; + const decisionId = searchParams.get('decisionId') as string | null; + const normInstrumentId = searchParams.get('normInstrumentId') as string | null; + + const docs = await listDocuments( + ctx.tenantId, + { + category: category && VALID_CATEGORIES.has(category) + ? category as 'entscheidung' | 'norm' | 'falldokument' | 'sonstiges' + : undefined, + caseId: caseId ?? undefined, + decisionId: decisionId ?? undefined, + normInstrumentId: normInstrumentId ?? undefined, + }, + limit, + offset, + ); + + return Response.json(docs); +} diff --git a/src/components/documents/dokument-upload.tsx b/src/components/documents/dokument-upload.tsx new file mode 100644 index 0000000..44299f5 --- /dev/null +++ b/src/components/documents/dokument-upload.tsx @@ -0,0 +1,206 @@ +'use client'; + +import { useState, useRef, useCallback, useEffect } from 'react'; + +interface DokumentUploadProps { + category: 'entscheidung' | 'norm' | 'falldokument' | 'sonstiges'; + /** Optional linked entity ID */ + caseId?: string; + decisionId?: string; + normInstrumentId?: string; + /** Label shown above the upload form */ + label?: string; +} + +interface DocumentItem { + id: string; + filename: string; + mimeType: string; + fileSizeBytes: number; + status: string; + createdAt: string; +} + +const STATUS_LABELS: Record = { + uploaded: 'Hochgeladen', + extracting: 'Text wird extrahiert...', + extracted: 'Extrahiert', + failed: 'Fehlgeschlagen', +}; + +const STATUS_COLORS: Record = { + uploaded: 'bg-blue-500/10 text-blue-700', + extracting: 'bg-yellow-500/10 text-yellow-700', + extracted: 'bg-green-500/10 text-green-700', + failed: 'bg-red-500/10 text-red-700', +}; + +function formatFileSize(bytes: number): string { + if (bytes < 1024) return `${bytes} B`; + if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(0)} KB`; + return `${(bytes / 1024 / 1024).toFixed(1)} MB`; +} + +export default function DokumentUpload({ + category, + caseId, + decisionId, + normInstrumentId, + label = 'Dokument hochladen', +}: DokumentUploadProps) { + const [uploading, setUploading] = useState(false); + const [error, setError] = useState(''); + const [success, setSuccess] = useState(''); + const [documents, setDocuments] = useState([]); + const [dragging, setDragging] = useState(false); + const fileRef = useRef(null); + + const fetchDocuments = useCallback(async () => { + const params = new URLSearchParams({ category }); + if (caseId) params.set('caseId', caseId); + if (decisionId) params.set('decisionId', decisionId); + if (normInstrumentId) params.set('normInstrumentId', normInstrumentId); + + try { + const res = await fetch(`/api/documents?${params}`); + if (res.ok) { + const data = await res.json(); + setDocuments(data); + } + } catch { + // Silently fail on list fetch + } + }, [category, caseId, decisionId, normInstrumentId]); + + useEffect(() => { + fetchDocuments(); + }, [fetchDocuments]); + + async function doUpload(file: File) { + setError(''); + setSuccess(''); + setUploading(true); + + try { + const formData = new FormData(); + formData.append('file', file); + formData.append('category', category); + if (caseId) formData.append('caseId', caseId); + if (decisionId) formData.append('decisionId', decisionId); + if (normInstrumentId) formData.append('normInstrumentId', normInstrumentId); + + const res = await fetch('/api/documents', { + method: 'POST', + body: formData, + }); + + if (!res.ok) { + const data = await res.json(); + throw new Error(data.error || 'Upload fehlgeschlagen'); + } + + setSuccess(`"${file.name}" erfolgreich hochgeladen.`); + if (fileRef.current) fileRef.current.value = ''; + fetchDocuments(); + } catch (err) { + setError(err instanceof Error ? err.message : 'Ein Fehler ist aufgetreten'); + } finally { + setUploading(false); + } + } + + function handleSubmit(e: React.FormEvent) { + e.preventDefault(); + const file = fileRef.current?.files?.[0]; + if (file) doUpload(file); + } + + function handleDrop(e: React.DragEvent) { + e.preventDefault(); + setDragging(false); + const file = e.dataTransfer.files[0]; + if (file) doUpload(file); + } + + function handleDragOver(e: React.DragEvent) { + e.preventDefault(); + setDragging(true); + } + + function handleDragLeave(e: React.DragEvent) { + e.preventDefault(); + setDragging(false); + } + + return ( +
+
+

{label}

+
+
+ +

+ PDF oder DOCX, max. 10 MB. Drag & Drop wird unterstuetzt. +

+
+ +
+ {error &&

{error}

} + {success &&

{success}

} +
+ + {documents.length > 0 && ( +
+

+ Dokumente ({documents.length}) +

+
+ {documents.map((doc) => ( +
+
+

+ {doc.filename} +

+

+ {formatFileSize(doc.fileSizeBytes)} ·{' '} + {new Date(doc.createdAt).toLocaleDateString('de-DE')} +

+
+ + {STATUS_LABELS[doc.status] ?? doc.status} + +
+ ))} +
+
+ )} +
+ ); +} diff --git a/src/lib/db/schema.ts b/src/lib/db/schema.ts index 3460ba8..4179405 100644 --- a/src/lib/db/schema.ts +++ b/src/lib/db/schema.ts @@ -892,6 +892,75 @@ export const nonRenewalDeadlines = pgTable( ], ); +// ============================================================ +// Dokumente (Generic Document Upload — Phase 3.4) +// ============================================================ + +/** Document category — what kind of document this is */ +export const documentCategoryEnum = pgEnum("document_category", [ + "entscheidung", // Court decision / arbitration award document + "norm", // Legal norm / statute document + "falldokument", // Case-related document + "sonstiges", // Other / miscellaneous +]); + +/** Document processing status */ +export const documentStatusEnum = pgEnum("document_status", [ + "uploaded", + "extracting", + "extracted", + "failed", +]); + +/** + * Dokumente — generic uploaded documents for decisions, norms, and cases. + * Tenant-isolated via RLS. Files stored in /uploads/{tenantId}/documents/... + */ +export const documents = pgTable( + "documents", + { + id: uuid("id").primaryKey().defaultRandom(), + tenantId: uuid("tenant_id").notNull().references(() => tenants.id, { onDelete: "cascade" }), + userId: uuid("user_id").notNull().references(() => users.id), + /** Document category */ + category: documentCategoryEnum("category").notNull().default("sonstiges"), + /** Optional link to a case */ + caseId: uuid("case_id").references(() => cases.id, { onDelete: "set null" }), + /** Optional link to a decision */ + decisionId: uuid("decision_id").references(() => decisions.id, { onDelete: "set null" }), + /** Optional link to a norm instrument */ + normInstrumentId: uuid("norm_instrument_id").references(() => normInstruments.id, { onDelete: "set null" }), + /** Original filename */ + filename: varchar("filename", { length: 500 }).notNull(), + /** MIME type */ + mimeType: varchar("mime_type", { length: 100 }).notNull(), + /** File size in bytes */ + fileSizeBytes: integer("file_size_bytes").notNull(), + /** Storage path (absolute on filesystem) */ + storagePath: text("storage_path").notNull(), + /** Extracted plain text from the document */ + extractedText: text("extracted_text"), + /** Processing status */ + status: documentStatusEnum("status").notNull().default("uploaded"), + /** Error message if processing failed */ + errorMessage: text("error_message"), + metadata: jsonb("metadata").$type>(), + /** DSGVO: scheduled deletion date */ + deleteAfter: timestamp("delete_after", { withTimezone: true }), + createdAt: timestamp("created_at", { withTimezone: true }).defaultNow().notNull(), + updatedAt: timestamp("updated_at", { withTimezone: true }).defaultNow().notNull(), + }, + (t) => [ + index("documents_tenant_idx").on(t.tenantId), + index("documents_case_idx").on(t.caseId), + index("documents_decision_idx").on(t.decisionId), + index("documents_norm_instrument_idx").on(t.normInstrumentId), + index("documents_category_idx").on(t.category), + index("documents_status_idx").on(t.status), + index("documents_delete_after_idx").on(t.deleteAfter), + ], +); + // ============================================================ // DSGVO / Audit // ============================================================ @@ -1037,3 +1106,11 @@ export const compensationRulesRelations = relations(compensationRules, ({ one }) export const nonRenewalDeadlinesRelations = relations(nonRenewalDeadlines, ({ one }) => ({ contract: one(contracts, { fields: [nonRenewalDeadlines.contractId], references: [contracts.id] }), })); + +export const documentsRelations = relations(documents, ({ one }) => ({ + tenant: one(tenants, { fields: [documents.tenantId], references: [tenants.id] }), + case: one(cases, { fields: [documents.caseId], references: [cases.id] }), + decision: one(decisions, { fields: [documents.decisionId], references: [decisions.id] }), + normInstrument: one(normInstruments, { fields: [documents.normInstrumentId], references: [normInstruments.id] }), + user: one(users, { fields: [documents.userId], references: [users.id] }), +})); diff --git a/src/lib/documents/index.ts b/src/lib/documents/index.ts new file mode 100644 index 0000000..aee2e3e --- /dev/null +++ b/src/lib/documents/index.ts @@ -0,0 +1,224 @@ +// Generic Document Upload Module — upload, text extraction, listing +// Handles PDF/DOCX for decisions, norms, and case documents +// All DB access uses withTenantDb() for RLS-based tenant isolation (DSGVO Art. 32) + +import { withTenantDb } from '@/lib/db'; +import { documents } from '@/lib/db/schema'; +import { eq, and, desc } from 'drizzle-orm'; + +const ALLOWED_MIME_TYPES = new Set([ + 'application/pdf', + 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', +]); + +const MAX_FILE_SIZE = 10 * 1024 * 1024; // 10 MB + +type DocumentCategory = 'entscheidung' | 'norm' | 'falldokument' | 'sonstiges'; + +interface UploadOptions { + tenantId: string; + userId: string; + file: File; + category: DocumentCategory; + caseId?: string; + decisionId?: string; + normInstrumentId?: string; +} + +interface UploadResult { + documentId: string; + filename: string; + status: string; +} + +/** + * Validate and store a document upload. + * Text extraction happens asynchronously via extractDocumentText(). + */ +export async function uploadDocument(opts: UploadOptions): Promise { + const { tenantId, userId, file, category, caseId, decisionId, normInstrumentId } = opts; + + if (!ALLOWED_MIME_TYPES.has(file.type)) { + throw new Error( + `Ungueltiger Dateityp: ${file.type}. Erlaubt sind PDF und DOCX.`, + ); + } + + if (file.size > MAX_FILE_SIZE) { + throw new Error( + `Datei zu gross: ${(file.size / 1024 / 1024).toFixed(1)} MB. Maximum: ${MAX_FILE_SIZE / 1024 / 1024} MB.`, + ); + } + + const buffer = Buffer.from(await file.arrayBuffer()); + + const fs = await import('node:fs/promises'); + const path = await import('node:path'); + const uploadDir = path.join(process.cwd(), 'uploads', tenantId, 'documents', category); + await fs.mkdir(uploadDir, { recursive: true }); + const filePath = path.join(uploadDir, `${Date.now()}-${file.name}`); + await fs.writeFile(filePath, buffer); + + // DSGVO: default retention — 90 days from upload + const deleteAfter = new Date(); + deleteAfter.setDate(deleteAfter.getDate() + 90); + + const doc = await withTenantDb(tenantId, async (tdb) => { + const [d] = await tdb + .insert(documents) + .values({ + tenantId, + userId, + category, + caseId: caseId ?? null, + decisionId: decisionId ?? null, + normInstrumentId: normInstrumentId ?? null, + filename: file.name, + mimeType: file.type, + fileSizeBytes: file.size, + storagePath: filePath, + status: 'uploaded', + deleteAfter, + }) + .returning(); + return d; + }); + + return { + documentId: doc.id, + filename: doc.filename, + status: doc.status, + }; +} + +/** + * Extract text from a document (PDF or DOCX). + * Updates the document record with extracted text. + */ +export async function extractDocumentText(tenantId: string, documentId: string): Promise { + const doc = await withTenantDb(tenantId, async (tdb) => { + const [d] = await tdb + .select() + .from(documents) + .where(eq(documents.id, documentId)) + .limit(1); + return d ?? null; + }); + + if (!doc) throw new Error('Dokument nicht gefunden'); + + await withTenantDb(tenantId, async (tdb) => { + await tdb + .update(documents) + .set({ status: 'extracting', updatedAt: new Date() }) + .where(eq(documents.id, documentId)); + }); + + try { + const fs = await import('node:fs/promises'); + const fileBuffer = await fs.readFile(doc.storagePath); + + let text: string; + + if (doc.mimeType === 'application/pdf') { + const pdfParse = (await import('pdf-parse')).default; + const pdfData = await pdfParse(fileBuffer); + text = pdfData.text; + } else { + const mammoth = await import('mammoth'); + const result = await mammoth.extractRawText({ buffer: fileBuffer }); + text = result.value; + } + + await withTenantDb(tenantId, async (tdb) => { + await tdb + .update(documents) + .set({ + extractedText: text, + status: 'extracted', + updatedAt: new Date(), + }) + .where(eq(documents.id, documentId)); + }); + + return text; + } catch (err) { + const message = err instanceof Error ? err.message : 'Textextraktion fehlgeschlagen'; + await withTenantDb(tenantId, async (tdb) => { + await tdb + .update(documents) + .set({ + status: 'failed', + errorMessage: message, + updatedAt: new Date(), + }) + .where(eq(documents.id, documentId)); + }); + throw err; + } +} + +/** + * List documents for a tenant, optionally filtered by category or linked entity. + */ +export async function listDocuments( + tenantId: string, + filters?: { + category?: DocumentCategory; + caseId?: string; + decisionId?: string; + normInstrumentId?: string; + }, + limit = 20, + offset = 0, +) { + return withTenantDb(tenantId, async (tdb) => { + const conditions = [eq(documents.tenantId, tenantId)]; + + if (filters?.category) { + conditions.push(eq(documents.category, filters.category)); + } + if (filters?.caseId) { + conditions.push(eq(documents.caseId, filters.caseId)); + } + if (filters?.decisionId) { + conditions.push(eq(documents.decisionId, filters.decisionId)); + } + if (filters?.normInstrumentId) { + conditions.push(eq(documents.normInstrumentId, filters.normInstrumentId)); + } + + return tdb + .select({ + id: documents.id, + filename: documents.filename, + mimeType: documents.mimeType, + fileSizeBytes: documents.fileSizeBytes, + category: documents.category, + status: documents.status, + caseId: documents.caseId, + decisionId: documents.decisionId, + normInstrumentId: documents.normInstrumentId, + createdAt: documents.createdAt, + }) + .from(documents) + .where(and(...conditions)) + .orderBy(desc(documents.createdAt)) + .limit(limit) + .offset(offset); + }); +} + +/** + * Get a single document by ID. + */ +export async function getDocument(tenantId: string, documentId: string) { + return withTenantDb(tenantId, async (tdb) => { + const [doc] = await tdb + .select() + .from(documents) + .where(eq(documents.id, documentId)) + .limit(1); + return doc ?? null; + }); +}