From 0806030bd98998a77ac90d8a1c9bba0930fed8d9 Mon Sep 17 00:00:00 2001 From: Martin Date: Fri, 24 Jul 2026 10:12:56 +0200 Subject: [PATCH] Datenmodell: Asset-Typen SOFTWARE und PROJECT mit Fachprofilen MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Neue AssetType-Werte SOFTWARE und PROJECT plus 1:1-Profile: - SoftwareProfile (Anbieter-Verknüpfung, Version/Patch-Stand, Freigabestatus BEANTRAGT/FREIGEGEBEN/GESPERRT, Freigeber, Kritikalität, Review) - ProjectProfile (IS-Klassifizierung, ISB-Einbindung, Projektstatus) Migration inkl. RLS-Policies; TENANT_MODELS und Dependency-Graph (Node-Typ + Icons Package/FolderKanban) um beide Typen erweitert. Co-Authored-By: Claude Opus 4.8 --- .../migration.sql | 91 +++++++++++++++++++ prisma/schema.prisma | 68 ++++++++++++++ src/components/dependency-graph.tsx | 4 + src/server/db.ts | 2 + src/server/dependency-graph.ts | 2 + 5 files changed, 167 insertions(+) create mode 100644 prisma/migrations/20260724095408_software_and_project_assets/migration.sql diff --git a/prisma/migrations/20260724095408_software_and_project_assets/migration.sql b/prisma/migrations/20260724095408_software_and_project_assets/migration.sql new file mode 100644 index 0000000..93b2885 --- /dev/null +++ b/prisma/migrations/20260724095408_software_and_project_assets/migration.sql @@ -0,0 +1,91 @@ +-- CreateEnum +CREATE TYPE "SoftwareApprovalStatus" AS ENUM ('BEANTRAGT', 'FREIGEGEBEN', 'GESPERRT'); + +-- CreateEnum +CREATE TYPE "ProjectStatus" AS ENUM ('GEPLANT', 'LAUFEND', 'ABGESCHLOSSEN', 'ABGEBROCHEN'); + +-- AlterEnum +-- This migration adds more than one value to an enum. +-- With PostgreSQL versions 11 and earlier, this is not possible +-- in a single migration. This can be worked around by creating +-- multiple migrations, each migration adding only one value to +-- the enum. + + +ALTER TYPE "AssetType" ADD VALUE 'SOFTWARE'; +ALTER TYPE "AssetType" ADD VALUE 'PROJECT'; + +-- CreateTable +CREATE TABLE "software_profiles" ( + "id" TEXT NOT NULL, + "tenant_id" TEXT NOT NULL, + "asset_id" TEXT NOT NULL, + "ref_no" INTEGER NOT NULL, + "provider_asset_id" TEXT, + "version" TEXT, + "approval_status" "SoftwareApprovalStatus" NOT NULL DEFAULT 'BEANTRAGT', + "approved_by" TEXT, + "criticality" INTEGER NOT NULL DEFAULT 1, + "next_review" TIMESTAMP(3), + "notes" TEXT, + "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updated_at" TIMESTAMP(3) NOT NULL, + "created_by" TEXT, + + CONSTRAINT "software_profiles_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "project_profiles" ( + "id" TEXT NOT NULL, + "tenant_id" TEXT NOT NULL, + "asset_id" TEXT NOT NULL, + "ref_no" INTEGER NOT NULL, + "classification" TEXT, + "isb_involved" BOOLEAN NOT NULL DEFAULT false, + "status" "ProjectStatus" NOT NULL DEFAULT 'GEPLANT', + "notes" TEXT, + "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updated_at" TIMESTAMP(3) NOT NULL, + "created_by" TEXT, + + CONSTRAINT "project_profiles_pkey" PRIMARY KEY ("id") +); + +-- CreateIndex +CREATE UNIQUE INDEX "software_profiles_asset_id_key" ON "software_profiles"("asset_id"); + +-- CreateIndex +CREATE INDEX "software_profiles_tenant_id_idx" ON "software_profiles"("tenant_id"); + +-- CreateIndex +CREATE UNIQUE INDEX "software_profiles_tenant_id_ref_no_key" ON "software_profiles"("tenant_id", "ref_no"); + +-- CreateIndex +CREATE UNIQUE INDEX "project_profiles_asset_id_key" ON "project_profiles"("asset_id"); + +-- CreateIndex +CREATE INDEX "project_profiles_tenant_id_idx" ON "project_profiles"("tenant_id"); + +-- CreateIndex +CREATE UNIQUE INDEX "project_profiles_tenant_id_ref_no_key" ON "project_profiles"("tenant_id", "ref_no"); + +-- AddForeignKey +ALTER TABLE "software_profiles" ADD CONSTRAINT "software_profiles_asset_id_fkey" FOREIGN KEY ("asset_id") REFERENCES "assets"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "software_profiles" ADD CONSTRAINT "software_profiles_provider_asset_id_fkey" FOREIGN KEY ("provider_asset_id") REFERENCES "assets"("id") ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "project_profiles" ADD CONSTRAINT "project_profiles_asset_id_fkey" FOREIGN KEY ("asset_id") REFERENCES "assets"("id") ON DELETE CASCADE ON UPDATE CASCADE; + + +-- RLS für die neuen mandantenbezogenen Tabellen. +DO $$ +DECLARE t text; +BEGIN + FOREACH t IN ARRAY ARRAY['software_profiles','project_profiles'] LOOP + EXECUTE format('ALTER TABLE %I ENABLE ROW LEVEL SECURITY', t); + EXECUTE format('CREATE POLICY tenant_isolation ON %I USING (tenant_id = current_setting(''app.tenant_id'', true))', t); + END LOOP; +END $$; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 7c653c4..2ffb0b4 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -211,6 +211,8 @@ enum AssetType { LOCATION SUPPLIER IT_SERVICE + SOFTWARE + PROJECT PERSON DATA } @@ -260,6 +262,9 @@ model Asset { supplierProfile SupplierProfile? serviceProfile ITServiceProfile? @relation("serviceAsset") serviceProvided ITServiceProfile[] @relation("serviceProvider") + softwareProfile SoftwareProfile? @relation("softwareAsset") + softwareProvided SoftwareProfile[] @relation("softwareProvider") + projectProfile ProjectProfile? assessments SupplierAssessment[] evidence SupplierEvidence[] decisions ManagementDecision[] @@ -581,6 +586,69 @@ model ITServiceProfile { @@map("it_service_profiles") } +// Software-Whitelist als Asset (analog IT-Service, R11/VA-16): eine freigegebene +// Software ist ein Asset(type SOFTWARE) mit optionalem Anbieter (Asset type SUPPLIER). +model SoftwareProfile { + id String @id @default(cuid()) + tenantId String @map("tenant_id") + assetId String @unique @map("asset_id") + refNo Int @map("ref_no") + providerAssetId String? @map("provider_asset_id") // Asset(type SUPPLIER) + version String? // Version/Patch-Stand + approvalStatus SoftwareApprovalStatus @default(BEANTRAGT) @map("approval_status") + approvedBy String? @map("approved_by") + criticality Int @default(1) + nextReview DateTime? @map("next_review") + notes String? + + createdAt DateTime @default(now()) @map("created_at") + updatedAt DateTime @updatedAt @map("updated_at") + createdBy String? @map("created_by") + + asset Asset @relation("softwareAsset", fields: [assetId], references: [id], onDelete: Cascade) + provider Asset? @relation("softwareProvider", fields: [providerAssetId], references: [id]) + + @@unique([tenantId, refNo]) + @@index([tenantId]) + @@map("software_profiles") +} + +enum SoftwareApprovalStatus { + BEANTRAGT // Freigabe beantragt + FREIGEGEBEN // freigegeben (Whitelist) + GESPERRT // gesperrt/untersagt +} + +// Informationssicherheit in Projekten (R01/VA-19): ein Projekt ist ein Asset(type PROJECT) +// mit Kritikalität (C/I/A am Asset) und Risiko-Verknüpfung (RiskAsset). +model ProjectProfile { + id String @id @default(cuid()) + tenantId String @map("tenant_id") + assetId String @unique @map("asset_id") + refNo Int @map("ref_no") + classification String? // IS-Klassifizierung + isbInvolved Boolean @default(false) @map("isb_involved") + status ProjectStatus @default(GEPLANT) + notes String? + + createdAt DateTime @default(now()) @map("created_at") + updatedAt DateTime @updatedAt @map("updated_at") + createdBy String? @map("created_by") + + asset Asset @relation(fields: [assetId], references: [id], onDelete: Cascade) + + @@unique([tenantId, refNo]) + @@index([tenantId]) + @@map("project_profiles") +} + +enum ProjectStatus { + GEPLANT + LAUFEND + ABGESCHLOSSEN + ABGEBROCHEN +} + // Bewertung & Nachweise (6.1.1) model SupplierAssessment { id String @id @default(cuid()) diff --git a/src/components/dependency-graph.tsx b/src/components/dependency-graph.tsx index 1b72992..a41340c 100644 --- a/src/components/dependency-graph.tsx +++ b/src/components/dependency-graph.tsx @@ -24,6 +24,8 @@ import { MapPin, Truck, Server, + Package, + FolderKanban, Users, FileText, GitBranch, @@ -44,6 +46,8 @@ const KIND_ICON: Record = { LOCATION: MapPin, SUPPLIER: Truck, IT_SERVICE: Server, + SOFTWARE: Package, + PROJECT: FolderKanban, PERSON: Users, DATA: Database, }; diff --git a/src/server/db.ts b/src/server/db.ts index e5d89aa..877dc7e 100644 --- a/src/server/db.ts +++ b/src/server/db.ts @@ -45,6 +45,8 @@ const TENANT_MODELS = new Set([ "RiskMeasure", "SupplierProfile", "ITServiceProfile", + "SoftwareProfile", + "ProjectProfile", "SupplierAssessment", "Contract", "Nda", diff --git a/src/server/dependency-graph.ts b/src/server/dependency-graph.ts index 998b5b2..e667bc1 100644 --- a/src/server/dependency-graph.ts +++ b/src/server/dependency-graph.ts @@ -21,6 +21,8 @@ export type GraphNodeKind = | "LOCATION" | "SUPPLIER" | "IT_SERVICE" + | "SOFTWARE" + | "PROJECT" | "PERSON" | "DATA";