""" Shared versioning utilities for all compliance document types. Provides create_version_snapshot() and list_versions() helpers that work with all 5 version tables (DSFA, VVT, TOM, Loeschfristen, Obligations). """ import json import logging from datetime import datetime from typing import Optional, List from sqlalchemy import text from sqlalchemy.orm import Session logger = logging.getLogger(__name__) # Table → FK column mapping VERSION_TABLES = { "dsfa": ("compliance_dsfa_versions", "dsfa_id", "compliance_dsfas"), "vvt_activity": ("compliance_vvt_activity_versions", "activity_id", "compliance_vvt_activities"), "tom": ("compliance_tom_versions", "measure_id", "compliance_tom_measures"), "loeschfristen": ("compliance_loeschfristen_versions", "policy_id", "compliance_loeschfristen"), "obligation": ("compliance_obligation_versions", "obligation_id", "compliance_obligations"), } def create_version_snapshot( db: Session, doc_type: str, doc_id: str, tenant_id: str, snapshot: dict, change_summary: str = "", changed_sections: list = None, created_by: str = "system", ) -> dict: """Create a new version snapshot for any document type. Args: doc_type: One of "dsfa", "vvt_activity", "tom", "loeschfristen", "obligation" doc_id: UUID of the source document tenant_id: Tenant UUID snapshot: Full JSONB snapshot of the document state change_summary: Human-readable summary of changes changed_sections: List of section identifiers that changed created_by: User who created this version Returns: Dict with version info (id, version_number, created_at) """ if doc_type not in VERSION_TABLES: raise ValueError(f"Unknown document type: {doc_type}") version_table, fk_column, source_table = VERSION_TABLES[doc_type] # Get next version number result = db.execute( text(f"SELECT COALESCE(MAX(version_number), 0) FROM {version_table} WHERE {fk_column} = :doc_id"), {"doc_id": doc_id}, ) next_version = result.scalar() + 1 # Insert version result = db.execute( text(f""" INSERT INTO {version_table} ({fk_column}, tenant_id, version_number, snapshot, change_summary, changed_sections, created_by) VALUES (:doc_id, :tenant_id, :version_number, CAST(:snapshot AS jsonb), :change_summary, CAST(:changed_sections AS jsonb), :created_by) RETURNING id, version_number, created_at """), { "doc_id": doc_id, "tenant_id": tenant_id, "version_number": next_version, "snapshot": json.dumps(snapshot), "change_summary": change_summary, "changed_sections": json.dumps(changed_sections or []), "created_by": created_by, }, ) row = result.fetchone() # Update current_version on the source table db.execute( text(f"UPDATE {source_table} SET current_version = :v WHERE id = :doc_id"), {"v": next_version, "doc_id": doc_id}, ) return { "id": str(row[0]), "version_number": row[1], "created_at": row[2].isoformat() if row[2] else None, } def list_versions( db: Session, doc_type: str, doc_id: str, tenant_id: str, ) -> List[dict]: """List all versions for a document, newest first.""" if doc_type not in VERSION_TABLES: raise ValueError(f"Unknown document type: {doc_type}") version_table, fk_column, _ = VERSION_TABLES[doc_type] result = db.execute( text(f""" SELECT id, version_number, status, change_summary, changed_sections, created_by, approved_by, approved_at, created_at FROM {version_table} WHERE {fk_column} = :doc_id AND tenant_id = :tenant_id ORDER BY version_number DESC """), {"doc_id": doc_id, "tenant_id": tenant_id}, ) rows = result.fetchall() return [ { "id": str(r[0]), "version_number": r[1], "status": r[2], "change_summary": r[3], "changed_sections": r[4] or [], "created_by": r[5], "approved_by": r[6], "approved_at": r[7].isoformat() if r[7] else None, "created_at": r[8].isoformat() if r[8] else None, } for r in rows ] def get_version( db: Session, doc_type: str, doc_id: str, version_number: int, tenant_id: str, ) -> Optional[dict]: """Get a specific version with its full snapshot.""" if doc_type not in VERSION_TABLES: raise ValueError(f"Unknown document type: {doc_type}") version_table, fk_column, _ = VERSION_TABLES[doc_type] result = db.execute( text(f""" SELECT id, version_number, status, snapshot, change_summary, changed_sections, created_by, approved_by, approved_at, created_at FROM {version_table} WHERE {fk_column} = :doc_id AND version_number = :v AND tenant_id = :tenant_id """), {"doc_id": doc_id, "v": version_number, "tenant_id": tenant_id}, ) r = result.fetchone() if not r: return None return { "id": str(r[0]), "version_number": r[1], "status": r[2], "snapshot": r[3], "change_summary": r[4], "changed_sections": r[5] or [], "created_by": r[6], "approved_by": r[7], "approved_at": r[8].isoformat() if r[8] else None, "created_at": r[9].isoformat() if r[9] else None, }