feat(sdk): VVT master libraries, process templates, Loeschfristen profiling + document

VVT: Master library tables (7 catalogs), 500+ seed entries, process templates
with instantiation, library API endpoints + 18 tests.
Loeschfristen: Baseline catalog, compliance checks, profiling engine, HTML document
generator, MkDocs documentation.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Benjamin Admin
2026-03-19 11:56:25 +01:00
parent f2819b99af
commit 2a70441eaa
20 changed files with 6621 additions and 9 deletions

View File

@@ -1755,6 +1755,20 @@ class VVTActivityCreate(BaseModel):
next_review_at: Optional[datetime] = None
created_by: Optional[str] = None
dsfa_id: Optional[str] = None
# Library refs (optional, parallel to freetext)
purpose_refs: Optional[List[str]] = None
legal_basis_refs: Optional[List[str]] = None
data_subject_refs: Optional[List[str]] = None
data_category_refs: Optional[List[str]] = None
recipient_refs: Optional[List[str]] = None
retention_rule_ref: Optional[str] = None
transfer_mechanism_refs: Optional[List[str]] = None
tom_refs: Optional[List[str]] = None
source_template_id: Optional[str] = None
risk_score: Optional[int] = None
linked_loeschfristen_ids: Optional[List[str]] = None
linked_tom_measure_ids: Optional[List[str]] = None
art30_completeness: Optional[Dict[str, Any]] = None
class VVTActivityUpdate(BaseModel):
@@ -1783,6 +1797,20 @@ class VVTActivityUpdate(BaseModel):
next_review_at: Optional[datetime] = None
created_by: Optional[str] = None
dsfa_id: Optional[str] = None
# Library refs
purpose_refs: Optional[List[str]] = None
legal_basis_refs: Optional[List[str]] = None
data_subject_refs: Optional[List[str]] = None
data_category_refs: Optional[List[str]] = None
recipient_refs: Optional[List[str]] = None
retention_rule_ref: Optional[str] = None
transfer_mechanism_refs: Optional[List[str]] = None
tom_refs: Optional[List[str]] = None
source_template_id: Optional[str] = None
risk_score: Optional[int] = None
linked_loeschfristen_ids: Optional[List[str]] = None
linked_tom_measure_ids: Optional[List[str]] = None
art30_completeness: Optional[Dict[str, Any]] = None
class VVTActivityResponse(BaseModel):
@@ -1813,6 +1841,20 @@ class VVTActivityResponse(BaseModel):
next_review_at: Optional[datetime] = None
created_by: Optional[str] = None
dsfa_id: Optional[str] = None
# Library refs
purpose_refs: Optional[List[str]] = None
legal_basis_refs: Optional[List[str]] = None
data_subject_refs: Optional[List[str]] = None
data_category_refs: Optional[List[str]] = None
recipient_refs: Optional[List[str]] = None
retention_rule_ref: Optional[str] = None
transfer_mechanism_refs: Optional[List[str]] = None
tom_refs: Optional[List[str]] = None
source_template_id: Optional[str] = None
risk_score: Optional[int] = None
linked_loeschfristen_ids: Optional[List[str]] = None
linked_tom_measure_ids: Optional[List[str]] = None
art30_completeness: Optional[Dict[str, Any]] = None
created_at: datetime
updated_at: Optional[datetime] = None

View File

