diff --git a/package.json b/package.json index f0cd366..4d1cd55 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@govoplan/projects", - "version": "0.1.18", + "version": "0.1.19", "private": true, "description": "GovOPlaN Projects domain module.", "type": "module", diff --git a/pyproject.toml b/pyproject.toml index 6f7764d..d943809 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,14 +4,14 @@ build-backend = "setuptools.build_meta" [project] name = "govoplan-projects" -version = "0.1.18" +version = "0.1.19" description = "GovOPlaN Projects domain module." readme = "README.md" requires-python = ">=3.12" license = { file = "LICENSE" } authors = [{ name = "GovOPlaN" }] dependencies = [ - "govoplan-core>=0.1.18", + "govoplan-core>=0.1.37", "govoplan-access>=0.1.18", ] diff --git a/src/govoplan_projects/backend/manifest.py b/src/govoplan_projects/backend/manifest.py index a405106..2021211 100644 --- a/src/govoplan_projects/backend/manifest.py +++ b/src/govoplan_projects/backend/manifest.py @@ -14,6 +14,7 @@ from govoplan_core.core.module_guards import ( ) from govoplan_core.core.modules import ( CapabilityDocumentation, + DocumentationCondition, DocumentationLink, DocumentationTopic, FrontendModule, @@ -50,7 +51,7 @@ from govoplan_projects.backend.service import ( MODULE_ID = "projects" MODULE_NAME = "Projects" -MODULE_VERSION = "0.1.18" +MODULE_VERSION = "0.1.19" READ_SCOPE = "projects:project:read" WRITE_SCOPE = "projects:project:write" ADMIN_SCOPE = "projects:project:admin" @@ -209,21 +210,23 @@ ROLE_TEMPLATES = ( DOCUMENTATION = ( DocumentationTopic( id="projects.module-boundary", - title="Projects module boundary", + title="Plan and coordinate projects", summary=( - "Portfolios, projects, versioned goals and outcomes, milestones, " - "dependencies, capacity, benefits, participants, status, and references." + "Coordinate portfolios, projects, milestones, participants, dependencies, " + "capacity assumptions, outcomes, and benefit reviews without absorbing work-item ownership." ), body=( - "Projects owns native project context. Tasks and Tickets own " - "actionable work, Cases owns formal procedures, and Connectors " - "owns OpenProject synchronization. Reporting owns measured indicators; " - "Risk Compliance owns risks and controls; Projects links those facts to " - "planning, change impact, and benefit review." + "Projects owns native portfolio and project context, immutable revisions of goals and intended outcomes, " + "milestones, participants, status, dependencies, capacity assumptions, benefit reviews, and governed resource references. " + "Create or revise the planning context, review its impact, and link provider-owned work and evidence without copying it. " + "Tasks and Tickets retain actionable work, Cases retains formal procedures, and Connectors retains OpenProject transport. " + "Reporting owns measured indicators and Risk Compliance owns risks and controls; Projects links those facts to planning, " + "change impact, and benefit review." ), layer="available", documentation_types=("admin", "user"), audience=("user", "operator", "module_admin", "product_owner"), + conditions=(DocumentationCondition(required_scopes=(READ_SCOPE,)),), related_modules=OPTIONAL_DEPENDENCIES, links=( DocumentationLink( @@ -238,23 +241,105 @@ DOCUMENTATION = ( ), ), metadata={ - "seed": True, - "domain_objects": [ - "portfolio", - "project", - "milestone", - "versioned goal and intended outcome", - "dependency and capacity assumption", - "benefit review", - "project participant", - "project resource link", - "external project reference", + "kind": "workflow", + "help_contexts": [ + "projects.workspace", + "projects.project", + "projects.milestone", ], - "first_slice": ( - "Implement project and portfolio identity, status, milestones, " - "participants, outcome/benefit intent, dependency/resource links, " - "and OpenProject reference mapping." + "purpose": ( + "Maintain a revisioned planning context and connect provider-owned work, evidence, and measurements to it." ), + "prerequisites": [ + "The actor can read the project; revisions require project write access.", + "Referenced work, cases, files, risks, reports, and external systems remain authorized by their owner providers.", + ], + "steps": [ + "Select a portfolio or create the project identity, status, participants, and planning horizon.", + "Record versioned goals, intended outcomes, milestones, dependencies, and capacity assumptions.", + "Link Tasks, Tickets, Cases, files, risks, controls, reports, or external project references without copying owner data.", + "Review status, change impact, milestone progress, and benefit evidence before appending a revision.", + "Use owner-module links to manage actionable work or formal procedures in their authoritative surface.", + ], + "fields": { + "portfolio": "Groups projects for planning and oversight without changing project authority.", + "project": "The native revisioned context for outcome, status, participation, and resource links.", + "milestone": "A dated project checkpoint, not an actionable Task or formal Case state.", + "participant": "An explicit project access grant and role, independent of object ownership elsewhere.", + "external_reference": "A governed link to a provider-owned project or resource, including OpenProject mappings.", + }, + "limitations": [ + "Projects does not own Tasks, Tickets, Cases, source measurements, risks, controls, files, or external synchronization transport.", + "A linked object remains unavailable when its owner provider denies current access.", + ], + "operational_consequences": { + "revise_plan": "Appends a new planning revision while preserving prior goals, status, and evidence links.", + "change_participation": "Changes project access only and does not grant access to linked provider-owned objects.", + "link_external_resource": "Stores a governed reference; synchronization remains a Connector responsibility.", + }, + "verification": [ + "The project revision names its portfolio, status, participants, milestones, and intended outcomes.", + "Every linked resource identifies its owner module or external provider and remains independently authorized.", + "Benefit and change-impact review preserves the revision and evidence references used for the decision.", + ], + }, + translations={ + "de": { + "title": "Projekte planen und koordinieren", + "summary": ( + "Portfolios, Projekte, Meilensteine, Beteiligte, Abhängigkeiten, Kapazitätsannahmen, " + "Ergebnisse und Nutzenprüfungen koordinieren, ohne die Eigentümerschaft an Arbeitselementen zu übernehmen." + ), + "body": ( + "Projects führt den nativen Portfolio- und Projektkontext, unveränderliche Revisionen von Zielen und " + "beabsichtigten Ergebnissen, Meilensteine, Beteiligte, Status, Abhängigkeiten, Kapazitätsannahmen, " + "Nutzenprüfungen und gesteuerte Ressourcenreferenzen. Planungskontext wird angelegt oder revidiert, " + "seine Auswirkung geprüft und anbietergeführte Arbeit und Nachweise werden verknüpft, ohne sie zu kopieren. " + "Tasks und Tickets behalten ausführbare Arbeit, Cases formelle Verfahren und Connectors den OpenProject-Transport. " + "Reporting führt gemessene Kennzahlen und Risk Compliance Risiken und Kontrollen; Projects verknüpft diese Fakten " + "mit Planung, Änderungsfolgen und Nutzenprüfung." + ), + } + }, + structured_translation_version="1", + structured_translations={ + "de": { + "purpose": ( + "Einen revisionierten Planungskontext pflegen und anbietergeführte Arbeit, Nachweise und Messwerte damit verknüpfen." + ), + "prerequisites": [ + "Die handelnde Person darf das Projekt lesen; Revisionen erfordern Projektschreibberechtigung.", + "Verknüpfte Arbeit, Verfahren, Dateien, Risiken, Berichte und externe Systeme bleiben durch ihre Eigentümeranbieter autorisiert.", + ], + "steps": [ + "Ein Portfolio auswählen oder Projektidentität, Status, Beteiligte und Planungshorizont anlegen.", + "Revisionierte Ziele, beabsichtigte Ergebnisse, Meilensteine, Abhängigkeiten und Kapazitätsannahmen erfassen.", + "Tasks, Tickets, Cases, Dateien, Risiken, Kontrollen, Berichte oder externe Projektreferenzen verknüpfen, ohne Eigentümerdaten zu kopieren.", + "Status, Änderungsfolgen, Meilensteinfortschritt und Nutzennachweise prüfen, bevor eine Revision angefügt wird.", + "Verknüpfungen zu Eigentümermodulen verwenden, um ausführbare Arbeit oder formelle Verfahren in deren führender Oberfläche zu verwalten.", + ], + "fields": { + "portfolio": "Gruppiert Projekte für Planung und Aufsicht, ohne die Projektzuständigkeit zu verändern.", + "project": "Der native revisionierte Kontext für Ergebnis, Status, Beteiligung und Ressourcenverknüpfungen.", + "milestone": "Ein datierter Projektprüfpunkt, kein ausführbarer Task und kein formeller Case-Status.", + "participant": "Eine ausdrückliche Projektzugriffsfreigabe und Rolle, unabhängig von Eigentümerschaft in anderen Modulen.", + "external_reference": "Eine gesteuerte Verknüpfung zu einem anbietergeführten Projekt oder einer Ressource, einschließlich OpenProject-Zuordnungen.", + }, + "limitations": [ + "Projects führt weder Tasks, Tickets, Cases, Quellmesswerte, Risiken, Kontrollen, Dateien noch externen Synchronisationstransport.", + "Ein verknüpftes Objekt bleibt unverfügbar, wenn sein Eigentümeranbieter den aktuellen Zugriff verweigert.", + ], + "operational_consequences": { + "revise_plan": "Fügt eine neue Planungsrevision an und bewahrt frühere Ziele, Status und Nachweisverknüpfungen.", + "change_participation": "Ändert nur den Projektzugriff und gewährt keinen Zugriff auf verknüpfte anbietergeführte Objekte.", + "link_external_resource": "Speichert eine gesteuerte Referenz; Synchronisation bleibt Aufgabe eines Connectors.", + }, + "verification": [ + "Die Projektrevision nennt Portfolio, Status, Beteiligte, Meilensteine und beabsichtigte Ergebnisse.", + "Jede verknüpfte Ressource nennt Eigentümermodul oder externen Anbieter und bleibt unabhängig autorisiert.", + "Nutzen- und Änderungsfolgenprüfung bewahrt die für die Entscheidung verwendete Revision und Nachweisreferenzen.", + ], + } }, ), ) @@ -434,6 +519,7 @@ manifest = ModuleManifest( related_modules=("core", "access", "tasks", "audit"), order=90, metadata={ + "kind": "reference", "help_contexts": [ "projects.workspace", "privacy.data-subject-requests", @@ -444,6 +530,35 @@ manifest = ModuleManifest( "retain_project_attribution": "Preserves immutable lifecycle accountability.", }, }, + translations={ + "de": { + "title": "Datenschutzanfragen zu Projekten", + "summary": ( + "Projektbeteiligung und verantwortbare Aktivitäten ohne Projektinhalte exportieren." + ), + "body": ( + "Projects gleicht exakte Konto- und Identitätsfreigaben sowie eine ausdrücklich angegebene " + "Funktionszuweisungsreferenz ab. Exakte Konto-, Identitäts- und Mitgliedschaftskennungen für " + "Anlage- und Lebenszykluszuschreibung werden getrennt ermittelt. Suchen können auf ein Projekt, " + "Portfolio oder einen Meilenstein eingegrenzt werden; eine Objektkennung allein begründet niemals " + "eine betroffene Person. Beteiligungsexporte enthalten Rolle, Berechtigungen, Aktivstatus und " + "Quellrevision. Titel, Suchtext, Planungs- und Ereignisinhalte, Anforderungsprüfsummen, " + "Idempotenzschlüssel und unbeteiligte Mitglieder bleiben ausgeschlossen. Die Entfernung einer " + "Beteiligung erfordert eine manuelle Prüfung von Zugriff und Eigentumsfortbestand; unveränderliche " + "Zuschreibungen zu Anlage, Revision und Ereignissen bleiben als Steuerungsnachweise erhalten." + ), + } + }, + structured_translation_version="1", + structured_translations={ + "de": { + "consequence_classes": { + "export_project_membership": "Gibt exakte Betroffenenfreigaben ohne unbeteiligte Mitglieder zurück.", + "review_membership_removal": "Erfordert eine Prüfung von Eigentümerschaft und Zugriffskontinuität.", + "retain_project_attribution": "Bewahrt unveränderliche Lebenszyklusverantwortung auf.", + } + } + }, ), *DOCUMENTATION, ), diff --git a/tests/test_documentation.py b/tests/test_documentation.py new file mode 100644 index 0000000..2d36d8a --- /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_projects.backend.manifest import manifest + + +class ProjectsDocumentationTests(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/webui/package.json b/webui/package.json index 83aea9f..fe70b12 100644 --- a/webui/package.json +++ b/webui/package.json @@ -1,6 +1,6 @@ { "name": "@govoplan/projects-webui", - "version": "0.1.18", + "version": "0.1.19", "private": true, "type": "module", "main": "src/index.ts", diff --git a/webui/src/styles/projects.css b/webui/src/styles/projects.css index 00a033a..74f1f9e 100644 --- a/webui/src/styles/projects.css +++ b/webui/src/styles/projects.css @@ -4,6 +4,11 @@ overflow: hidden; } +.projects-page :where(button, input, select, textarea, a[href]):focus-visible { + outline: var(--focus-outline); + outline-offset: 2px; +} + .projects-search { flex: 1 1 560px; }