feat: add resident application status portal
This commit is contained in:
@@ -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",),
|
||||
|
||||
Reference in New Issue
Block a user