@@ -0,0 +1,427 @@
"""
FastAPI routes for VVT Master Libraries + Process Templates.
Library endpoints (read-only, global):
GET /vvt/libraries — Overview: all library types + counts
GET /vvt/libraries/data-subjects — Data subjects (filter: typical_for)
GET /vvt/libraries/data-categories — Hierarchical (filter: parent_id, is_art9, flat)
GET /vvt/libraries/recipients — Recipients (filter: type)
GET /vvt/libraries/legal-bases — Legal bases (filter: is_art9, type)
GET /vvt/libraries/retention-rules — Retention rules
GET /vvt/libraries/transfer-mechanisms — Transfer mechanisms
GET /vvt/libraries/purposes — Purposes (filter: typical_for)
GET /vvt/libraries/toms — TOMs (filter: category)
Template endpoints:
GET /vvt/templates — List templates (filter: business_function, search)
GET /vvt/templates/{id} — Single template with resolved labels
POST /vvt/templates/{id}/instantiate — Create VVT activity from template
"""
import logging
import uuid
from datetime import datetime
from typing import Optional
from fastapi import APIRouter, Depends, HTTPException, Query, Request
from sqlalchemy.orm import Session
from classroom_engine.database import get_db
from ..db.vvt_library_models import (
VVTLibDataSubjectDB,
VVTLibDataCategoryDB,
VVTLibRecipientDB,
VVTLibLegalBasisDB,
VVTLibRetentionRuleDB,
VVTLibTransferMechanismDB,
VVTLibPurposeDB,
VVTLibTomDB,
VVTProcessTemplateDB,
)
from ..db.vvt_models import VVTActivityDB, VVTAuditLogDB
from .tenant_utils import get_tenant_id
logger = logging.getLogger(__name__)
router = APIRouter(prefix="/vvt", tags=["compliance-vvt-libraries"])
# ============================================================================
# Helper: row → dict
# ============================================================================
def _row_to_dict(row, extra_fields=None):
"""Generic row → dict for library items."""
d = {
"id": row.id,
"label_de": row.label_de,
}
if hasattr(row, 'description_de') and row.description_de:
d["description_de"] = row.description_de
if hasattr(row, 'sort_order'):
d["sort_order"] = row.sort_order
if extra_fields:
for f in extra_fields:
if hasattr(row, f):
val = getattr(row, f)
if val is not None:
d[f] = val
return d
# ============================================================================
# Library Overview
# ============================================================================
@router.get("/libraries")
async def get_libraries_overview(db: Session = Depends(get_db)):
"""Overview of all library types with item counts."""
return {
"libraries": [
{"type": "data-subjects", "count": db.query(VVTLibDataSubjectDB).count()},
{"type": "data-categories", "count": db.query(VVTLibDataCategoryDB).count()},
{"type": "recipients", "count": db.query(VVTLibRecipientDB).count()},
{"type": "legal-bases", "count": db.query(VVTLibLegalBasisDB).count()},
{"type": "retention-rules", "count": db.query(VVTLibRetentionRuleDB).count()},
{"type": "transfer-mechanisms", "count": db.query(VVTLibTransferMechanismDB).count()},
{"type": "purposes", "count": db.query(VVTLibPurposeDB).count()},
{"type": "toms", "count": db.query(VVTLibTomDB).count()},
]
}
# ============================================================================
# Data Subjects
# ============================================================================
@router.get("/libraries/data-subjects")
async def list_data_subjects(
typical_for: Optional[str] = Query(None, description="Filter by business function"),
db: Session = Depends(get_db),
):
query = db.query(VVTLibDataSubjectDB).order_by(VVTLibDataSubjectDB.sort_order)
rows = query.all()
items = [_row_to_dict(r, ["art9_relevant", "typical_for"]) for r in rows]
if typical_for:
items = [i for i in items if typical_for in (i.get("typical_for") or [])]
return items
# ============================================================================
# Data Categories (hierarchical)
# ============================================================================
@router.get("/libraries/data-categories")
async def list_data_categories(
flat: Optional[bool] = Query(False, description="Return flat list instead of tree"),
parent_id: Optional[str] = Query(None),
is_art9: Optional[bool] = Query(None),
db: Session = Depends(get_db),
):
query = db.query(VVTLibDataCategoryDB).order_by(VVTLibDataCategoryDB.sort_order)
if parent_id is not None:
query = query.filter(VVTLibDataCategoryDB.parent_id == parent_id)
if is_art9 is not None:
query = query.filter(VVTLibDataCategoryDB.is_art9 == is_art9)
rows = query.all()
extra = ["parent_id", "is_art9", "is_art10", "risk_weight", "default_retention_rule", "default_legal_basis"]
items = [_row_to_dict(r, extra) for r in rows]
if flat or parent_id is not None or is_art9 is not None:
return items
# Build tree
by_parent: dict = {}
for item in items:
pid = item.get("parent_id")
by_parent.setdefault(pid, []).append(item)
tree = []
for item in by_parent.get(None, []):
children = by_parent.get(item["id"], [])
if children:
item["children"] = children
tree.append(item)
return tree
# ============================================================================
# Recipients
# ============================================================================
@router.get("/libraries/recipients")
async def list_recipients(
type: Optional[str] = Query(None, description="INTERNAL, PROCESSOR, CONTROLLER, AUTHORITY"),
db: Session = Depends(get_db),
):
query = db.query(VVTLibRecipientDB).order_by(VVTLibRecipientDB.sort_order)
if type:
query = query.filter(VVTLibRecipientDB.type == type)
rows = query.all()
return [_row_to_dict(r, ["type", "is_third_country", "country"]) for r in rows]
# ============================================================================
# Legal Bases
# ============================================================================
@router.get("/libraries/legal-bases")
async def list_legal_bases(
is_art9: Optional[bool] = Query(None),
type: Optional[str] = Query(None),
db: Session = Depends(get_db),
):
query = db.query(VVTLibLegalBasisDB).order_by(VVTLibLegalBasisDB.sort_order)
if is_art9 is not None:
query = query.filter(VVTLibLegalBasisDB.is_art9 == is_art9)
if type:
query = query.filter(VVTLibLegalBasisDB.type == type)
rows = query.all()
return [_row_to_dict(r, ["article", "type", "is_art9", "typical_national_law"]) for r in rows]
# ============================================================================
# Retention Rules
# ============================================================================
@router.get("/libraries/retention-rules")
async def list_retention_rules(db: Session = Depends(get_db)):
rows = db.query(VVTLibRetentionRuleDB).order_by(VVTLibRetentionRuleDB.sort_order).all()
return [_row_to_dict(r, ["legal_basis", "duration", "duration_unit", "start_event", "deletion_procedure"]) for r in rows]
# ============================================================================
# Transfer Mechanisms
# ============================================================================
@router.get("/libraries/transfer-mechanisms")
async def list_transfer_mechanisms(db: Session = Depends(get_db)):
rows = db.query(VVTLibTransferMechanismDB).order_by(VVTLibTransferMechanismDB.sort_order).all()
return [_row_to_dict(r, ["article", "requires_tia"]) for r in rows]
# ============================================================================
# Purposes
# ============================================================================
@router.get("/libraries/purposes")
async def list_purposes(
typical_for: Optional[str] = Query(None),
db: Session = Depends(get_db),
):
rows = db.query(VVTLibPurposeDB).order_by(VVTLibPurposeDB.sort_order).all()
items = [_row_to_dict(r, ["typical_legal_basis", "typical_for"]) for r in rows]
if typical_for:
items = [i for i in items if typical_for in (i.get("typical_for") or [])]
return items
# ============================================================================
# TOMs
# ============================================================================
@router.get("/libraries/toms")
async def list_toms(
category: Optional[str] = Query(None),
db: Session = Depends(get_db),
):
query = db.query(VVTLibTomDB).order_by(VVTLibTomDB.sort_order)
if category:
query = query.filter(VVTLibTomDB.category == category)
rows = query.all()
return [_row_to_dict(r, ["category", "art32_reference"]) for r in rows]
# ============================================================================
# Process Templates
# ============================================================================
def _template_to_dict(t: VVTProcessTemplateDB) -> dict:
return {
"id": t.id,
"name": t.name,
"description": t.description,
"business_function": t.business_function,
"purpose_refs": t.purpose_refs or [],
"legal_basis_refs": t.legal_basis_refs or [],
"data_subject_refs": t.data_subject_refs or [],
"data_category_refs": t.data_category_refs or [],
"recipient_refs": t.recipient_refs or [],
"tom_refs": t.tom_refs or [],
"transfer_mechanism_refs": t.transfer_mechanism_refs or [],
"retention_rule_ref": t.retention_rule_ref,
"typical_systems": t.typical_systems or [],
"protection_level": t.protection_level or "MEDIUM",
"dpia_required": t.dpia_required or False,
"risk_score": t.risk_score,
"tags": t.tags or [],
"is_system": t.is_system,
"sort_order": t.sort_order,
}
def _resolve_labels(template_dict: dict, db: Session) -> dict:
"""Resolve library IDs to labels within the template dict."""
resolvers = {
"purpose_refs": (VVTLibPurposeDB, "purpose_labels"),
"legal_basis_refs": (VVTLibLegalBasisDB, "legal_basis_labels"),
"data_subject_refs": (VVTLibDataSubjectDB, "data_subject_labels"),
"data_category_refs": (VVTLibDataCategoryDB, "data_category_labels"),
"recipient_refs": (VVTLibRecipientDB, "recipient_labels"),
"tom_refs": (VVTLibTomDB, "tom_labels"),
"transfer_mechanism_refs": (VVTLibTransferMechanismDB, "transfer_mechanism_labels"),
}
for refs_key, (model, labels_key) in resolvers.items():
ids = template_dict.get(refs_key) or []
if ids:
rows = db.query(model).filter(model.id.in_(ids)).all()
label_map = {r.id: r.label_de for r in rows}
template_dict[labels_key] = {rid: label_map.get(rid, rid) for rid in ids}
# Resolve single retention rule
rr = template_dict.get("retention_rule_ref")
if rr:
row = db.query(VVTLibRetentionRuleDB).filter(VVTLibRetentionRuleDB.id == rr).first()
if row:
template_dict["retention_rule_label"] = row.label_de
return template_dict
@router.get("/templates")
async def list_templates(
business_function: Optional[str] = Query(None),
search: Optional[str] = Query(None),
db: Session = Depends(get_db),
):
"""List process templates (system + tenant)."""
query = db.query(VVTProcessTemplateDB).order_by(VVTProcessTemplateDB.sort_order)
if business_function:
query = query.filter(VVTProcessTemplateDB.business_function == business_function)
if search:
term = f"%{search}%"
query = query.filter(
(VVTProcessTemplateDB.name.ilike(term)) |
(VVTProcessTemplateDB.description.ilike(term))
)
templates = query.all()
return [_template_to_dict(t) for t in templates]
@router.get("/templates/{template_id}")
async def get_template(
template_id: str,
db: Session = Depends(get_db),
):
"""Get a single template with resolved library labels."""
t = db.query(VVTProcessTemplateDB).filter(VVTProcessTemplateDB.id == template_id).first()
if not t:
raise HTTPException(status_code=404, detail=f"Template '{template_id}' not found")
result = _template_to_dict(t)
return _resolve_labels(result, db)
@router.post("/templates/{template_id}/instantiate", status_code=201)
async def instantiate_template(
template_id: str,
http_request: Request,
tid: str = Depends(get_tenant_id),
db: Session = Depends(get_db),
):
"""Create a new VVT activity from a process template."""
t = db.query(VVTProcessTemplateDB).filter(VVTProcessTemplateDB.id == template_id).first()
if not t:
raise HTTPException(status_code=404, detail=f"Template '{template_id}' not found")
# Generate unique VVT-ID
count = db.query(VVTActivityDB).filter(VVTActivityDB.tenant_id == tid).count()
vvt_id = f"VVT-{count + 1:04d}"
# Resolve library IDs to freetext labels for backward-compat fields
purpose_labels = _resolve_ids(db, VVTLibPurposeDB, t.purpose_refs or [])
legal_labels = _resolve_ids(db, VVTLibLegalBasisDB, t.legal_basis_refs or [])
subject_labels = _resolve_ids(db, VVTLibDataSubjectDB, t.data_subject_refs or [])
category_labels = _resolve_ids(db, VVTLibDataCategoryDB, t.data_category_refs or [])
recipient_labels = _resolve_ids(db, VVTLibRecipientDB, t.recipient_refs or [])
# Resolve retention rule
retention_period = {}
if t.retention_rule_ref:
rr = db.query(VVTLibRetentionRuleDB).filter(VVTLibRetentionRuleDB.id == t.retention_rule_ref).first()
if rr:
retention_period = {
"description": rr.label_de,
"legalBasis": rr.legal_basis or "",
"deletionProcedure": rr.deletion_procedure or "",
"duration": rr.duration,
"durationUnit": rr.duration_unit,
}
# Build structured TOMs from tom_refs
structured_toms = {"accessControl": [], "confidentiality": [], "integrity": [], "availability": [], "separation": []}
if t.tom_refs:
tom_rows = db.query(VVTLibTomDB).filter(VVTLibTomDB.id.in_(t.tom_refs)).all()
for tr in tom_rows:
cat = tr.category
if cat in structured_toms:
structured_toms[cat].append(tr.label_de)
act = VVTActivityDB(
tenant_id=tid,
vvt_id=vvt_id,
name=t.name,
description=t.description or "",
purposes=purpose_labels,
legal_bases=[{"type": lid, "description": lbl} for lid, lbl in zip(t.legal_basis_refs or [], legal_labels)],
data_subject_categories=subject_labels,
personal_data_categories=category_labels,
recipient_categories=[{"type": "unknown", "name": lbl} for lbl in recipient_labels],
retention_period=retention_period,
business_function=t.business_function,
systems=[{"systemId": s, "name": s} for s in (t.typical_systems or [])],
protection_level=t.protection_level or "MEDIUM",
dpia_required=t.dpia_required or False,
structured_toms=structured_toms,
status="DRAFT",
created_by=http_request.headers.get("X-User-ID", "system"),
# Library refs
purpose_refs=t.purpose_refs,
legal_basis_refs=t.legal_basis_refs,
data_subject_refs=t.data_subject_refs,
data_category_refs=t.data_category_refs,
recipient_refs=t.recipient_refs,
retention_rule_ref=t.retention_rule_ref,
transfer_mechanism_refs=t.transfer_mechanism_refs,
tom_refs=t.tom_refs,
source_template_id=t.id,
risk_score=t.risk_score,
)
db.add(act)
db.flush()
# Audit log
audit = VVTAuditLogDB(
tenant_id=tid,
action="CREATE",
entity_type="activity",
entity_id=act.id,
changed_by=http_request.headers.get("X-User-ID", "system"),
new_values={"vvt_id": vvt_id, "source_template_id": t.id, "name": t.name},
)
db.add(audit)
db.commit()
db.refresh(act)
# Return full response
from .vvt_routes import _activity_to_response
return _activity_to_response(act)
def _resolve_ids(db: Session, model, ids: list) -> list:
"""Resolve list of library IDs to list of label_de strings."""
if not ids:
return []
rows = db.query(model).filter(model.id.in_(ids)).all()
label_map = {r.id: r.label_de for r in rows}
return [label_map.get(i, i) for i in ids]

View File

