e34f7cb507
CI / detect-changes (push) Successful in 7s
CI / branch-name (push) Has been skipped
CI / guardrail-integrity (push) Has been skipped
CI / secret-scan (push) Has been skipped
CI / dep-audit (push) Has been skipped
CI / sbom-scan (push) Has been skipped
CI / build-sha-integrity (push) Failing after 4s
CI / validate-canonical-controls (push) Successful in 11s
CI / loc-budget (push) Failing after 14s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / nodejs-build (push) Has been skipped
CI / test-go (push) Has been skipped
CI / iace-gt-coverage (push) Has been skipped
CI / test-python-backend (push) Successful in 30s
CI / test-python-document-crawler (push) Has been skipped
CI / test-python-dsms-gateway (push) Has been skipped
Phase 1 of the workspace-cutover initiative: compliance becomes the
single source of truth for documents. Step one is making the existing
compliance_legal_documents workflow rich enough to express the DSB→
Mandant approval pattern that the workspace's 5-stage UI needed.
Migration 148:
- Adds CHECK constraint on status (was free-form VARCHAR20)
- Allows: draft, review, review_internal, review_client, approved,
published, archived, rejected (legacy "review" kept for backward
compat — 0 existing rows so no backfill needed)
- Adds CHECK on approvals.action with extended values:
submitted_internal, submitted_client, approved_internal,
approved_client, rejected_internal, rejected_client
- Adds 6 new columns for the richer audit trail: submitted_by/at,
approved_internal_by/at, approved_client_by/at
Service:
- New methods submit_internal_review, approve_internal, approve_client
- submit_review / approve kept as backwards-compat aliases that map to
the new methods
- reject() now reads current status to log specific rejected_internal
or rejected_client action
- _version_to_response includes all new audit fields
Routes:
- POST /versions/{id}/submit-internal-review
- POST /versions/{id}/approve-internal (DSB sagt OK → Mandant ist dran)
- POST /versions/{id}/approve-client (Mandant sagt OK → approved)
- Existing submit-review / approve endpoints stay but map through aliases
Schema:
- VersionResponse extended with optional submitted_by/at,
approved_internal_by/at, approved_client_by/at fields
This unlocks Phase 2 (Generate-All in compliance generator), Phase 3
(Document-Library tab in admin), Phase 4 (workspace cutover — drop its
own document storage and route everything through this lifecycle).
[migration-approved]
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
109 lines
4.0 KiB
Python
109 lines
4.0 KiB
Python
"""
|
|
SQLAlchemy models for Legal Documents — Rechtliche Texte mit Versionierung und Approval-Workflow.
|
|
|
|
Tables:
|
|
- compliance_legal_documents: Dokumenttypen (DSE, AGB, Cookie-Policy etc.)
|
|
- compliance_legal_document_versions: Versionen mit Status-Workflow
|
|
- compliance_legal_document_approvals: Audit-Trail fuer Freigaben
|
|
"""
|
|
|
|
import uuid
|
|
from datetime import datetime
|
|
|
|
from sqlalchemy import (
|
|
Column, String, Text, Boolean, DateTime, Index, ForeignKey
|
|
)
|
|
from sqlalchemy.dialects.postgresql import UUID
|
|
|
|
from classroom_engine.database import Base
|
|
|
|
|
|
class LegalDocumentDB(Base):
|
|
"""Legal document type — DSE, AGB, Cookie-Policy, Impressum, AVV etc."""
|
|
|
|
__tablename__ = 'compliance_legal_documents'
|
|
|
|
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
|
tenant_id = Column(String(100))
|
|
type = Column(String(50), nullable=False) # privacy_policy|terms|cookie_policy|imprint|dpa
|
|
name = Column(String(300), nullable=False)
|
|
description = Column(Text)
|
|
mandatory = Column(Boolean, default=False)
|
|
created_at = Column(DateTime, default=datetime.utcnow, nullable=False)
|
|
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
|
|
|
|
__table_args__ = (
|
|
Index('idx_legal_docs_tenant', 'tenant_id'),
|
|
Index('idx_legal_docs_type', 'type'),
|
|
)
|
|
|
|
def __repr__(self):
|
|
return f"<LegalDocument {self.type}: {self.name}>"
|
|
|
|
|
|
class LegalDocumentVersionDB(Base):
|
|
"""Version of a legal document with 5-stage Approval-Workflow.
|
|
|
|
Lifecycle:
|
|
draft → review_internal (DSB-Prüfung)
|
|
→ review_client (Mandanten-Prüfung)
|
|
→ approved → published
|
|
(Ablehnung: review_internal/review_client → rejected → zurück zu draft)
|
|
"""
|
|
|
|
__tablename__ = 'compliance_legal_document_versions'
|
|
|
|
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
|
document_id = Column(UUID(as_uuid=True), ForeignKey('compliance_legal_documents.id', ondelete='CASCADE'), nullable=False)
|
|
version = Column(String(20), nullable=False)
|
|
language = Column(String(10), default='de')
|
|
title = Column(String(300), nullable=False)
|
|
content = Column(Text, nullable=False)
|
|
summary = Column(Text)
|
|
status = Column(String(20), default='draft') # draft|review_internal|review_client|approved|published|archived|rejected (+ legacy 'review')
|
|
created_by = Column(String(200))
|
|
|
|
# Backwards-compat single approval (legacy 4-stage Flow)
|
|
approved_by = Column(String(200))
|
|
approved_at = Column(DateTime)
|
|
|
|
# 5-stage Trail
|
|
submitted_by = Column(String(200))
|
|
submitted_at = Column(DateTime)
|
|
approved_internal_by = Column(String(200))
|
|
approved_internal_at = Column(DateTime)
|
|
approved_client_by = Column(String(200))
|
|
approved_client_at = Column(DateTime)
|
|
|
|
rejection_reason = Column(Text)
|
|
created_at = Column(DateTime, default=datetime.utcnow, nullable=False)
|
|
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
|
|
|
|
__table_args__ = (
|
|
Index('idx_legal_doc_versions_doc', 'document_id'),
|
|
Index('idx_legal_doc_versions_status', 'status'),
|
|
)
|
|
|
|
def __repr__(self):
|
|
return f"<LegalDocumentVersion {self.version} [{self.status}]>"
|
|
|
|
|
|
class LegalDocumentApprovalDB(Base):
|
|
"""Audit trail for all approval actions on document versions."""
|
|
|
|
__tablename__ = 'compliance_legal_document_approvals'
|
|
|
|
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
|
version_id = Column(UUID(as_uuid=True), ForeignKey('compliance_legal_document_versions.id', ondelete='CASCADE'), nullable=False)
|
|
action = Column(String(50), nullable=False) # submitted|approved|rejected|published|archived
|
|
approver = Column(String(200))
|
|
comment = Column(Text)
|
|
created_at = Column(DateTime, default=datetime.utcnow, nullable=False)
|
|
|
|
__table_args__ = (
|
|
Index('idx_legal_doc_approvals_version', 'version_id'),
|
|
)
|
|
|
|
def __repr__(self):
|
|
return f"<LegalDocumentApproval {self.action} on version {self.version_id}>"
|