diff --git a/pyproject.toml b/pyproject.toml index 2f643a4..e9cd8fc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,12 +4,12 @@ build-backend = "setuptools.build_meta" [project] name = "govoplan-payments" -version = "0.1.20" +version = "0.1.21" description = "Replay-safe payment obligations and reconciliation evidence for GovOPlaN." readme = "README.md" requires-python = ">=3.12" authors = [{ name = "GovOPlaN" }] -dependencies = ["govoplan-core>=0.1.18"] +dependencies = ["govoplan-core>=0.1.37"] [tool.setuptools.packages.find] where = ["src"] diff --git a/src/govoplan_payments/__init__.py b/src/govoplan_payments/__init__.py index e167a0e..3c67702 100644 --- a/src/govoplan_payments/__init__.py +++ b/src/govoplan_payments/__init__.py @@ -1,3 +1,3 @@ """GovOPlaN Payments module.""" -__version__ = "0.1.20" +__version__ = "0.1.21" diff --git a/src/govoplan_payments/backend/manifest.py b/src/govoplan_payments/backend/manifest.py index 4b86dd7..907e395 100644 --- a/src/govoplan_payments/backend/manifest.py +++ b/src/govoplan_payments/backend/manifest.py @@ -8,6 +8,7 @@ from govoplan_core.core.module_guards import ( ) from govoplan_core.core.modules import ( CapabilityDocumentation, + DocumentationCondition, DocumentationLink, DocumentationTopic, FrontendModule, @@ -35,7 +36,7 @@ from govoplan_payments.backend.service import SqlPaymentRequestProvider MODULE_ID = "payments" MODULE_NAME = "Payments" -MODULE_VERSION = "0.1.20" +MODULE_VERSION = "0.1.21" READ_SCOPE = "payments:payment:read" WRITE_SCOPE = "payments:payment:write" RECONCILE_SCOPE = "payments:payment:reconcile" @@ -277,6 +278,7 @@ manifest = ModuleManifest( audience=("data_subject", "operator", "auditor", "module_admin"), related_modules=("core", "cases", "workflow_engine", "ledger"), metadata={ + "kind": "reference", "help_contexts": [ "payments.workspace", "payments.state.requested", @@ -298,6 +300,48 @@ manifest = ModuleManifest( ), }, }, + translations={ + "de": { + "title": "Datenschutzanfragen zu Zahlungen", + "summary": ( + "Exakte Zahlungsverpflichtungen und Finanznachweise exportieren, ohne " + "Zahlungsbeschreibungen als Identitätssuchfläche zu verwenden." + ), + "body": ( + "Payments besitzt keine Spalte für Einwohner- oder Antragstelleridentitäten und " + "durchsucht weder Zahlungsbetreffe noch Kontext-JSON, Metadaten oder Quelldatensätze " + "nach einer Person. Eine vollständige Finanzauskunft erfordert deshalb eine exakte " + "Zahlungszeilenkennung, Zahlungskennung oder menschenlesbare Zahlungsreferenz, die als " + "verifizierte externe Betroffenenreferenz bereitgestellt wird. Das Auskunftspaket enthält " + "Verpflichtungsbetrag, Währung, Betreff, Status, Zeitpunkte, Quell- und begrenzte " + "Kontextreferenzen, Abstimmungsfakten, typisierte Nachweisreferenzen und Fakten zu " + "Lebenszyklusereignissen. Abstimmungsmetadaten, Ereignisinhalte, Prüfsummen, " + "Wiederholungsschlüssel, Anbieterdaten, Prüf-URLs und Zugangsdaten bleiben ausgeschlossen. " + "Eine Anfrage nur mit Konto-, Mitgliedschafts-, Identitäts- oder exakter Akteursreferenz " + "liefert minimierte Zuschreibungen zu Anforderung, Abstimmung und Ereignissen dieser " + "bearbeitenden Person; Zahlungsbetreffe werden nicht offengelegt. Jedes Ergebnis ist exakt " + "mandantenbegrenzt. Alle Löschaktionen sind reine Aufbewahrungsergebnisse und nicht " + "ausführbar, weil Verpflichtungen, Erfüllungen, Nachweisverknüpfungen und " + "Lebenszykluszuschreibungen gesteuerte finanzielle und gesetzliche Nachweise bleiben." + ), + } + }, + structured_translation_version="1", + structured_translations={ + "de": { + "consequence_classes": { + "export_exact_payment": ( + "Gibt Verpflichtung und begrenzte Finanznachweise für eine verifizierte exakte Zahlungskennung zurück." + ), + "export_operator_attribution": ( + "Gibt minimierte Finanzaktivität, aber niemals beliebige Zahlungsinhalte zurück." + ), + "retain_payment_evidence": ( + "Bewahrt Finanznachweise gemäß konfigurierter gesetzlicher Aufbewahrung und Sperre auf." + ), + } + } + }, ), DocumentationTopic( id="payments.requests-and-reconciliation", @@ -313,6 +357,8 @@ manifest = ModuleManifest( layer="configured", documentation_types=("admin", "user"), audience=("operator", "module_admin", "auditor", "product_owner"), + conditions=(DocumentationCondition(required_scopes=(READ_SCOPE,)),), + related_modules=("cases", "workflow_engine", "audit", "ledger"), links=( DocumentationLink( label="Payments boundary and recovery", @@ -321,6 +367,7 @@ manifest = ModuleManifest( ), ), metadata={ + "kind": "workflow", "help_contexts": [ "payments.request", "payments.workspace", @@ -329,6 +376,26 @@ manifest = ModuleManifest( "payments.state.requested", "payments.state.paid", ], + "purpose": ( + "Create an exact payment obligation and reconcile it as paid only against matching immutable evidence." + ), + "prerequisites": [ + "The actor can read Payments; creating and reconciling require their dedicated scopes.", + "The source procedure supplies a stable same-tenant reference and a replay-safe request key.", + "Manual reconciliation has a same-tenant versioned or checksum-bound evidence reference.", + ], + "steps": [ + "Create the amount, currency, source reference, due date, and human payment reference in the guided dialog.", + "Retain the returned payment ID in the calling Case or Workflow instead of writing Payments tables.", + "Reload the obligation before reconciliation when the workspace reports stale data.", + "Provide the external transaction reference, exact settlement time, and immutable evidence reference.", + "Confirm full amount and currency; any mismatch, duplicate, partial amount, or cross-tenant evidence fails closed.", + "Review the appended lifecycle and audit evidence after the obligation becomes paid.", + ], + "limitations": [ + "Only full manual reconciliation is supported; partial payment, refund, reversal, and correction need future governed flows.", + "Online checkout, callbacks, Ledger posting, XRechnung, and an applicant payment page are not implemented here.", + ], "privacy_notes": [ "Procedure context uses stable references; applicant names, bank account details, and submitted form values are not required.", "The immutable evidence remains owned by its provider; Payments stores only the typed EvidenceReference.", @@ -337,6 +404,79 @@ manifest = ModuleManifest( "request_payment": "Creates a durable amount/currency obligation and a stable applicant payment reference.", "reconcile_manual": "Marks the exact obligation paid and appends evidence; a future governed adjustment is required to reverse it.", }, + "verification": [ + "The paid obligation retains the original amount, currency, payment ID, and human reference unchanged.", + "Reconciliation names the external transaction and immutable evidence reference.", + "Replay and duplicate checks prove that one external settlement did not create conflicting paid states.", + ], + }, + translations={ + "de": { + "title": "Zahlungsanforderungen und manuelle Abstimmung", + "summary": ( + "Eine exakte Verpflichtung anlegen und nur mit passendem unveränderlichem " + "Nachweis als bezahlt kennzeichnen." + ), + "body": ( + "Payments führt die mandantengebundene Zahlungskennung, die menschenlesbare " + "Zahlungsreferenz, angeforderten Betrag und Währung, Lebenszyklusereignisse und " + "Abstimmungsnachweise. Ein Case, Workflow oder anderes Verfahren ruft die Fähigkeit " + "payments.requests mit eigener Quellreferenz und Wiederholungsschlüssel auf und bewahrt " + "die zurückgegebene Zahlungskennung auf, statt Payments-Tabellen zu schreiben. Der " + "Arbeitsbereich zeigt angeforderte und bezahlte Verpflichtungen mit Quelle, Fälligkeit " + "oder Erfüllungszeit und Abstimmungsnachweis. Schreibberechtigte legen eine Anforderung im " + "geführten Dialog an. Abstimmungsberechtigte verwenden den getrennten folgenreichen Dialog, " + "der Betrag und Währung fixiert und eine externe Transaktionsreferenz sowie eine " + "mandantengleiche versionierte oder prüfsummengebundene EvidenceReference verlangt. Neu " + "laden erhält vorhandene Daten und kennzeichnet sie als veraltet, wenn die Aktualisierung " + "scheitert. Fehlende Anlege- oder Abstimmungsberechtigung bleibt mit erforderlicher " + "Berechtigung und zuständiger Administration sichtbar. Der erste unterstützte Zahlungseingang " + "ist die manuelle Abstimmung einer vollständigen Zahlung. Abweichung, doppelte Erfüllung unter " + "anderem Schlüssel, mandantenfremder Nachweis, Teilbetrag oder Zeitstempel ohne Zeitzone " + "scheitert geschlossen. Erfolgreiche Anforderungen und Abstimmungen fügen " + "Zahlungsereignisse an; API-Aktionen erzeugen bei installiertem Audit Nachweise. Es gibt keine " + "stille Korrektur: Storno, Erstattung, Teilzahlung, Online-Checkout, Anbieter-Callbacks, " + "Ledger-Buchung und XRechnung bleiben ausdrückliche zukünftige Abläufe." + ), + } + }, + structured_translation_version="1", + structured_translations={ + "de": { + "purpose": ( + "Eine exakte Zahlungsverpflichtung anlegen und nur anhand passender unveränderlicher Nachweise als bezahlt abstimmen." + ), + "prerequisites": [ + "Die handelnde Person darf Payments lesen; Anlegen und Abstimmen erfordern ihre jeweils eigenen Berechtigungen.", + "Das Quellverfahren liefert eine stabile mandantengleiche Referenz und einen wiederholungssicheren Anforderungsschlüssel.", + "Für die manuelle Abstimmung liegt eine mandantengleiche versionierte oder prüfsummengebundene Nachweisreferenz vor.", + ], + "steps": [ + "Betrag, Währung, Quellreferenz, Fälligkeit und menschenlesbare Zahlungsreferenz im geführten Dialog anlegen.", + "Die zurückgegebene Zahlungskennung im aufrufenden Case oder Workflow bewahren, statt Payments-Tabellen zu schreiben.", + "Die Verpflichtung vor der Abstimmung neu laden, wenn der Arbeitsbereich veraltete Daten meldet.", + "Externe Transaktionsreferenz, exakte Erfüllungszeit und unveränderliche Nachweisreferenz angeben.", + "Vollständigen Betrag und Währung bestätigen; Abweichung, Duplikat, Teilbetrag oder mandantenfremder Nachweis scheitert geschlossen.", + "Nach dem Wechsel auf bezahlt die angefügten Lebenszyklus- und Auditnachweise prüfen.", + ], + "limitations": [ + "Nur vollständige manuelle Abstimmung wird unterstützt; Teilzahlung, Erstattung, Storno und Korrektur benötigen zukünftige gesteuerte Abläufe.", + "Online-Checkout, Callbacks, Ledger-Buchung, XRechnung und eine Antragsteller-Zahlungsseite sind hier nicht implementiert.", + ], + "privacy_notes": [ + "Verfahrenskontext verwendet stabile Referenzen; Namen von Antragstellern, Bankverbindungen und übermittelte Formularwerte sind nicht erforderlich.", + "Der unveränderliche Nachweis bleibt Eigentum seines Anbieters; Payments speichert nur die typisierte EvidenceReference.", + ], + "consequence_classes": { + "request_payment": "Erzeugt eine dauerhafte Betrags- und Währungsverpflichtung sowie eine stabile Zahlungsreferenz für Antragsteller.", + "reconcile_manual": "Kennzeichnet die exakte Verpflichtung als bezahlt und fügt Nachweise an; eine zukünftige gesteuerte Anpassung ist zur Umkehr erforderlich.", + }, + "verification": [ + "Die bezahlte Verpflichtung bewahrt ursprünglichen Betrag, Währung, Zahlungskennung und menschenlesbare Referenz unverändert.", + "Die Abstimmung nennt externe Transaktion und unveränderliche Nachweisreferenz.", + "Wiederholungs- und Duplikatprüfungen belegen, dass eine externe Erfüllung keine widersprüchlichen Bezahltzustände erzeugt hat.", + ], + } }, ), ), diff --git a/tests/test_documentation.py b/tests/test_documentation.py new file mode 100644 index 0000000..d52c11f --- /dev/null +++ b/tests/test_documentation.py @@ -0,0 +1,31 @@ +from __future__ import annotations + +import unittest + +from govoplan_core.core.modules import ( + documentation_structured_translation_issues, + user_workflow_scope_condition_issues, +) +from govoplan_payments.backend.manifest import manifest + + +class PaymentsDocumentationTests(unittest.TestCase): + def test_public_topics_have_complete_german_reference_content(self) -> None: + self.assertEqual(2, len(manifest.documentation)) + for topic in manifest.documentation: + translation = topic.translations.get("de", {}) + self.assertTrue( + all(translation.get(key) for key in ("title", "summary", "body")) + ) + self.assertEqual((), documentation_structured_translation_issues(topic)) + + def test_documentation_has_scope_conditioned_workflow_and_reference(self) -> None: + kinds = {topic.metadata.get("kind") for topic in manifest.documentation} + self.assertIn("workflow", kinds) + self.assertIn("reference", kinds) + for topic in manifest.documentation: + self.assertEqual((), user_workflow_scope_condition_issues(topic)) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/test_payments.py b/tests/test_payments.py index cbc4dcd..65b9132 100644 --- a/tests/test_payments.py +++ b/tests/test_payments.py @@ -201,7 +201,7 @@ class PaymentTests(unittest.TestCase): self.assertEqual((), self.provider.list_payments(self.session, tenant_id="tenant-2")) def test_manifest_exposes_permission_bounded_operator_workspace(self) -> None: - self.assertEqual("0.1.20", manifest.version) + self.assertEqual("0.1.21", manifest.version) self.assertIsNotNone(manifest.frontend) assert manifest.frontend is not None self.assertEqual("@govoplan/payments-webui", manifest.frontend.package_name) diff --git a/webui/package.json b/webui/package.json index 715c6ed..0a3ff26 100644 --- a/webui/package.json +++ b/webui/package.json @@ -1,6 +1,6 @@ { "name": "@govoplan/payments-webui", - "version": "0.1.20", + "version": "0.1.21", "private": true, "type": "module", "main": "src/index.ts",