-- CreateEnum CREATE TYPE "MilestoneStatus" AS ENUM ('open', 'reached', 'confirmed', 'billed'); -- CreateEnum CREATE TYPE "BillingRecordKind" AS ENUM ('order_completion', 'milestone', 'daily_report'); -- CreateEnum CREATE TYPE "BillingRecordStatus" AS ENUM ('open', 'billed', 'voided'); -- AlterTable ALTER TABLE "material_usages" ADD COLUMN "billing_record_id" TEXT; -- AlterTable ALTER TABLE "time_entries" ADD COLUMN "billing_record_id" TEXT; -- CreateTable CREATE TABLE "work_order_milestones" ( "id" TEXT NOT NULL, "tenant_id" TEXT NOT NULL, "work_order_id" TEXT NOT NULL, "title" TEXT NOT NULL, "description" TEXT, "sort_order" INTEGER NOT NULL DEFAULT 0, "status" "MilestoneStatus" NOT NULL DEFAULT 'open', "reached_by_id" TEXT, "reached_at" TIMESTAMP(3), "reached_note" TEXT, "confirmed_by_id" TEXT, "confirmed_at" TIMESTAMP(3), "rejected_by_id" TEXT, "rejected_at" TIMESTAMP(3), "rejection_reason" TEXT, "billing_record_id" TEXT, "created_by_id" TEXT, "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, "updated_at" TIMESTAMP(3) NOT NULL, "deleted_at" TIMESTAMP(3), CONSTRAINT "work_order_milestones_pkey" PRIMARY KEY ("id") ); -- CreateTable CREATE TABLE "billing_records" ( "id" TEXT NOT NULL, "tenant_id" TEXT NOT NULL, "work_order_id" TEXT NOT NULL, "kind" "BillingRecordKind" NOT NULL, "milestone_id" TEXT, "report_id" TEXT, "period_from" TIMESTAMP(3) NOT NULL, "period_to" TIMESTAMP(3) NOT NULL, "status" "BillingRecordStatus" NOT NULL DEFAULT 'open', "snapshot" JSONB, "pdf_document_id" TEXT, "pdf_checksum" TEXT, "invoice_number" TEXT, "billed_by_id" TEXT, "billed_at" TIMESTAMP(3), "voided_by_id" TEXT, "voided_at" TIMESTAMP(3), "void_reason" TEXT, "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, "updated_at" TIMESTAMP(3) NOT NULL, CONSTRAINT "billing_records_pkey" PRIMARY KEY ("id") ); -- CreateIndex CREATE INDEX "work_order_milestones_tenant_id_work_order_id_idx" ON "work_order_milestones"("tenant_id", "work_order_id"); -- CreateIndex CREATE INDEX "work_order_milestones_tenant_id_status_idx" ON "work_order_milestones"("tenant_id", "status"); -- CreateIndex CREATE INDEX "billing_records_tenant_id_status_created_at_idx" ON "billing_records"("tenant_id", "status", "created_at"); -- CreateIndex CREATE INDEX "billing_records_tenant_id_work_order_id_idx" ON "billing_records"("tenant_id", "work_order_id"); -- CreateIndex CREATE INDEX "material_usages_tenant_id_billing_record_id_idx" ON "material_usages"("tenant_id", "billing_record_id"); -- CreateIndex CREATE INDEX "time_entries_tenant_id_billing_record_id_idx" ON "time_entries"("tenant_id", "billing_record_id"); -- AddForeignKey ALTER TABLE "work_order_milestones" ADD CONSTRAINT "work_order_milestones_work_order_id_fkey" FOREIGN KEY ("work_order_id") REFERENCES "work_orders"("id") ON DELETE CASCADE ON UPDATE CASCADE; -- AddForeignKey ALTER TABLE "billing_records" ADD CONSTRAINT "billing_records_work_order_id_fkey" FOREIGN KEY ("work_order_id") REFERENCES "work_orders"("id") ON DELETE CASCADE ON UPDATE CASCADE; -- L14 Abrechnungsübersicht: Mandantentrennung (RLS) für die neuen Tabellen SELECT enable_tenant_rls('work_order_milestones'); SELECT enable_tenant_rls('billing_records'); -- Höchstens ein nicht stornierter Abrechnungseintrag je Quelle (Stornierung legt einen neuen offenen Eintrag an) CREATE UNIQUE INDEX "billing_records_order_completion_active_key" ON "billing_records"("tenant_id", "work_order_id") WHERE "kind" = 'order_completion' AND "status" <> 'voided'; CREATE UNIQUE INDEX "billing_records_milestone_active_key" ON "billing_records"("tenant_id", "milestone_id") WHERE "kind" = 'milestone' AND "status" <> 'voided'; CREATE UNIQUE INDEX "billing_records_daily_report_active_key" ON "billing_records"("tenant_id", "report_id") WHERE "kind" = 'daily_report' AND "status" <> 'voided';