@@ -174,6 +174,20 @@ def _activity_to_response(act: VVTActivityDB) -> VVTActivityResponse:
next_review_at=act.next_review_at,
created_by=act.created_by,
dsfa_id=str(act.dsfa_id) if act.dsfa_id else None,
# Library refs
purpose_refs=act.purpose_refs,
legal_basis_refs=act.legal_basis_refs,
data_subject_refs=act.data_subject_refs,
data_category_refs=act.data_category_refs,
recipient_refs=act.recipient_refs,
retention_rule_ref=act.retention_rule_ref,
transfer_mechanism_refs=act.transfer_mechanism_refs,
tom_refs=act.tom_refs,
source_template_id=act.source_template_id,
risk_score=act.risk_score,
linked_loeschfristen_ids=act.linked_loeschfristen_ids,
linked_tom_measure_ids=act.linked_tom_measure_ids,
art30_completeness=act.art30_completeness,
created_at=act.created_at,
updated_at=act.updated_at,
)
@@ -336,6 +350,107 @@ async def delete_activity(
return {"success": True, "message": f"Activity {activity_id} deleted"}
# ============================================================================
# Art. 30 Completeness Check
# ============================================================================
@router.get("/activities/{activity_id}/completeness")
async def get_activity_completeness(
activity_id: str,
tid: str = Depends(get_tenant_id),
db: Session = Depends(get_db),
):
"""Calculate Art. 30 completeness score for a VVT activity."""
act = db.query(VVTActivityDB).filter(
VVTActivityDB.id == activity_id,
VVTActivityDB.tenant_id == tid,
).first()
if not act:
raise HTTPException(status_code=404, detail=f"Activity {activity_id} not found")
return _calculate_completeness(act)
def _calculate_completeness(act: VVTActivityDB) -> dict:
"""Calculate Art. 30 completeness — required fields per DSGVO Art. 30 Abs. 1."""
missing = []
warnings = []
total_checks = 10
passed = 0
# 1. Name/Zweck
if act.name:
passed += 1
else:
missing.append("name")
# 2. Verarbeitungszwecke
has_purposes = bool(act.purposes) or bool(act.purpose_refs)
if has_purposes:
passed += 1
else:
missing.append("purposes")
# 3. Rechtsgrundlage
has_legal = bool(act.legal_bases) or bool(act.legal_basis_refs)
if has_legal:
passed += 1
else:
missing.append("legal_bases")
# 4. Betroffenenkategorien
has_subjects = bool(act.data_subject_categories) or bool(act.data_subject_refs)
if has_subjects:
passed += 1
else:
missing.append("data_subjects")
# 5. Datenkategorien
has_categories = bool(act.personal_data_categories) or bool(act.data_category_refs)
if has_categories:
passed += 1
else:
missing.append("data_categories")
# 6. Empfaenger
has_recipients = bool(act.recipient_categories) or bool(act.recipient_refs)
if has_recipients:
passed += 1
else:
missing.append("recipients")
# 7. Drittland-Uebermittlung (checked but not strictly required)
passed += 1 # always passes — no transfer is valid state
# 8. Loeschfristen
has_retention = bool(act.retention_period and act.retention_period.get('description')) or bool(act.retention_rule_ref)
if has_retention:
passed += 1
else:
missing.append("retention_period")
# 9. TOM-Beschreibung
has_tom = bool(act.tom_description) or bool(act.tom_refs) or bool(act.structured_toms)
if has_tom:
passed += 1
else:
missing.append("tom_description")
# 10. Verantwortlicher
if act.responsible:
passed += 1
else:
missing.append("responsible")
# Warnings
if act.dpia_required and not act.dsfa_id:
warnings.append("dpia_required_but_no_dsfa_linked")
if act.third_country_transfers and not act.transfer_mechanism_refs:
warnings.append("third_country_transfer_without_mechanism")
score = int((passed / total_checks) * 100)
return {"score": score, "missing": missing, "warnings": warnings, "passed": passed, "total": total_checks}
# ============================================================================
# Audit Log
# ============================================================================

View File

@@ -0,0 +1,164 @@
"""
SQLAlchemy models for VVT Master Libraries + Process Templates.
Tables (global, no tenant_id):
- vvt_lib_data_subjects
- vvt_lib_data_categories (hierarchical, self-referencing)
- vvt_lib_recipients
- vvt_lib_legal_bases
- vvt_lib_retention_rules
- vvt_lib_transfer_mechanisms
- vvt_lib_purposes
- vvt_lib_toms
Tenant-scoped:
- vvt_process_templates (system + tenant-specific)
"""
from datetime import datetime
from sqlalchemy import (
Column, String, Text, Boolean, Integer, DateTime, JSON, Index,
ForeignKey,
)
from sqlalchemy.dialects.postgresql import UUID
from classroom_engine.database import Base
class VVTLibDataSubjectDB(Base):
__tablename__ = 'vvt_lib_data_subjects'
id = Column(String(50), primary_key=True)
label_de = Column(String(200), nullable=False)
description_de = Column(Text)
art9_relevant = Column(Boolean, default=False)
typical_for = Column(JSON, default=list)
sort_order = Column(Integer, default=0)
created_at = Column(DateTime(timezone=True), default=datetime.utcnow)
class VVTLibDataCategoryDB(Base):
__tablename__ = 'vvt_lib_data_categories'
id = Column(String(50), primary_key=True)
parent_id = Column(String(50), ForeignKey('vvt_lib_data_categories.id', ondelete='SET NULL'), nullable=True)
label_de = Column(String(200), nullable=False)
description_de = Column(Text)
is_art9 = Column(Boolean, default=False)
is_art10 = Column(Boolean, default=False)
risk_weight = Column(Integer, default=1)
default_retention_rule = Column(String(50))
default_legal_basis = Column(String(50))
sort_order = Column(Integer, default=0)
created_at = Column(DateTime(timezone=True), default=datetime.utcnow)
class VVTLibRecipientDB(Base):
__tablename__ = 'vvt_lib_recipients'
id = Column(String(50), primary_key=True)
type = Column(String(20), nullable=False)
label_de = Column(String(200), nullable=False)
description_de = Column(Text)
is_third_country = Column(Boolean, default=False)
country = Column(String(5))
sort_order = Column(Integer, default=0)
created_at = Column(DateTime(timezone=True), default=datetime.utcnow)
class VVTLibLegalBasisDB(Base):
__tablename__ = 'vvt_lib_legal_bases'
id = Column(String(50), primary_key=True)
article = Column(String(50), nullable=False)
type = Column(String(30), nullable=False)
label_de = Column(String(300), nullable=False)
description_de = Column(Text)
is_art9 = Column(Boolean, default=False)
typical_national_law = Column(String(100))
sort_order = Column(Integer, default=0)
created_at = Column(DateTime(timezone=True), default=datetime.utcnow)
class VVTLibRetentionRuleDB(Base):
__tablename__ = 'vvt_lib_retention_rules'
id = Column(String(50), primary_key=True)
label_de = Column(String(300), nullable=False)
description_de = Column(Text)
legal_basis = Column(String(200))
duration = Column(Integer, nullable=False)
duration_unit = Column(String(10), nullable=False)
start_event = Column(String(200))
deletion_procedure = Column(String(500))
sort_order = Column(Integer, default=0)
created_at = Column(DateTime(timezone=True), default=datetime.utcnow)
class VVTLibTransferMechanismDB(Base):
__tablename__ = 'vvt_lib_transfer_mechanisms'
id = Column(String(50), primary_key=True)
label_de = Column(String(300), nullable=False)
description_de = Column(Text)
article = Column(String(50))
requires_tia = Column(Boolean, default=False)
sort_order = Column(Integer, default=0)
created_at = Column(DateTime(timezone=True), default=datetime.utcnow)
class VVTLibPurposeDB(Base):
__tablename__ = 'vvt_lib_purposes'
id = Column(String(50), primary_key=True)
label_de = Column(String(300), nullable=False)
description_de = Column(Text)
typical_legal_basis = Column(String(50))
typical_for = Column(JSON, default=list)
sort_order = Column(Integer, default=0)
created_at = Column(DateTime(timezone=True), default=datetime.utcnow)
class VVTLibTomDB(Base):
__tablename__ = 'vvt_lib_toms'
id = Column(String(50), primary_key=True)
category = Column(String(30), nullable=False)
label_de = Column(String(300), nullable=False)
description_de = Column(Text)
art32_reference = Column(String(100))
sort_order = Column(Integer, default=0)
created_at = Column(DateTime(timezone=True), default=datetime.utcnow)
class VVTProcessTemplateDB(Base):
__tablename__ = 'vvt_process_templates'
id = Column(String(80), primary_key=True)
name = Column(String(300), nullable=False)
description = Column(Text)
business_function = Column(String(50))
purpose_refs = Column(JSON, default=list)
legal_basis_refs = Column(JSON, default=list)
data_subject_refs = Column(JSON, default=list)
data_category_refs = Column(JSON, default=list)
recipient_refs = Column(JSON, default=list)
tom_refs = Column(JSON, default=list)
transfer_mechanism_refs = Column(JSON, default=list)
retention_rule_ref = Column(String(50))
typical_systems = Column(JSON, default=list)
protection_level = Column(String(10), default='MEDIUM')
dpia_required = Column(Boolean, default=False)
risk_score = Column(Integer)
tags = Column(JSON, default=list)
is_system = Column(Boolean, default=True)
tenant_id = Column(UUID(as_uuid=True), nullable=True)
sort_order = Column(Integer, default=0)
created_at = Column(DateTime(timezone=True), default=datetime.utcnow)
updated_at = Column(DateTime(timezone=True), default=datetime.utcnow, onupdate=datetime.utcnow)
__table_args__ = (
Index('idx_vvt_process_templates_bf', 'business_function'),
Index('idx_vvt_process_templates_system', 'is_system'),
)

View File

@@ -79,6 +79,26 @@ class VVTActivityDB(Base):
next_review_at = Column(DateTime(timezone=True), nullable=True)
created_by = Column(String(200), default='system')
dsfa_id = Column(UUID(as_uuid=True), nullable=True)
# Library refs (Phase 1 — parallel to freetext fields)
purpose_refs = Column(JSON, nullable=True)
legal_basis_refs = Column(JSON, nullable=True)
data_subject_refs = Column(JSON, nullable=True)
data_category_refs = Column(JSON, nullable=True)
recipient_refs = Column(JSON, nullable=True)
retention_rule_ref = Column(String(50), nullable=True)
transfer_mechanism_refs = Column(JSON, nullable=True)
tom_refs = Column(JSON, nullable=True)
# Cross-module links
linked_loeschfristen_ids = Column(JSON, nullable=True)
linked_tom_measure_ids = Column(JSON, nullable=True)
# Template + risk
source_template_id = Column(String(80), nullable=True)
risk_score = Column(Integer, nullable=True)
art30_completeness = Column(JSON, nullable=True)
created_at = Column(DateTime, default=datetime.utcnow, nullable=False)
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)

View File

