feat: add resident application status portal

This commit is contained in:
2026-08-19 12:33:34 +02:00
parent a15f599756
commit 89a142ead1
8 changed files with 512 additions and 3 deletions
+81 -2
View File
@@ -1,5 +1,9 @@
from __future__ import annotations
from govoplan_core.core.application_status import (
CAPABILITY_APPLICATION_STATUS_PROJECTION,
application_status_projection_provider,
)
from govoplan_core.core.institutional import (
CAPABILITY_SERVICE_AVAILABILITY,
CAPABILITY_SERVICE_DEFINITIONS,
@@ -19,6 +23,7 @@ from govoplan_core.core.modules import (
NavItem,
PermissionDefinition,
ProductAreaContribution,
PublicFrontendRoute,
RoleTemplate,
)
from govoplan_core.core.provider_governance import (
@@ -52,6 +57,27 @@ def _router(context: ModuleContext):
return router
def _public_tenant_resolver(request: object, session: object) -> str | None:
path = str(getattr(getattr(request, "url", None), "path", ""))
if "/portal/status/" not in path:
return None
path_params = getattr(request, "path_params", {})
tracking_id = str(
path_params.get("trackingId")
or path_params.get("tracking_id")
or path.rsplit("/", 1)[-1]
or ""
).strip()
if not tracking_id:
return None
app = getattr(request, "app", None)
registry = getattr(getattr(app, "state", None), "govoplan_registry", None)
provider = application_status_projection_provider(registry)
if provider is None:
return None
return provider.tenant_id_for_tracking_id(session, tracking_id=tracking_id)
manifest = ModuleManifest(
id=MODULE_ID,
name="Portal",
@@ -70,6 +96,7 @@ manifest = ModuleManifest(
CAPABILITY_SERVICE_AVAILABILITY,
*SERVICE_LAUNCH_CAPABILITIES,
CAPABILITY_POSTBOX_PORTAL,
CAPABILITY_APPLICATION_STATUS_PROJECTION,
),
permissions=(
PermissionDefinition(
@@ -113,11 +140,18 @@ manifest = ModuleManifest(
version_max_exclusive="0.2.0",
optional=True,
),
ModuleInterfaceRequirement(
name=CAPABILITY_APPLICATION_STATUS_PROJECTION,
version_min="1.0.0",
version_max_exclusive="2.0.0",
optional=True,
),
),
capability_factories={
CAPABILITY_PORTAL_SERVICE_DIRECTORY: _service_directory,
},
route_factory=_router,
public_tenant_resolver=_public_tenant_resolver,
nav_items=(
NavItem(
path="/portal",
@@ -138,6 +172,13 @@ manifest = ModuleManifest(
order=25,
),
),
public_routes=(
PublicFrontendRoute(
path="/portal/status/:trackingId",
component="PortalStatusPage",
order=12,
),
),
nav_items=(
NavItem(
path="/portal",
@@ -173,6 +214,13 @@ manifest = ModuleManifest(
label="Service directory",
order=20,
),
ViewSurface(
id="portal.application-status",
module_id=MODULE_ID,
kind="route",
label="Applicant status",
order=30,
),
),
),
capability_documentation={
@@ -211,6 +259,36 @@ manifest = ModuleManifest(
metadata={"kind": "guide", "help_contexts": ["portal.postboxes"]},
order=20,
),
DocumentationTopic(
id="portal.application-status",
title="Track an application",
summary="View the bounded public lifecycle through the access profile configured for the exact submitted Form revision.",
body=(
"Portal presents the applicant status projection owned by Forms Runtime. The service administrator chooses authenticated-only access, a short-lived email link, or a permanent public bearer link for each exact published Form revision. "
"Authenticated access is checked against the applicant account. Email-link requests return the same response for matching and non-matching details, revoke the previous link when a new one is sent, and depend on configured Notifications and Mail delivery. A permanent link does not expire or require sign-in and must therefore be handled like a bearer secret. "
"The page exposes only lifecycle states, update times, a tracking identifier, and the submission receipt. It does not display Form values, evidence, internal notes, actors, decision reasoning, or module handoff details."
),
layer="configured",
documentation_types=("admin", "user"),
audience=("public", "user", "operator", "module_admin"),
links=(
DocumentationLink(
label="Applicant status",
href="/portal/status/:trackingId",
kind="runtime",
),
),
related_modules=("forms_runtime", "notifications", "mail"),
metadata={
"kind": "guide",
"help_contexts": ["portal.application-status"],
"privacy_notes": [
"Possession of a permanent status link grants access to the bounded projection until its owner suspends the policy or revokes the grant.",
"The email request surface does not reveal whether the tracking identifier, email address, provider, or delivery attempt matched."
],
},
order=30,
),
DocumentationTopic(
id="portal.service-directory",
title="Service directory",
@@ -258,9 +336,10 @@ manifest = ModuleManifest(
known_limits=(
"Portal does not persist service definitions; the Services provider remains authoritative.",
"Case, Forms Runtime, and Workflow Engine own launch effects. Portal keeps entries unavailable whenever the selected owner capability is absent.",
"Forms Runtime owns applicant-status access, redaction, and timeline semantics; Portal only presents that projection. Payment and decision-document actions are not yet included in the first status surface.",
),
owned_concepts=("service discovery", "service presentation", "channel entry"),
non_owned_concepts=("institutional service definition", "case lifecycle"),
owned_concepts=("service discovery", "service presentation", "channel entry", "applicant status presentation"),
non_owned_concepts=("institutional service definition", "case lifecycle", "applicant status access decision"),
reference_packages=("product.service-to-decision",),
documentation=ModuleArchitectureDocumentation(
security=("docs/SERVICE_DIRECTORY_CONCEPT.md",),