From e1cffd3e53b95edee90d1fb9792786cf1cedd8b5 Mon Sep 17 00:00:00 2001 From: Albrecht Degering Date: Sun, 23 Aug 2026 20:19:14 +0200 Subject: [PATCH] docs(cases): complete German reference coverage --- package.json | 2 +- pyproject.toml | 2 +- src/govoplan_cases/backend/manifest.py | 232 ++++++++++++++++-- .../test_interface_documentation_contract.py | 15 +- webui/package.json | 2 +- webui/src/module.ts | 2 +- 6 files changed, 226 insertions(+), 29 deletions(-) diff --git a/package.json b/package.json index 73f67be..488e062 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@govoplan/cases-webui", - "version": "0.1.20", + "version": "0.1.21", "private": true, "type": "module", "main": "webui/src/index.ts", diff --git a/pyproject.toml b/pyproject.toml index 1f006cc..12e60ab 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "govoplan-cases" -version = "0.1.20" +version = "0.1.21" description = "GovOPlaN administrative case context module." readme = "README.md" requires-python = ">=3.12" diff --git a/src/govoplan_cases/backend/manifest.py b/src/govoplan_cases/backend/manifest.py index 4a665f8..6ab7cf4 100644 --- a/src/govoplan_cases/backend/manifest.py +++ b/src/govoplan_cases/backend/manifest.py @@ -43,7 +43,10 @@ from govoplan_cases.backend.party_context import ( ) from govoplan_cases.backend.acl import CaseAclProvider from govoplan_cases.backend.db import models as case_models -from govoplan_cases.backend.dsar_provider import CASES_DSAR_CAPABILITY, CasesDsarProvider +from govoplan_cases.backend.dsar_provider import ( + CASES_DSAR_CAPABILITY, + CasesDsarProvider, +) from govoplan_cases.backend.service_intake import ( CAPABILITY_CASES_SERVICE_INTAKE, CaseServiceIntake, @@ -66,7 +69,7 @@ from govoplan_core.db.base import Base MODULE_ID = "cases" -MODULE_VERSION = "0.1.20" +MODULE_VERSION = "0.1.21" READ_SCOPE = "cases:case:read" CREATE_SCOPE = "cases:case:create" UPDATE_SCOPE = "cases:case:update" @@ -169,20 +172,47 @@ manifest = ModuleManifest( CAPABILITY_DECISION_REGISTRY, ), permissions=( - _permission(READ_SCOPE, "View cases", "Read tenant cases and their governed history."), - _permission(CREATE_SCOPE, "Create cases", "Open a case from a configured case type or service intake."), - _permission(UPDATE_SCOPE, "Update cases", "Create a guarded immutable case revision."), - _permission(ASSIGN_SCOPE, "Assign cases", "Change stable function, assignment, or work-item references on a case."), - _permission(CLOSE_SCOPE, "Close cases", "Move a case to a configured terminal status."), - _permission(SHARE_SCOPE, "Share cases", "Restrict a case and grant object-level access to selected principals."), - _permission(ADMIN_SCOPE, "Administer cases", "Configure tenant case types and statuses."), + _permission( + READ_SCOPE, "View cases", "Read tenant cases and their governed history." + ), + _permission( + CREATE_SCOPE, + "Create cases", + "Open a case from a configured case type or service intake.", + ), + _permission( + UPDATE_SCOPE, "Update cases", "Create a guarded immutable case revision." + ), + _permission( + ASSIGN_SCOPE, + "Assign cases", + "Change stable function, assignment, or work-item references on a case.", + ), + _permission( + CLOSE_SCOPE, "Close cases", "Move a case to a configured terminal status." + ), + _permission( + SHARE_SCOPE, + "Share cases", + "Restrict a case and grant object-level access to selected principals.", + ), + _permission( + ADMIN_SCOPE, "Administer cases", "Configure tenant case types and statuses." + ), ), role_templates=( RoleTemplate( slug="case_manager", name="Case manager", description="Create, assign, update, and close tenant cases.", - permissions=(READ_SCOPE, CREATE_SCOPE, UPDATE_SCOPE, ASSIGN_SCOPE, CLOSE_SCOPE, SHARE_SCOPE), + permissions=( + READ_SCOPE, + CREATE_SCOPE, + UPDATE_SCOPE, + ASSIGN_SCOPE, + CLOSE_SCOPE, + SHARE_SCOPE, + ), ), RoleTemplate( slug="case_reader", @@ -194,7 +224,15 @@ manifest = ModuleManifest( slug="case_administrator", name="Case administrator", description="Configure case types and statuses and manage cases.", - permissions=(READ_SCOPE, CREATE_SCOPE, UPDATE_SCOPE, ASSIGN_SCOPE, CLOSE_SCOPE, SHARE_SCOPE, ADMIN_SCOPE), + permissions=( + READ_SCOPE, + CREATE_SCOPE, + UPDATE_SCOPE, + ASSIGN_SCOPE, + CLOSE_SCOPE, + SHARE_SCOPE, + ADMIN_SCOPE, + ), ), ), route_factory=_router, @@ -240,7 +278,11 @@ manifest = ModuleManifest( label="i18n:govoplan-core.product_area.services_cases", icon="landmark", description="i18n:govoplan-core.product_area.services_cases_description", - surface_ids=("cases.nav.cases", "cases.route.cases", "cases.route.cases.caseid"), + surface_ids=( + "cases.nav.cases", + "cases.route.cases", + "cases.route.cases.caseid", + ), order=20, ), ), @@ -339,14 +381,41 @@ manifest = ModuleManifest( ModuleInterfaceProvider(name="cases.service_launcher", version="0.1.0"), ModuleInterfaceProvider(name=CAPABILITY_RECORD_SOURCE_CASES, version="1.0.0"), ModuleInterfaceProvider(name=CASES_DSAR_CAPABILITY, version="0.1.0"), - ModuleInterfaceProvider(name=CAPABILITY_TICKET_CASE_ESCALATION, version="1.0.0"), + ModuleInterfaceProvider( + name=CAPABILITY_TICKET_CASE_ESCALATION, version="1.0.0" + ), ), requires_interfaces=( - ModuleInterfaceRequirement(name="services.definition", version_min="0.1.0", version_max_exclusive="0.2.0", optional=True), - ModuleInterfaceRequirement(name="parties.procedure", version_min="0.1.0", version_max_exclusive="0.2.0", optional=True), - ModuleInterfaceRequirement(name="parties.representation", version_min="0.1.0", version_max_exclusive="0.2.0", optional=True), - ModuleInterfaceRequirement(name="mandates.resolution", version_min="0.1.0", version_max_exclusive="0.2.0", optional=True), - ModuleInterfaceRequirement(name="decisions.formal_outcome", version_min="0.1.0", version_max_exclusive="0.2.0", optional=True), + ModuleInterfaceRequirement( + name="services.definition", + version_min="0.1.0", + version_max_exclusive="0.2.0", + optional=True, + ), + ModuleInterfaceRequirement( + name="parties.procedure", + version_min="0.1.0", + version_max_exclusive="0.2.0", + optional=True, + ), + ModuleInterfaceRequirement( + name="parties.representation", + version_min="0.1.0", + version_max_exclusive="0.2.0", + optional=True, + ), + ModuleInterfaceRequirement( + name="mandates.resolution", + version_min="0.1.0", + version_max_exclusive="0.2.0", + optional=True, + ), + ModuleInterfaceRequirement( + name="decisions.formal_outcome", + version_min="0.1.0", + version_max_exclusive="0.2.0", + optional=True, + ), ), capability_factories={ CAPABILITY_CASES_SERVICE_INTAKE: _service_intake, @@ -461,7 +530,10 @@ manifest = ModuleManifest( }, metadata={ "kind": "workflow", - "help_contexts": ["tickets.action.escalate", "cases.state.ticket-escalation"], + "help_contexts": [ + "tickets.action.escalate", + "cases.state.ticket-escalation", + ], "consequence_classes": { "create_case": "Creates one Cases-owned formal procedure and returns its stable reference.", "preserve_ticket": "Does not convert, copy, or mutate the Ticket history.", @@ -511,6 +583,27 @@ manifest = ModuleManifest( kind="repository", ), ), + translations={ + "de": { + "title": "Exakte Files-, Mail- und Campaign-Verweise mit einem Vorgang verknüpfen", + "summary": ( + "Einen vom zuständigen Modul autorisierten Verweis hinzufügen oder entfernen, ohne dessen Inhalt nach Cases zu kopieren." + ), + "body": ( + "Für einen aktiven Vorgang können Files, Mail und Campaigns über den versionierten Schnellzugriff-Ergebnisvertrag eine " + "exakte mandantengebundene Auswahl zurückgeben. Cases akzeptiert nur unterstützte Kombinationen aus zuständigem Modul " + "und Verweisart, den passenden Mandanten, eine festgelegte Version und die kanonische lokale Route des zuständigen Moduls. " + "Verknüpfen und Entfernen prüfen die Änderungsberechtigung für den Vorgang erneut, verwenden erwartete Revision und " + "Idempotenz und ergänzen eine unveränderliche Vorgangshistorie. Cases speichert nur typisierte Modulkennung, exakte " + "Version und Modulroute; Dateibytes, Nachrichtenkopf oder -inhalt, Empfangende, Campaign-Inhalt oder eine frühere " + "Zugriffsentscheidung werden nie kopiert. Das Öffnen wechselt stets in das zuständige Modul, das den aktuellen Zugriff " + "erneut prüft. Wird dieses Modul deaktiviert oder der Quellzugriff später entzogen, behält der Vorgang den historischen " + "Verweis, legt aber keinen Quellinhalt offen. Das Entfernen nimmt nur den Verweis aus der nächsten Vorgangsrevision und " + "löscht niemals das Quellobjekt. Ungespeicherte Vorgangsänderungen müssen vor dem Verknüpfen eines Schnellzugriff-Ergebnisses " + "gespeichert oder verworfen werden." + ), + } + }, metadata={ "kind": "workflow", "help_contexts": ["cases.detail.references"], @@ -540,6 +633,27 @@ manifest = ModuleManifest( kind="repository", ), ), + translations={ + "de": { + "title": "Betroffenenanfragen für Vorgänge", + "summary": ( + "Gesteuerte Vorgangstatsachen ausgeben, ohne die antragstellende Identität zu erraten oder undurchsichtige " + "Vorgangsdaten offenzulegen." + ), + "body": ( + "Der DSAR-Provider von Cases gleicht im exakten Mandanten Konten-, Identitäts- und Mitgliedschaftszuordnungen, " + "Zugriffsfreigaben für Konten oder Identitäten und ausdrückliche Cases-Verweise ab. Wird ein kanonischer Selektor mit " + "einem direkten Verweis kombiniert, muss eine Cases-eigene Beziehung beide bestätigen; andernfalls schließt die Suche " + "sicher. Direkte Vorgangs- und Revisionsverweise exportieren eine typisierte, minimierte Lebenszyklusprojektion. Rohe " + "Snapshots, Metadaten, Suchtext, Freitext-Änderungsgründe, Timeline-Nutzdaten und -Zusammenfassungen, Nachweiskennungen, " + "Anfrage-Digests, Idempotenzschlüssel, Auditkennungen und unbeteiligte Zugriffsbetroffene sind ausgeschlossen. " + "Unveränderliche Vorgangskennungen, Revisionen, Timelines und Zuordnungen werden aufbewahrt. Aktuelle offene Vorgänge und " + "aktive Zugriffsmerkmale erfordern eine berechtigte manuelle Prüfung im normalen Vorgangs-/Zugriffslebenszyklus; eine " + "automatische Löschung wird nicht angeboten. Die Verbindung zwischen antragstellender Person und Identität gehört Parties; " + "Cases leitet sie daher nicht aus Beteiligtenkennungen ab." + ), + } + }, ), DocumentationTopic( id="cases.workflow.quick-access-context", @@ -603,9 +717,31 @@ manifest = ModuleManifest( ), links=( DocumentationLink(label="Cases", href="/cases", kind="runtime"), - DocumentationLink(label="Cases concept", href="govoplan-cases/docs/CONCEPT.md", kind="repository"), - DocumentationLink(label="Decisions domain", href="govoplan-decisions/docs/DECISIONS_DOMAIN.md", kind="repository"), + DocumentationLink( + label="Cases concept", + href="govoplan-cases/docs/CONCEPT.md", + kind="repository", + ), + DocumentationLink( + label="Decisions domain", + href="govoplan-decisions/docs/DECISIONS_DOMAIN.md", + kind="repository", + ), ), + translations={ + "de": { + "title": "Förmliche Entscheidung aus einem Vorgang festhalten", + "summary": "Handlungsbefugnis erneut prüfen und eine exakte Decision-Revision in der Vorgangshistorie bewahren.", + "body": ( + "Die Aktion in der Vorgangsdetailansicht akzeptiert nur Tenor, Begründung, Bedingungen, Wirksamkeitszeitpunkt und " + "Änderungsgrund. Cases löst die exakte Vorgangsrevision auf, prüft den objektbezogenen Änderungszugriff, ermittelt über " + "Access die aktuelle Handlungszuweisung und verlangt genau ein wirksames Mandat für cases.formal_decision und den gewählten " + "Decision-Typ. Anschließend wird das Ergebnis über das optionale Decisions-Register aufgezeichnet und die exakte " + "Decision-Revision in derselben Datenbanktransaktion mit einer neuen Vorgangsrevision verknüpft. Eine Wiederholung " + "verwendet eine deterministische Decision-Kennung; abweichende Nutzdaten schließen sicher." + ), + } + }, metadata={ "kind": "workflow", "help_contexts": ["cases.action.decide", "cases.decision.editor"], @@ -646,8 +782,24 @@ manifest = ModuleManifest( links=( DocumentationLink(label="Cases", href="/cases", kind="runtime"), DocumentationLink(label="Records", href="/records", kind="runtime"), - DocumentationLink(label="Cases concept", href="govoplan-cases/docs/CONCEPT.md", kind="repository"), + DocumentationLink( + label="Cases concept", + href="govoplan-cases/docs/CONCEPT.md", + kind="repository", + ), ), + translations={ + "de": { + "title": "Exakte Vorgangsrevision in eine eAkte ablegen", + "summary": "Einen rekonstruierbaren Vorgangs-Snapshot in Records bewahren, ohne die Zuständigkeit von Cases zu verlagern.", + "body": ( + "Ist Records aktiviert, löst Cases eine exakte unveränderliche Vorgangsrevision erst auf, nachdem aktuelle " + "Vorgangsberechtigung und objektbezogener Zugriff geprüft wurden. Der abgelegte Verweis enthält Vorgangsnummer, " + "Lebenszyklusstatus, dargestelltes Gültigkeitsintervall, Aufzeichnungszeitpunkt und einen SHA-256-Digest des kanonischen " + "Snapshots. Cases bleibt für den Vorgangslebenszyklus maßgeblich." + ), + } + }, metadata={ "kind": "workflow", "help_contexts": ["cases.detail", "records.action.file"], @@ -689,6 +841,23 @@ manifest = ModuleManifest( kind="repository", ), ), + translations={ + "de": { + "title": "Institutioneller Kontext eines Vorgangs", + "summary": ( + "Cases bewahrt Service- und Beteiligtenverweise, ohne institutionelle Definitionen oder Betroffenenstammdaten zu übernehmen." + ), + "body": ( + "Vorgangstypen und -status sind Mandantenkonfiguration. Jede Anlage oder Änderung schreibt eine unveränderliche, durch " + "optimistische Nebenläufigkeitskontrolle geschützte Revision und ein wiederholungssicheres Timeline-Ereignis. Die " + "Service-Aufnahme hält exakte Bindungen an Service, Mandat, Zuständigkeit, Rechtsgrundlage, Formular, Workflow und Ergebnis " + "fest. Verfahrensbeteiligte stammen von einem optionalen Provider oder einer begrenzten Cases-Kompatibilitätsprojektion. " + "Zuweisungs-, Nachweis-, Decision- und Aktenverweise bleiben stabile Referenzen im Eigentum ihrer Quellmodule. Ist Search " + "aktiviert, liefert Cases eine neu aufbaubare reine Metadatenprojektion. Nachweise und geschützte Decision-Inhalte sind " + "ausgeschlossen; jeder Treffer wird vor der Offenlegung erneut gegen den aktuellen Vorgangszugriff geprüft." + ), + } + }, metadata={ "help_contexts": [ "cases.list", @@ -725,7 +894,26 @@ manifest = ModuleManifest( kind="repository", ), ), + translations={ + "de": { + "title": "Referenz für Vorgangslebenszyklus, Zugriff und Nachweise", + "summary": ( + "Revisions-, Status-, Zugriffs- und Verweisfelder gemeinsam mit ihren dauerhaften Folgen erläutern." + ), + "body": ( + "Änderungen an Titel und Status ergänzen eine unveränderliche Vorgangsrevision, geschützt durch erwartete Revision und " + "stabilen Idempotenzschlüssel. Jede akzeptierte Änderung ergänzt außerdem einen Timeline-Eintrag mit handelnder Person, " + "Zeitpunkt und Änderungsgrund; ein abschließender Status verlangt zusätzlich die Berechtigung zum Schließen. Die Sichtbarkeit " + "eines Vorgangs ist mandantenweit oder eingeschränkt. Eingeschränkte Vorgänge bleiben nur durch administrative Befugnis, " + "Zuweisungs- oder Einheitenkontext, Erstellerbefugnis oder eine ausdrückliche Konto-/Gruppenfreigabe sichtbar. " + "Zugriffsänderungen ergänzen eine weitere unveränderliche Revision und verlangen Bestätigung. Service-, Beteiligten-, " + "Zuweisungs-, Decision- und Aktenverweise bezeichnen Objekte anderer Provider; Cases bewahrt ihre stabilen Kennungen und " + "Versionen, ohne sie zu kopieren oder stillschweigend zu verändern." + ), + } + }, metadata={ + "kind": "reference", "help_contexts": [ "cases.field.title", "cases.field.status", diff --git a/tests/test_interface_documentation_contract.py b/tests/test_interface_documentation_contract.py index 79de8dc..8fae9b3 100644 --- a/tests/test_interface_documentation_contract.py +++ b/tests/test_interface_documentation_contract.py @@ -10,6 +10,14 @@ REPO_ROOT = Path(__file__).resolve().parents[1] class CasesInterfaceDocumentationContractTests(unittest.TestCase): + def test_all_static_topics_have_complete_german_content(self) -> None: + for topic in get_manifest().documentation: + german = (topic.translations or {}).get("de", {}) + self.assertEqual({"title", "summary", "body"}, set(german), topic.id) + self.assertTrue( + all(str(value).strip() for value in german.values()), topic.id + ) + def test_backend_surfaces_and_hierarchy_remain_declared(self) -> None: frontend = get_manifest().frontend self.assertIsNotNone(frontend) @@ -53,15 +61,16 @@ class CasesInterfaceDocumentationContractTests(unittest.TestCase): self.assertIn("update_case", reference.metadata["consequence_classes"]) self.assertIn("close_case", reference.metadata["consequence_classes"]) self.assertIn("change_access", reference.metadata["consequence_classes"]) + self.assertEqual("reference", reference.metadata["kind"]) self.assertIn( "link_owner_reference", owner_links.metadata["consequence_classes"], ) def test_webui_uses_shared_help_guard_and_confirmation_components(self) -> None: - list_page = ( - REPO_ROOT / "webui/src/features/cases/CasesPage.tsx" - ).read_text(encoding="utf-8") + list_page = (REPO_ROOT / "webui/src/features/cases/CasesPage.tsx").read_text( + encoding="utf-8" + ) detail_page = ( REPO_ROOT / "webui/src/features/cases/CaseDetailPage.tsx" ).read_text(encoding="utf-8") diff --git a/webui/package.json b/webui/package.json index 64a111a..ff750c9 100644 --- a/webui/package.json +++ b/webui/package.json @@ -1,6 +1,6 @@ { "name": "@govoplan/cases-webui", - "version": "0.1.20", + "version": "0.1.21", "private": true, "type": "module", "main": "src/index.ts", diff --git a/webui/src/module.ts b/webui/src/module.ts index 6f448f4..06bce70 100644 --- a/webui/src/module.ts +++ b/webui/src/module.ts @@ -10,7 +10,7 @@ const CaseDetailPage = lazy(() => import("./features/cases/CaseDetailPage")); export const casesModule: PlatformWebModule = { id: "cases", label: "i18n:govoplan-cases.cases", - version: "0.1.20", + version: "0.1.21", optionalDependencies: [ "access", "addresses",