@@ -0,0 +1,105 @@
-- Migration 064: VVT Master Libraries — 8 global reference tables
-- These are shared across all tenants (no tenant_id).
BEGIN;
-- 1. Data Subjects (Betroffenenkategorien)
CREATE TABLE IF NOT EXISTS vvt_lib_data_subjects (
id VARCHAR(50) PRIMARY KEY,
label_de VARCHAR(200) NOT NULL,
description_de TEXT,
art9_relevant BOOLEAN DEFAULT FALSE,
typical_for JSONB DEFAULT '[]'::jsonb,
sort_order INTEGER DEFAULT 0,
created_at TIMESTAMPTZ DEFAULT NOW()
);
-- 2. Data Categories (Datenkategorien — hierarchisch)
CREATE TABLE IF NOT EXISTS vvt_lib_data_categories (
id VARCHAR(50) PRIMARY KEY,
parent_id VARCHAR(50) REFERENCES vvt_lib_data_categories(id) ON DELETE SET NULL,
label_de VARCHAR(200) NOT NULL,
description_de TEXT,
is_art9 BOOLEAN DEFAULT FALSE,
is_art10 BOOLEAN DEFAULT FALSE,
risk_weight INTEGER DEFAULT 1 CHECK (risk_weight BETWEEN 1 AND 5),
default_retention_rule VARCHAR(50),
default_legal_basis VARCHAR(50),
sort_order INTEGER DEFAULT 0,
created_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE INDEX IF NOT EXISTS idx_vvt_lib_data_categories_parent ON vvt_lib_data_categories(parent_id);
-- 3. Recipients (Empfaengerkategorien)
CREATE TABLE IF NOT EXISTS vvt_lib_recipients (
id VARCHAR(50) PRIMARY KEY,
type VARCHAR(20) NOT NULL CHECK (type IN ('INTERNAL', 'PROCESSOR', 'CONTROLLER', 'AUTHORITY')),
label_de VARCHAR(200) NOT NULL,
description_de TEXT,
is_third_country BOOLEAN DEFAULT FALSE,
country VARCHAR(5),
sort_order INTEGER DEFAULT 0,
created_at TIMESTAMPTZ DEFAULT NOW()
);
-- 4. Legal Bases (Rechtsgrundlagen)
CREATE TABLE IF NOT EXISTS vvt_lib_legal_bases (
id VARCHAR(50) PRIMARY KEY,
article VARCHAR(50) NOT NULL,
type VARCHAR(30) NOT NULL CHECK (type IN ('CONSENT', 'CONTRACT', 'LEGAL_OBLIGATION', 'VITAL_INTEREST', 'PUBLIC_TASK', 'LEGITIMATE_INTEREST', 'ART9', 'NATIONAL')),
label_de VARCHAR(300) NOT NULL,
description_de TEXT,
is_art9 BOOLEAN DEFAULT FALSE,
typical_national_law VARCHAR(100),
sort_order INTEGER DEFAULT 0,
created_at TIMESTAMPTZ DEFAULT NOW()
);
-- 5. Retention Rules (Aufbewahrungsfristen)
CREATE TABLE IF NOT EXISTS vvt_lib_retention_rules (
id VARCHAR(50) PRIMARY KEY,
label_de VARCHAR(300) NOT NULL,
description_de TEXT,
legal_basis VARCHAR(200),
duration INTEGER NOT NULL,
duration_unit VARCHAR(10) NOT NULL CHECK (duration_unit IN ('DAYS', 'MONTHS', 'YEARS')),
start_event VARCHAR(200),
deletion_procedure VARCHAR(500),
sort_order INTEGER DEFAULT 0,
created_at TIMESTAMPTZ DEFAULT NOW()
);
-- 6. Transfer Mechanisms (Uebermittlungsmechanismen)
CREATE TABLE IF NOT EXISTS vvt_lib_transfer_mechanisms (
id VARCHAR(50) PRIMARY KEY,
label_de VARCHAR(300) NOT NULL,
description_de TEXT,
article VARCHAR(50),
requires_tia BOOLEAN DEFAULT FALSE,
sort_order INTEGER DEFAULT 0,
created_at TIMESTAMPTZ DEFAULT NOW()
);
-- 7. Purposes (Verarbeitungszwecke)
CREATE TABLE IF NOT EXISTS vvt_lib_purposes (
id VARCHAR(50) PRIMARY KEY,
label_de VARCHAR(300) NOT NULL,
description_de TEXT,
typical_legal_basis VARCHAR(50),
typical_for JSONB DEFAULT '[]'::jsonb,
sort_order INTEGER DEFAULT 0,
created_at TIMESTAMPTZ DEFAULT NOW()
);
-- 8. TOMs (Technisch-Organisatorische Massnahmen)
CREATE TABLE IF NOT EXISTS vvt_lib_toms (
id VARCHAR(50) PRIMARY KEY,
category VARCHAR(30) NOT NULL CHECK (category IN ('accessControl', 'confidentiality', 'integrity', 'availability', 'separation')),
label_de VARCHAR(300) NOT NULL,
description_de TEXT,
art32_reference VARCHAR(100),
sort_order INTEGER DEFAULT 0,
created_at TIMESTAMPTZ DEFAULT NOW()
);
COMMIT;

View File

@@ -0,0 +1,200 @@
-- Migration 065: VVT Library Seed Data (~150 entries)
-- All content self-authored, MIT-compatible.
BEGIN;
-- =============================================================================
-- Data Subjects (15)
-- =============================================================================
INSERT INTO vvt_lib_data_subjects (id, label_de, description_de, art9_relevant, typical_for, sort_order) VALUES
('EMPLOYEES', 'Beschaeftigte', 'Aktuelle Mitarbeiterinnen und Mitarbeiter', FALSE, '["hr","it_operations"]', 1),
('APPLICANTS', 'Bewerber', 'Stellenbewerberinnen und -bewerber', FALSE, '["hr"]', 2),
('CUSTOMERS', 'Kunden', 'Aktive Kundinnen und Kunden', FALSE, '["sales_crm","support","finance"]', 3),
('PROSPECTIVE_CUSTOMERS', 'Interessenten', 'Potenzielle Kundinnen und Kunden', FALSE, '["marketing","sales_crm"]', 4),
('SUPPLIERS', 'Lieferanten', 'Geschaeftspartner als Lieferanten', FALSE, '["finance"]', 5),
('BUSINESS_PARTNERS', 'Geschaeftspartner', 'Kooperationspartner, Berater, Dienstleister', FALSE, '["management","finance"]', 6),
('VISITORS', 'Besucher', 'Betriebsbesucher und Gaeste', FALSE, '["management"]', 7),
('WEBSITE_USERS', 'Website-Nutzer', 'Besucher der Unternehmenswebsite', FALSE, '["marketing","it_operations"]', 8),
('APP_USERS', 'App-Nutzer', 'Nutzer mobiler Anwendungen', FALSE, '["product_engineering"]', 9),
('NEWSLETTER_SUBSCRIBERS', 'Newsletter-Abonnenten', 'Empfaenger von Newslettern', FALSE, '["marketing"]', 10),
('MEMBERS', 'Mitglieder', 'Vereins- oder Verbandsmitglieder', FALSE, '["management"]', 11),
('PATIENTS', 'Patienten', 'Patientinnen und Patienten', TRUE, '["other"]', 12),
('STUDENTS', 'Schueler/Studierende', 'Lernende in Bildungseinrichtungen', FALSE, '["other"]', 13),
('MINORS', 'Minderjaehrige', 'Personen unter 16 Jahren (Art. 8 DSGVO)', FALSE, '["other"]', 14),
('OTHER', 'Sonstige', 'Andere Betroffenenkategorien', FALSE, '[]', 15)
ON CONFLICT (id) DO NOTHING;
-- =============================================================================
-- Data Categories — Parent categories (9)
-- =============================================================================
INSERT INTO vvt_lib_data_categories (id, parent_id, label_de, description_de, is_art9, is_art10, risk_weight, sort_order) VALUES
('IDENTIFICATION', NULL, 'Identifikationsdaten', 'Daten zur Identifizierung natuerlicher Personen', FALSE, FALSE, 2, 1),
('CONTACT_DATA', NULL, 'Kontaktdaten', 'Kommunikationsdaten und Adressen', FALSE, FALSE, 1, 2),
('FINANCIAL', NULL, 'Finanzdaten', 'Bank-, Gehalts- und Zahlungsdaten', FALSE, FALSE, 3, 3),
('EMPLOYMENT', NULL, 'Beschaeftigungsdaten', 'Arbeitsverhaeltnis und Qualifikation', FALSE, FALSE, 2, 4),
('DIGITAL_IDENTITY', NULL, 'Digitale Identitaet', 'Online-Kennungen und Zugangsdaten', FALSE, FALSE, 2, 5),
('COMMUNICATION', NULL, 'Kommunikationsdaten', 'Nachrichten und Vertragsdaten', FALSE, FALSE, 2, 6),
('MEDIA', NULL, 'Medien- und Standortdaten', 'Bild, Video, Standort', FALSE, FALSE, 3, 7),
('ART9_SPECIAL', NULL, 'Besondere Kategorien (Art. 9)', 'Besonders schuetzenswerte Daten', TRUE, FALSE, 5, 8),
('ART10', NULL, 'Strafrechtliche Daten (Art. 10)', 'Daten ueber strafrechtliche Verurteilungen', FALSE, TRUE, 5, 9)
ON CONFLICT (id) DO NOTHING;
-- =============================================================================
-- Data Categories — Child categories (26)
-- =============================================================================
INSERT INTO vvt_lib_data_categories (id, parent_id, label_de, description_de, is_art9, is_art10, risk_weight, default_retention_rule, default_legal_basis, sort_order) VALUES
('NAME', 'IDENTIFICATION', 'Name', 'Vor- und Nachname, Geburtsname', FALSE, FALSE, 1, NULL, NULL, 10),
('DOB', 'IDENTIFICATION', 'Geburtsdatum', 'Geburtstag und -ort', FALSE, FALSE, 2, NULL, NULL, 11),
('ADDRESS', 'CONTACT_DATA', 'Anschrift', 'Wohn- und Postadresse', FALSE, FALSE, 1, NULL, NULL, 20),
('CONTACT', 'CONTACT_DATA', 'Kontaktinformationen', 'Telefon, E-Mail, Fax', FALSE, FALSE, 1, NULL, NULL, 21),
('ID_NUMBER', 'IDENTIFICATION', 'Ausweisnummer', 'Personalausweis-, Reisepassnummer', FALSE, FALSE, 3, NULL, NULL, 12),
('SOCIAL_SECURITY', 'IDENTIFICATION', 'Sozialversicherungsnummer', 'SV-Nummer', FALSE, FALSE, 4, 'BDSG_35_DELETE', 'ART6_1C', 13),
('TAX_ID', 'FINANCIAL', 'Steuer-ID', 'Steueridentifikationsnummer', FALSE, FALSE, 3, 'AO_147_10Y', 'ART6_1C', 30),
('BANK_ACCOUNT', 'FINANCIAL', 'Bankverbindung', 'IBAN, BIC, Kontonummer', FALSE, FALSE, 3, 'HGB_257_10Y', 'ART6_1B', 31),
('PAYMENT_DATA', 'FINANCIAL', 'Zahlungsdaten', 'Kreditkartendaten, Zahlungshistorie', FALSE, FALSE, 4, 'HGB_257_10Y', 'ART6_1B', 32),
('SALARY_DATA', 'FINANCIAL', 'Gehaltsdaten', 'Brutto/Netto, Zulagen, Abzuege', FALSE, FALSE, 4, 'AO_147_10Y', 'BDSG_26', 33),
('EMPLOYMENT_DATA', 'EMPLOYMENT', 'Arbeitsvertragsdaten', 'Vertragsdetails, Position, Abteilung', FALSE, FALSE, 2, 'HGB_257_10Y', 'BDSG_26', 40),
('EDUCATION_DATA', 'EMPLOYMENT', 'Ausbildungsdaten', 'Zeugnisse, Qualifikationen, Zertifikate', FALSE, FALSE, 2, 'AGG_15_6M', 'BDSG_26', 41),
('IP_ADDRESS', 'DIGITAL_IDENTITY', 'IP-Adresse', 'IPv4/IPv6 Adressen', FALSE, FALSE, 2, 'CUSTOM_90D', 'ART6_1F', 50),
('DEVICE_ID', 'DIGITAL_IDENTITY', 'Geraete-ID', 'Browser-Fingerprint, Device-ID', FALSE, FALSE, 2, 'CUSTOM_14M', 'ART6_1A', 51),
('LOGIN_DATA', 'DIGITAL_IDENTITY', 'Zugangsdaten', 'Benutzername, Passwort-Hash', FALSE, FALSE, 3, NULL, 'ART6_1B', 52),
('USAGE_DATA', 'DIGITAL_IDENTITY', 'Nutzungsdaten', 'Klickverhalten, Seitenaufrufe, Sessions', FALSE, FALSE, 2, 'CUSTOM_14M', 'ART6_1A', 53),
('COMMUNICATION_DATA', 'COMMUNICATION', 'Korrespondenz', 'E-Mails, Chat-Nachrichten, Briefe', FALSE, FALSE, 2, 'BGB_195_3Y', NULL, 60),
('CONTRACT_DATA', 'COMMUNICATION', 'Vertragsdaten', 'Vertragsdetails, Bestellungen', FALSE, FALSE, 2, 'HGB_257_10Y', 'ART6_1B', 61),
('PHOTO_VIDEO', 'MEDIA', 'Bild-/Videodaten', 'Fotos, Videos von Personen', FALSE, FALSE, 3, 'CONSENT_REVOKE', 'ART6_1A', 70),
('LOCATION_DATA', 'MEDIA', 'Standortdaten', 'GPS-Koordinaten, Aufenthaltsorte', FALSE, FALSE, 3, 'CUSTOM_90D', 'ART6_1A', 71),
('HEALTH_DATA', 'ART9_SPECIAL', 'Gesundheitsdaten', 'Krankheitsdaten, Atteste, Behinderung', TRUE, FALSE, 5, 'BDSG_35_DELETE', 'ART9_2H', 80),
('GENETIC_DATA', 'ART9_SPECIAL', 'Genetische Daten', 'DNA-Analysen, genetische Merkmale', TRUE, FALSE, 5, 'BDSG_35_DELETE', 'ART9_2A', 81),
('BIOMETRIC_DATA', 'ART9_SPECIAL', 'Biometrische Daten', 'Fingerabdruck, Gesichtserkennung', TRUE, FALSE, 5, 'BDSG_35_DELETE', 'ART9_2A', 82),
('RACIAL_ETHNIC', 'ART9_SPECIAL', 'Rassische/ethnische Herkunft', 'Ethnische Zugehoerigkeit', TRUE, FALSE, 5, NULL, 'ART9_2A', 83),
('POLITICAL_OPINIONS', 'ART9_SPECIAL', 'Politische Meinungen', 'Parteizugehoerigkeit, politische Haltung', TRUE, FALSE, 5, NULL, 'ART9_2A', 84),
('RELIGIOUS_BELIEFS', 'ART9_SPECIAL', 'Religioese Ueberzeugungen', 'Konfession, religioese Praktiken', TRUE, FALSE, 5, NULL, 'ART9_2A', 85),
('TRADE_UNION', 'ART9_SPECIAL', 'Gewerkschaftszugehoerigkeit', 'Mitgliedschaft in Gewerkschaften', TRUE, FALSE, 5, NULL, 'ART9_2A', 86),
('SEX_LIFE', 'ART9_SPECIAL', 'Sexualleben/Orientierung', 'Sexuelle Orientierung', TRUE, FALSE, 5, NULL, 'ART9_2A', 87),
('CRIMINAL_DATA', 'ART10', 'Strafrechtliche Daten', 'Verurteilungen, Straftaten, Fuehrungszeugnis', FALSE, TRUE, 5, 'BDSG_35_DELETE', 'BDSG_24', 90)
ON CONFLICT (id) DO NOTHING;
-- =============================================================================
-- Legal Bases (12)
-- =============================================================================
INSERT INTO vvt_lib_legal_bases (id, article, type, label_de, description_de, is_art9, typical_national_law, sort_order) VALUES
('ART6_1A', 'Art. 6 Abs. 1 lit. a', 'CONSENT', 'Einwilligung', 'Die betroffene Person hat ihre Einwilligung gegeben', FALSE, NULL, 1),
('ART6_1B', 'Art. 6 Abs. 1 lit. b', 'CONTRACT', 'Vertragserfullung', 'Erforderlich fuer die Erfuellung eines Vertrags', FALSE, NULL, 2),
('ART6_1C', 'Art. 6 Abs. 1 lit. c', 'LEGAL_OBLIGATION', 'Rechtliche Verpflichtung', 'Erforderlich zur Erfuellung einer rechtlichen Verpflichtung', FALSE, NULL, 3),
('ART6_1D', 'Art. 6 Abs. 1 lit. d', 'VITAL_INTEREST', 'Lebenswichtige Interessen', 'Schutz lebenswichtiger Interessen', FALSE, NULL, 4),
('ART6_1E', 'Art. 6 Abs. 1 lit. e', 'PUBLIC_TASK', 'Oeffentliches Interesse', 'Wahrnehmung einer Aufgabe im oeffentlichen Interesse', FALSE, NULL, 5),
('ART6_1F', 'Art. 6 Abs. 1 lit. f', 'LEGITIMATE_INTEREST', 'Berechtigtes Interesse', 'Wahrung berechtigter Interessen des Verantwortlichen', FALSE, NULL, 6),
('ART9_2A', 'Art. 9 Abs. 2 lit. a', 'ART9', 'Ausdrueckliche Einwilligung (Art. 9)', 'Ausdrueckliche Einwilligung fuer besondere Kategorien', TRUE, NULL, 7),
('ART9_2B', 'Art. 9 Abs. 2 lit. b', 'ART9', 'Arbeitsrecht (Art. 9)', 'Erforderlich im Arbeitsrecht', TRUE, 'BDSG § 26', 8),
('ART9_2H', 'Art. 9 Abs. 2 lit. h', 'ART9', 'Gesundheitsvorsorge (Art. 9)', 'Gesundheitsvorsorge oder Arbeitsmedizin', TRUE, NULL, 9),
('BDSG_26', '§ 26 BDSG', 'NATIONAL', 'Beschaeftigtenverhaeltnis', 'Datenverarbeitung fuer Zwecke des Beschaeftigungsverhaeltnisses', FALSE, 'BDSG § 26', 10),
('BDSG_24', '§ 24 BDSG', 'NATIONAL', 'Strafrechtliche Daten', 'Verarbeitung strafrechtlicher Daten (Art. 10 DSGVO)', FALSE, 'BDSG § 24', 11),
('UWG_7', '§ 7 UWG', 'NATIONAL', 'Werbung mit Einwilligung', 'Werbliche Ansprache nach UWG', FALSE, 'UWG § 7', 12)
ON CONFLICT (id) DO NOTHING;
-- =============================================================================
-- Retention Rules (12)
-- =============================================================================
INSERT INTO vvt_lib_retention_rules (id, label_de, description_de, legal_basis, duration, duration_unit, start_event, deletion_procedure, sort_order) VALUES
('HGB_257_10Y', '10 Jahre (HGB § 257)', 'Handelsrechtliche Aufbewahrungspflicht fuer Handelsbuecher, Jahresabschluesse, Buchungsbelege', 'HGB § 257', 10, 'YEARS', 'Ende des Kalenderjahres', 'Vernichtung nach Ablauf der Aufbewahrungsfrist', 1),
('AO_147_10Y', '10 Jahre (AO § 147)', 'Steuerrechtliche Aufbewahrungspflicht fuer Buchungsbelege', 'AO § 147', 10, 'YEARS', 'Ende des Kalenderjahres', 'Vernichtung nach Ablauf der Aufbewahrungsfrist', 2),
('AO_147_6Y', '6 Jahre (AO § 147)', 'Steuerrechtliche Aufbewahrungspflicht fuer Geschaeftsbriefe', 'AO § 147', 6, 'YEARS', 'Ende des Kalenderjahres', 'Vernichtung nach Ablauf der Aufbewahrungsfrist', 3),
('AGG_15_6M', '6 Monate (AGG § 15)', 'Frist fuer Schadensersatzansprueche nach AGG', 'AGG § 15', 6, 'MONTHS', 'Ablehnung / Ende des Verfahrens', 'Loeschung personenbezogener Bewerbungsdaten', 4),
('ARBZG_16_2Y', '2 Jahre (ArbZG § 16)', 'Aufzeichnungspflicht der Arbeitszeiten', 'ArbZG § 16', 2, 'YEARS', 'Ende des Aufzeichnungszeitraums', 'Vernichtung der Arbeitszeitaufzeichnungen', 5),
('BGB_195_3Y', '3 Jahre (BGB § 195)', 'Regelverjaehrungsfrist fuer vertragliche Ansprueche', 'BGB § 195', 3, 'YEARS', 'Ende des Jahres der Anspruchsentstehung', 'Loeschung nach Ablauf der Verjaehrungsfrist', 6),
('CONSENT_REVOKE', 'Bis Widerruf', 'Speicherung bis zum Widerruf der Einwilligung', 'Art. 7 Abs. 3 DSGVO', 0, 'DAYS', 'Widerruf der Einwilligung', 'Unverzuegliche Loeschung nach Widerruf', 7),
('PURPOSE_END', 'Bis Zweckerfuellung', 'Speicherung bis der Verarbeitungszweck erreicht ist', 'Art. 5 Abs. 1 lit. e DSGVO', 0, 'DAYS', 'Zweckerfuellung', 'Loeschung nach Zweckerfuellung', 8),
('BDSG_35_DELETE', 'Unverzuegliche Loeschung', 'Loeschung sobald Speicherung nicht mehr erforderlich', 'BDSG § 35', 0, 'DAYS', 'Wegfall der Erforderlichkeit', 'Unverzuegliche Loeschung', 9),
('CUSTOM_90D', '90 Tage', 'Benutzerdefinierte Aufbewahrungsfrist von 90 Tagen', NULL, 90, 'DAYS', 'Erstellung des Datensatzes', 'Automatische Loeschung nach 90 Tagen', 10),
('CUSTOM_14M', '14 Monate', 'Benutzerdefinierte Aufbewahrungsfrist von 14 Monaten (z.B. Analytics)', NULL, 14, 'MONTHS', 'Erstellung des Datensatzes', 'Automatische Loeschung nach 14 Monaten', 11),
('CUSTOM_30D', '30 Tage', 'Benutzerdefinierte Aufbewahrungsfrist von 30 Tagen', NULL, 30, 'DAYS', 'Erstellung des Datensatzes', 'Automatische Loeschung nach 30 Tagen', 12)
ON CONFLICT (id) DO NOTHING;
-- =============================================================================
-- Recipients (15)
-- =============================================================================
INSERT INTO vvt_lib_recipients (id, type, label_de, description_de, is_third_country, country, sort_order) VALUES
('INTERNAL_HR', 'INTERNAL', 'Personalabteilung', 'Interne HR-Abteilung', FALSE, 'DE', 1),
('INTERNAL_FINANCE', 'INTERNAL', 'Finanzabteilung', 'Interne Buchhaltung und Finanzen', FALSE, 'DE', 2),
('INTERNAL_IT', 'INTERNAL', 'IT-Abteilung', 'Interne IT-Administration', FALSE, 'DE', 3),
('INTERNAL_MANAGEMENT', 'INTERNAL', 'Geschaeftsfuehrung', 'Geschaeftsfuehrung und Vorstand', FALSE, 'DE', 4),
('INTERNAL_MARKETING', 'INTERNAL', 'Marketingabteilung', 'Internes Marketing-Team', FALSE, 'DE', 5),
('INTERNAL_SUPPORT', 'INTERNAL', 'Kundenservice', 'Interner Support und Service', FALSE, 'DE', 6),
('PROCESSOR_PAYROLL', 'PROCESSOR', 'Lohnabrechnungsdienstleister', 'Externer Gehaltsabrechnungs-Dienstleister', FALSE, 'DE', 7),
('PROCESSOR_HOSTING', 'PROCESSOR', 'Hosting-Provider', 'Cloud- oder Server-Hosting-Anbieter', FALSE, NULL, 8),
('PROCESSOR_ANALYTICS', 'PROCESSOR', 'Analytics-Anbieter', 'Web-Analytics und Tracking-Dienstleister', FALSE, NULL, 9),
('PROCESSOR_EMAIL', 'PROCESSOR', 'E-Mail-Dienstleister', 'Newsletter- und E-Mail-Versand-Anbieter', FALSE, NULL, 10),
('PROCESSOR_HELPDESK', 'PROCESSOR', 'Helpdesk-Anbieter', 'Ticketsystem- und Support-Plattform', FALSE, NULL, 11),
('AUTHORITY_FINANZAMT', 'AUTHORITY', 'Finanzamt', 'Zustaendiges Finanzamt', FALSE, 'DE', 12),
('AUTHORITY_SOZIALVERSICHERUNG', 'AUTHORITY', 'Sozialversicherungstraeger', 'Renten-, Kranken-, Arbeitslosen-, Pflegeversicherung', FALSE, 'DE', 13),
('AUTHORITY_KRANKENKASSE', 'AUTHORITY', 'Krankenkasse', 'Gesetzliche oder private Krankenkasse', FALSE, 'DE', 14),
('AUTHORITY_DATENSCHUTZ', 'AUTHORITY', 'Datenschutzbehoerde', 'Zustaendige Datenschutz-Aufsichtsbehoerde', FALSE, 'DE', 15)
ON CONFLICT (id) DO NOTHING;
-- =============================================================================
-- Transfer Mechanisms (8)
-- =============================================================================
INSERT INTO vvt_lib_transfer_mechanisms (id, label_de, description_de, article, requires_tia, sort_order) VALUES
('ADEQUACY_DECISION', 'Angemessenheitsbeschluss', 'EU-Angemessenheitsbeschluss gemaess Art. 45 DSGVO', 'Art. 45 DSGVO', FALSE, 1),
('SCC_CONTROLLER', 'Standardvertragsklauseln (C2C)', 'Standardvertragsklauseln Controller-zu-Controller', 'Art. 46 Abs. 2 lit. c DSGVO', TRUE, 2),
('SCC_PROCESSOR', 'Standardvertragsklauseln (C2P)', 'Standardvertragsklauseln Controller-zu-Processor', 'Art. 46 Abs. 2 lit. c DSGVO', TRUE, 3),
('BCR', 'Binding Corporate Rules', 'Verbindliche interne Datenschutzvorschriften', 'Art. 47 DSGVO', FALSE, 4),
('CONSENT_49A', 'Einwilligung (Art. 49)', 'Ausdrueckliche Einwilligung der betroffenen Person', 'Art. 49 Abs. 1 lit. a DSGVO', FALSE, 5),
('DEROGATION_49', 'Ausnahme (Art. 49)', 'Ausnahme fuer bestimmte Faelle gemaess Art. 49', 'Art. 49 DSGVO', FALSE, 6),
('DPF', 'EU-US Data Privacy Framework', 'Zertifizierung unter dem EU-US Data Privacy Framework', 'Art. 45 DSGVO (DPF)', FALSE, 7),
('TIA', 'Transfer Impact Assessment', 'Einzelfallbezogene Risikobewertung fuer Drittlandtransfers', 'Art. 46 DSGVO + Schrems II', TRUE, 8)
ON CONFLICT (id) DO NOTHING;
-- =============================================================================
-- Purposes (20)
-- =============================================================================
INSERT INTO vvt_lib_purposes (id, label_de, description_de, typical_legal_basis, typical_for, sort_order) VALUES
('EMPLOYMENT_ADMIN', 'Personalverwaltung', 'Verwaltung des Beschaeftigungsverhaeltnisses', 'BDSG_26', '["hr"]', 1),
('PAYROLL', 'Gehaltsabrechnung', 'Durchfuehrung der Lohn- und Gehaltsabrechnung', 'BDSG_26', '["hr","finance"]', 2),
('RECRUITING', 'Bewerbermanagement', 'Durchfuehrung von Bewerbungsverfahren', 'BDSG_26', '["hr"]', 3),
('TIME_TRACKING', 'Zeiterfassung', 'Erfassung und Verwaltung von Arbeitszeiten', 'ART6_1C', '["hr"]', 4),
('ACCOUNTING', 'Buchhaltung', 'Fuehrung der Handelsbuecher und Finanzberichterstattung', 'ART6_1C', '["finance"]', 5),
('INVOICING', 'Rechnungsstellung', 'Erstellung und Verwaltung von Rechnungen', 'ART6_1B', '["finance"]', 6),
('CRM', 'Kundenbeziehungsmanagement', 'Verwaltung und Pflege von Kundenbeziehungen', 'ART6_1B', '["sales_crm"]', 7),
('DIRECT_MARKETING', 'Direktmarketing', 'Newsletter-Versand und Werbemassnahmen', 'ART6_1A', '["marketing"]', 8),
('WEBSITE_ANALYTICS', 'Web-Analyse', 'Analyse des Nutzerverhaltens auf der Website', 'ART6_1A', '["marketing","it_operations"]', 9),
('CUSTOMER_SUPPORT', 'Kundenbetreuung', 'Bearbeitung von Kundenanfragen und Support-Tickets', 'ART6_1B', '["support"]', 10),
('IT_ADMIN', 'IT-Administration', 'Verwaltung der IT-Infrastruktur und Benutzerkonten', 'ART6_1F', '["it_operations"]', 11),
('BACKUP_RECOVERY', 'Datensicherung', 'Backup-Erstellung und Wiederherstellung', 'ART6_1F', '["it_operations"]', 12),
('SECURITY_MONITORING', 'Sicherheitsueberwachung', 'Log-Analyse und Intrusion Detection', 'ART6_1F', '["it_operations"]', 13),
('IAM', 'Identitaets- und Zugriffsmanagement', 'Verwaltung von Benutzeridentitaeten und Berechtigungen', 'ART6_1F', '["it_operations"]', 14),
('VIDEO_CONFERENCING', 'Videokonferenz', 'Durchfuehrung von Online-Meetings und Videokonferenzen', 'ART6_1B', '["other"]', 15),
('VISITOR_MANAGEMENT', 'Besucherverwaltung', 'Erfassung und Verwaltung von Betriebsbesuchern', 'ART6_1F', '["management"]', 16),
('PAYMENT_PROCESSING', 'Zahlungsabwicklung', 'Verarbeitung und Abwicklung von Zahlungen', 'ART6_1B', '["finance"]', 17),
('SOCIAL_MEDIA', 'Social-Media-Marketing', 'Betrieb von Social-Media-Praesenzen', 'ART6_1A', '["marketing"]', 18),
('SALES_REPORTING', 'Vertriebssteuerung', 'Vertriebsanalysen und Berichterstattung', 'ART6_1F', '["sales_crm"]', 19),
('COMPLIANCE_DOCS', 'Compliance-Dokumentation', 'Erstellung und Pflege von Compliance-Dokumenten', 'ART6_1C', '["legal","management"]', 20)
ON CONFLICT (id) DO NOTHING;
-- =============================================================================
-- TOMs (20)
-- =============================================================================
INSERT INTO vvt_lib_toms (id, category, label_de, description_de, art32_reference, sort_order) VALUES
('AC_RBAC', 'accessControl', 'Rollenbasierte Zugriffskontrolle (RBAC)', 'Zugriff nur nach Rolle und Berechtigung', 'Art. 32 Abs. 1 lit. b', 1),
('AC_MFA', 'accessControl', 'Multi-Faktor-Authentifizierung', 'Zwei- oder mehrstufige Anmeldung', 'Art. 32 Abs. 1 lit. b', 2),
('AC_NEED_TO_KNOW', 'accessControl', 'Need-to-Know-Prinzip', 'Zugriff nur auf fuer die Aufgabe erforderliche Daten', 'Art. 32 Abs. 1 lit. b', 3),
('AC_PAM', 'accessControl', 'Privileged Access Management', 'Verwaltung und Ueberwachung privilegierter Zugaenge', 'Art. 32 Abs. 1 lit. b', 4),
('CONF_ENCRYPTION_REST', 'confidentiality', 'Verschluesselung ruhender Daten', 'AES-256 Verschluesselung fuer gespeicherte Daten', 'Art. 32 Abs. 1 lit. a', 5),
('CONF_ENCRYPTION_TRANSIT', 'confidentiality', 'Transportverschluesselung', 'TLS 1.3 fuer alle Datenuebertragungen', 'Art. 32 Abs. 1 lit. a', 6),
('CONF_PSEUDONYMIZATION', 'confidentiality', 'Pseudonymisierung', 'Verarbeitung ohne direkten Personenbezug', 'Art. 32 Abs. 1 lit. a', 7),
('CONF_NDA', 'confidentiality', 'Vertraulichkeitsvereinbarungen', 'NDAs fuer Mitarbeiter und Auftragnehmer', 'Art. 32 Abs. 1 lit. b', 8),
('INT_AUDIT_LOG', 'integrity', 'Audit-Logging', 'Lueckenlose Protokollierung aller Datenzugriffe', 'Art. 32 Abs. 1 lit. b', 9),
('INT_FOUR_EYES', 'integrity', 'Vier-Augen-Prinzip', 'Kritische Aenderungen nur mit Freigabe durch zweite Person', 'Art. 32 Abs. 1 lit. b', 10),
('INT_CHECKSUMS', 'integrity', 'Pruefsummen und Hashing', 'Integritaetspruefung durch kryptographische Hashes', 'Art. 32 Abs. 1 lit. b', 11),
('INT_CHANGE_MGMT', 'integrity', 'Change Management', 'Dokumentierter Aenderungsprozess fuer IT-Systeme', 'Art. 32 Abs. 1 lit. b', 12),
('AVAIL_BACKUP', 'availability', 'Regelmaessige Backups', 'Taegliche und woechentliche Datensicherungen', 'Art. 32 Abs. 1 lit. c', 13),
('AVAIL_REDUNDANCY', 'availability', 'Redundante Systeme', 'Hochverfuegbarkeit durch Systemredundanz', 'Art. 32 Abs. 1 lit. c', 14),
('AVAIL_321_RULE', 'availability', '3-2-1 Backup-Regel', 'Drei Kopien, zwei Medien, ein externer Standort', 'Art. 32 Abs. 1 lit. c', 15),
('AVAIL_MONITORING', 'availability', 'System-Monitoring', 'Kontinuierliche Ueberwachung der Systemverfuegbarkeit', 'Art. 32 Abs. 1 lit. c', 16),
('SEP_TENANT_ISOLATION', 'separation', 'Mandantentrennung', 'Logische Trennung der Daten verschiedener Mandanten', 'Art. 32 Abs. 1 lit. b', 17),
('SEP_NETWORK_SEG', 'separation', 'Netzwerksegmentierung', 'Trennung von Netzwerkbereichen (VLANs, Firewalls)', 'Art. 32 Abs. 1 lit. b', 18),
('SEP_DATA_SEPARATION', 'separation', 'Datentrennung', 'Separate Datenbanken oder Schemas pro Zweck', 'Art. 32 Abs. 1 lit. b', 19),
('SEP_ENV_SEPARATION', 'separation', 'Umgebungstrennung', 'Getrennte Entwicklungs-, Test- und Produktionsumgebungen', 'Art. 32 Abs. 1 lit. b', 20)
ON CONFLICT (id) DO NOTHING;
COMMIT;

View File

@@ -0,0 +1,54 @@
-- Migration 066: VVT Process Templates + Activity extensions
-- Template table + new ref columns on compliance_vvt_activities
BEGIN;
-- =============================================================================
-- Process Templates
-- =============================================================================
CREATE TABLE IF NOT EXISTS vvt_process_templates (
id VARCHAR(80) PRIMARY KEY,
name VARCHAR(300) NOT NULL,
description TEXT,
business_function VARCHAR(50),
purpose_refs JSONB DEFAULT '[]'::jsonb,
legal_basis_refs JSONB DEFAULT '[]'::jsonb,
data_subject_refs JSONB DEFAULT '[]'::jsonb,
data_category_refs JSONB DEFAULT '[]'::jsonb,
recipient_refs JSONB DEFAULT '[]'::jsonb,
tom_refs JSONB DEFAULT '[]'::jsonb,
transfer_mechanism_refs JSONB DEFAULT '[]'::jsonb,
retention_rule_ref VARCHAR(50),
typical_systems JSONB DEFAULT '[]'::jsonb,
protection_level VARCHAR(10) DEFAULT 'MEDIUM',
dpia_required BOOLEAN DEFAULT FALSE,
risk_score INTEGER,
tags JSONB DEFAULT '[]'::jsonb,
is_system BOOLEAN DEFAULT TRUE,
tenant_id UUID,
sort_order INTEGER DEFAULT 0,
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE INDEX IF NOT EXISTS idx_vvt_process_templates_bf ON vvt_process_templates(business_function);
CREATE INDEX IF NOT EXISTS idx_vvt_process_templates_system ON vvt_process_templates(is_system);
-- =============================================================================
-- New columns on compliance_vvt_activities (all DEFAULT NULL for backward compat)
-- =============================================================================
ALTER TABLE compliance_vvt_activities ADD COLUMN IF NOT EXISTS purpose_refs JSONB DEFAULT NULL;
ALTER TABLE compliance_vvt_activities ADD COLUMN IF NOT EXISTS legal_basis_refs JSONB DEFAULT NULL;
ALTER TABLE compliance_vvt_activities ADD COLUMN IF NOT EXISTS data_subject_refs JSONB DEFAULT NULL;
ALTER TABLE compliance_vvt_activities ADD COLUMN IF NOT EXISTS data_category_refs JSONB DEFAULT NULL;
ALTER TABLE compliance_vvt_activities ADD COLUMN IF NOT EXISTS recipient_refs JSONB DEFAULT NULL;
ALTER TABLE compliance_vvt_activities ADD COLUMN IF NOT EXISTS retention_rule_ref VARCHAR(50) DEFAULT NULL;
ALTER TABLE compliance_vvt_activities ADD COLUMN IF NOT EXISTS transfer_mechanism_refs JSONB DEFAULT NULL;
ALTER TABLE compliance_vvt_activities ADD COLUMN IF NOT EXISTS tom_refs JSONB DEFAULT NULL;
ALTER TABLE compliance_vvt_activities ADD COLUMN IF NOT EXISTS linked_loeschfristen_ids JSONB DEFAULT NULL;
ALTER TABLE compliance_vvt_activities ADD COLUMN IF NOT EXISTS linked_tom_measure_ids JSONB DEFAULT NULL;
ALTER TABLE compliance_vvt_activities ADD COLUMN IF NOT EXISTS source_template_id VARCHAR(80) DEFAULT NULL;
ALTER TABLE compliance_vvt_activities ADD COLUMN IF NOT EXISTS risk_score INTEGER DEFAULT NULL;
ALTER TABLE compliance_vvt_activities ADD COLUMN IF NOT EXISTS art30_completeness JSONB DEFAULT NULL;
COMMIT;

View File

@@ -0,0 +1,305 @@
-- Migration 067: VVT Process Templates Seed — 18 templates from vvt-baseline-catalog
-- All content self-authored, MIT-compatible.
BEGIN;
INSERT INTO vvt_process_templates (id, name, description, business_function, purpose_refs, legal_basis_refs, data_subject_refs, data_category_refs, recipient_refs, tom_refs, retention_rule_ref, typical_systems, protection_level, dpia_required, risk_score, tags, sort_order) VALUES
-- HR Templates
('hr-mitarbeiterverwaltung',
'Mitarbeiterverwaltung',
'Verwaltung des Beschaeftigungsverhaeltnisses inkl. Personalakte, Urlaub, Krankmeldungen',
'hr',
'["EMPLOYMENT_ADMIN", "PAYROLL"]',
'["BDSG_26", "ART6_1B"]',
'["EMPLOYEES"]',
'["NAME", "DOB", "ADDRESS", "CONTACT", "SOCIAL_SECURITY", "BANK_ACCOUNT", "EMPLOYMENT_DATA", "HEALTH_DATA"]',
'["INTERNAL_HR", "INTERNAL_FINANCE", "PROCESSOR_PAYROLL", "AUTHORITY_SOZIALVERSICHERUNG", "AUTHORITY_KRANKENKASSE"]',
'["AC_RBAC", "AC_NEED_TO_KNOW", "CONF_ENCRYPTION_REST", "CONF_ENCRYPTION_TRANSIT", "INT_AUDIT_LOG", "SEP_TENANT_ISOLATION"]',
'HGB_257_10Y',
'["HR-Software", "Personalakte (digital)"]',
'HIGH', TRUE, 3,
'["personal", "pflicht"]',
1),
('hr-gehaltsabrechnung',
'Gehaltsabrechnung',
'Monatliche Lohn- und Gehaltsabrechnung inkl. Steuer- und Sozialversicherungsmeldungen',
'hr',
'["PAYROLL"]',
'["BDSG_26", "ART6_1C"]',
'["EMPLOYEES"]',
'["NAME", "ADDRESS", "SOCIAL_SECURITY", "TAX_ID", "BANK_ACCOUNT", "SALARY_DATA"]',
'["INTERNAL_HR", "INTERNAL_FINANCE", "PROCESSOR_PAYROLL", "AUTHORITY_FINANZAMT", "AUTHORITY_SOZIALVERSICHERUNG"]',
'["AC_RBAC", "AC_NEED_TO_KNOW", "CONF_ENCRYPTION_REST", "CONF_ENCRYPTION_TRANSIT", "INT_AUDIT_LOG", "INT_FOUR_EYES"]',
'AO_147_10Y',
'["Lohnabrechnungssoftware", "DATEV"]',
'HIGH', FALSE, 3,
'["personal", "finanzen", "pflicht"]',
2),
('hr-bewerbermanagement',
'Bewerbermanagement',
'Durchfuehrung von Bewerbungsverfahren vom Eingang bis zur Zu-/Absage',
'hr',
'["RECRUITING"]',
'["BDSG_26", "ART6_1B"]',
'["APPLICANTS"]',
'["NAME", "DOB", "ADDRESS", "CONTACT", "EDUCATION_DATA", "PHOTO_VIDEO"]',
'["INTERNAL_HR", "INTERNAL_MANAGEMENT"]',
'["AC_RBAC", "AC_NEED_TO_KNOW", "CONF_ENCRYPTION_REST", "CONF_NDA"]',
'AGG_15_6M',
'["Bewerbermanagement-Software", "E-Mail"]',
'MEDIUM', FALSE, 2,
'["personal", "recruiting"]',
3),
('hr-zeiterfassung',
'Zeiterfassung',
'Erfassung und Verwaltung von Arbeitszeiten gemaess ArbZG',
'hr',
'["TIME_TRACKING"]',
'["ART6_1C", "BDSG_26"]',
'["EMPLOYEES"]',
'["NAME", "EMPLOYMENT_DATA"]',
'["INTERNAL_HR", "INTERNAL_MANAGEMENT"]',
'["AC_RBAC", "INT_AUDIT_LOG", "CONF_ENCRYPTION_TRANSIT"]',
'ARBZG_16_2Y',
'["Zeiterfassungssystem", "Stempeluhr"]',
'LOW', FALSE, 1,
'["personal", "pflicht"]',
4),
-- Finance Templates
('finance-buchhaltung',
'Buchhaltung',
'Fuehrung der Handelsbuecher und steuerrechtliche Dokumentation',
'finance',
'["ACCOUNTING", "INVOICING"]',
'["ART6_1C", "ART6_1B"]',
'["CUSTOMERS", "SUPPLIERS", "EMPLOYEES"]',
'["NAME", "ADDRESS", "CONTACT", "BANK_ACCOUNT", "PAYMENT_DATA", "CONTRACT_DATA", "TAX_ID"]',
'["INTERNAL_FINANCE", "AUTHORITY_FINANZAMT", "PROCESSOR_HOSTING"]',
'["AC_RBAC", "INT_AUDIT_LOG", "INT_FOUR_EYES", "CONF_ENCRYPTION_REST", "AVAIL_BACKUP"]',
'HGB_257_10Y',
'["Buchhaltungssoftware", "DATEV", "ERP-System"]',
'HIGH', FALSE, 2,
'["finanzen", "pflicht"]',
5),
('finance-zahlungsverkehr',
'Zahlungsverkehr',
'Verarbeitung und Abwicklung von ein- und ausgehenden Zahlungen',
'finance',
'["PAYMENT_PROCESSING"]',
'["ART6_1B", "ART6_1C"]',
'["CUSTOMERS", "SUPPLIERS"]',
'["NAME", "BANK_ACCOUNT", "PAYMENT_DATA", "CONTRACT_DATA"]',
'["INTERNAL_FINANCE", "PROCESSOR_HOSTING"]',
'["AC_RBAC", "AC_MFA", "CONF_ENCRYPTION_REST", "CONF_ENCRYPTION_TRANSIT", "INT_AUDIT_LOG"]',
'HGB_257_10Y',
'["Online-Banking", "Payment-Gateway"]',
'HIGH', FALSE, 3,
'["finanzen"]',
6),
-- Sales/CRM Templates
('sales-kundenverwaltung',
'Kundenverwaltung',
'Verwaltung und Pflege der Kundenbeziehungen im CRM-System',
'sales_crm',
'["CRM"]',
'["ART6_1B", "ART6_1F"]',
'["CUSTOMERS", "PROSPECTIVE_CUSTOMERS"]',
'["NAME", "ADDRESS", "CONTACT", "CONTRACT_DATA", "COMMUNICATION_DATA"]',
'["INTERNAL_MARKETING", "INTERNAL_SUPPORT", "PROCESSOR_HOSTING"]',
'["AC_RBAC", "CONF_ENCRYPTION_REST", "CONF_ENCRYPTION_TRANSIT", "INT_AUDIT_LOG", "SEP_TENANT_ISOLATION"]',
'BGB_195_3Y',
'["CRM-System", "E-Mail-Client"]',
'MEDIUM', FALSE, 2,
'["vertrieb", "kunden"]',
7),
('sales-vertriebssteuerung',
'Vertriebssteuerung',
'Vertriebsanalysen, Forecasting und Berichterstattung',
'sales_crm',
'["SALES_REPORTING"]',
'["ART6_1F"]',
'["CUSTOMERS", "PROSPECTIVE_CUSTOMERS"]',
'["NAME", "CONTACT", "CONTRACT_DATA"]',
'["INTERNAL_MANAGEMENT", "INTERNAL_MARKETING"]',
'["AC_RBAC", "AC_NEED_TO_KNOW", "CONF_PSEUDONYMIZATION"]',
'BGB_195_3Y',
'["CRM-System", "BI-Tool"]',
'LOW', FALSE, 1,
'["vertrieb", "reporting"]',
8),
-- Marketing Templates
('marketing-newsletter',
'Newsletter-Versand',
'Versand von Newslettern und Werbemails an Abonnenten',
'marketing',
'["DIRECT_MARKETING"]',
'["ART6_1A", "UWG_7"]',
'["NEWSLETTER_SUBSCRIBERS", "CUSTOMERS"]',
'["NAME", "CONTACT", "USAGE_DATA"]',
'["INTERNAL_MARKETING", "PROCESSOR_EMAIL"]',
'["AC_RBAC", "CONF_ENCRYPTION_TRANSIT", "SEP_DATA_SEPARATION"]',
'CONSENT_REVOKE',
'["Newsletter-Tool", "E-Mail-Marketing-Plattform"]',
'LOW', FALSE, 1,
'["marketing", "einwilligung"]',
9),
('marketing-website-analytics',
'Website-Analyse',
'Analyse des Nutzerverhaltens auf der Unternehmenswebsite',
'marketing',
'["WEBSITE_ANALYTICS"]',
'["ART6_1A"]',
'["WEBSITE_USERS"]',
'["IP_ADDRESS", "DEVICE_ID", "USAGE_DATA"]',
'["INTERNAL_MARKETING", "PROCESSOR_ANALYTICS"]',
'["CONF_PSEUDONYMIZATION", "CONF_ENCRYPTION_TRANSIT", "SEP_DATA_SEPARATION"]',
'CUSTOM_14M',
'["Web-Analytics-Tool", "Tag-Manager"]',
'LOW', FALSE, 1,
'["marketing", "einwilligung", "tracking"]',
10),
('marketing-social-media',
'Social-Media-Marketing',
'Betrieb und Verwaltung von Social-Media-Praesenzen',
'marketing',
'["SOCIAL_MEDIA"]',
'["ART6_1A", "ART6_1F"]',
'["WEBSITE_USERS", "CUSTOMERS"]',
'["NAME", "CONTACT", "USAGE_DATA", "PHOTO_VIDEO"]',
'["INTERNAL_MARKETING", "PROCESSOR_ANALYTICS"]',
'["AC_RBAC", "CONF_ENCRYPTION_TRANSIT"]',
'PURPOSE_END',
'["Social-Media-Plattformen", "Social-Media-Management-Tool"]',
'LOW', FALSE, 1,
'["marketing", "social-media"]',
11),
-- Support Templates
('support-ticketsystem',
'Ticketsystem / Kundenservice',
'Bearbeitung von Kundenanfragen ueber das Ticketsystem',
'support',
'["CUSTOMER_SUPPORT"]',
'["ART6_1B"]',
'["CUSTOMERS"]',
'["NAME", "CONTACT", "COMMUNICATION_DATA", "CONTRACT_DATA"]',
'["INTERNAL_SUPPORT", "PROCESSOR_HELPDESK"]',
'["AC_RBAC", "CONF_ENCRYPTION_TRANSIT", "INT_AUDIT_LOG"]',
'BGB_195_3Y',
'["Ticketsystem", "Help-Desk-Software"]',
'MEDIUM', FALSE, 1,
'["support", "kunden"]',
12),
-- IT Templates
('it-systemadministration',
'IT-Systemadministration',
'Verwaltung der IT-Infrastruktur, Benutzerkonten und Berechtigungen',
'it_operations',
'["IT_ADMIN"]',
'["ART6_1F", "ART6_1B"]',
'["EMPLOYEES"]',
'["NAME", "LOGIN_DATA", "IP_ADDRESS", "DEVICE_ID"]',
'["INTERNAL_IT", "PROCESSOR_HOSTING"]',
'["AC_RBAC", "AC_MFA", "AC_PAM", "CONF_ENCRYPTION_REST", "CONF_ENCRYPTION_TRANSIT", "INT_AUDIT_LOG", "SEP_NETWORK_SEG", "SEP_ENV_SEPARATION"]',
'CUSTOM_90D',
'["Active Directory", "LDAP", "IT-Management-Tool"]',
'HIGH', FALSE, 2,
'["it", "infrastruktur"]',
13),
('it-backup',
'Datensicherung und Recovery',
'Regelmaessige Backups und Wiederherstellungsverfahren',
'it_operations',
'["BACKUP_RECOVERY"]',
'["ART6_1F"]',
'["EMPLOYEES", "CUSTOMERS"]',
'["NAME", "ADDRESS", "CONTACT", "CONTRACT_DATA", "LOGIN_DATA"]',
'["INTERNAL_IT", "PROCESSOR_HOSTING"]',
'["AVAIL_BACKUP", "AVAIL_321_RULE", "AVAIL_REDUNDANCY", "CONF_ENCRYPTION_REST", "INT_CHECKSUMS"]',
'CUSTOM_90D',
'["Backup-Software", "Cloud-Backup", "NAS"]',
'HIGH', FALSE, 2,
'["it", "verfuegbarkeit"]',
14),
('it-logging',
'Logging und Sicherheitsueberwachung',
'Protokollierung von System- und Sicherheitsereignissen',
'it_operations',
'["SECURITY_MONITORING"]',
'["ART6_1F"]',
'["EMPLOYEES", "CUSTOMERS", "WEBSITE_USERS"]',
'["IP_ADDRESS", "LOGIN_DATA", "USAGE_DATA", "DEVICE_ID"]',
'["INTERNAL_IT"]',
'["CONF_ENCRYPTION_REST", "INT_AUDIT_LOG", "INT_CHECKSUMS", "AVAIL_MONITORING", "SEP_DATA_SEPARATION"]',
'CUSTOM_90D',
'["SIEM-System", "Log-Management", "Monitoring-Tool"]',
'MEDIUM', FALSE, 2,
'["it", "sicherheit"]',
15),
('it-iam',
'Identitaets- und Zugriffsmanagement',
'Verwaltung von Benutzeridentitaeten, Rollen und Berechtigungen',
'it_operations',
'["IAM"]',
'["ART6_1F", "BDSG_26"]',
'["EMPLOYEES"]',
'["NAME", "LOGIN_DATA", "EMPLOYMENT_DATA"]',
'["INTERNAL_IT", "INTERNAL_HR"]',
'["AC_RBAC", "AC_MFA", "AC_PAM", "AC_NEED_TO_KNOW", "INT_AUDIT_LOG", "CONF_ENCRYPTION_REST"]',
'AGG_15_6M',
'["IAM-System", "SSO-Provider", "Active Directory"]',
'HIGH', FALSE, 2,
'["it", "sicherheit", "zugriffskontrolle"]',
16),
-- Other Templates
('other-videokonferenz',
'Videokonferenz',
'Durchfuehrung von Online-Meetings und Videokonferenzen',
'other',
'["VIDEO_CONFERENCING"]',
'["ART6_1B", "ART6_1F"]',
'["EMPLOYEES", "CUSTOMERS", "BUSINESS_PARTNERS"]',
'["NAME", "CONTACT", "PHOTO_VIDEO", "IP_ADDRESS"]',
'["INTERNAL_IT", "PROCESSOR_HOSTING"]',
'["CONF_ENCRYPTION_TRANSIT", "AC_RBAC"]',
'PURPOSE_END',
'["Videokonferenz-Tool", "Webinar-Plattform"]',
'LOW', FALSE, 1,
'["kommunikation"]',
17),
('other-besuchermanagement',
'Besuchermanagement',
'Erfassung und Verwaltung von Betriebsbesuchern',
'other',
'["VISITOR_MANAGEMENT"]',
'["ART6_1F"]',
'["VISITORS"]',
'["NAME", "CONTACT", "PHOTO_VIDEO"]',
'["INTERNAL_MANAGEMENT"]',
'["AC_RBAC", "CONF_ENCRYPTION_REST"]',
'CUSTOM_30D',
'["Besuchermanagement-System", "Empfangsterminal"]',
'LOW', FALSE, 1,
'["sonstiges", "besucher"]',
18)
ON CONFLICT (id) DO NOTHING;
COMMIT;

File diff suppressed because it is too large Load Diff