Surface exact service journey filing

This commit is contained in:
2026-08-06 12:42:20 +02:00
parent df51bb1787
commit 011c3880c8
3 changed files with 67 additions and 9 deletions
+4 -3
View File
@@ -27,8 +27,8 @@ packages tracked by Records #2-#6:
valid/recorded time, purpose capture, institutional context, and search; valid/recorded time, purpose capture, institutional context, and search;
- a full-height eAkte workspace with file plan, list, details, chronology, - a full-height eAkte workspace with file plan, list, details, chronology,
temporal status, create/edit, and filing actions; temporal status, create/edit, and filing actions;
- a provider-neutral Core filing contract with exact Files-version and - a provider-neutral Core filing contract with exact Files-version,
Cases-revision providers. Cases-revision, Forms-submission, and formal-Decision providers;
- governed close/reopen transitions, class-bound retention calculation, - governed close/reopen transitions, class-bound retention calculation,
effective-dated holds, appraisal, evidence-bound disposition proposals, and effective-dated holds, appraisal, evidence-bound disposition proposals, and
mandatory independent approval before finalization; mandatory independent approval before finalization;
@@ -224,7 +224,8 @@ available in an evidence/details view.
1. Persist file plans, record classes, records, record items, exact references, 1. Persist file plans, record classes, records, record items, exact references,
chronology, permissions, temporal reads, institutional context, and search. chronology, permissions, temporal reads, institutional context, and search.
2. Integrate filing from Cases, Forms Runtime, Decisions, Campaign/Postbox, 2. Integrate filing from Cases, Forms Runtime, Decisions, Campaign/Postbox,
Files, and Reporting. Files, and Reporting. **Cases, Forms Runtime, Decisions, and Files are
implemented; Campaign/Postbox and Reporting remain.**
3. Add closure, retention calculation, holds, appraisal, and reviewed 3. Add closure, retention calculation, holds, appraisal, and reviewed
disposition without destructive provider effects. **Implemented.** disposition without destructive provider effects. **Implemented.**
4. Add native transfer packages and one target-tested xdomea/archive provider. 4. Add native transfer packages and one target-tested xdomea/archive provider.
+59 -6
View File
@@ -58,6 +58,14 @@ import {
const EMPTY_CATALOG: RecordCatalog = { file_plan: [], classes: [] }; const EMPTY_CATALOG: RecordCatalog = { file_plan: [], classes: [] };
const PAGE_SIZE = 50; const PAGE_SIZE = 50;
type FilingPreset = {
sourceModule: string;
resourceType: string;
resourceId: string;
sourceRevision: string;
sourceLabel: string;
};
export default function RecordsPage({ settings, auth }: PlatformRouteContext) { export default function RecordsPage({ settings, auth }: PlatformRouteContext) {
const [searchParams, setSearchParams] = useSearchParams(); const [searchParams, setSearchParams] = useSearchParams();
const { selection, selectionKey, isDefault: temporalIsDefault } = useTemporalDataContext(); const { selection, selectionKey, isDefault: temporalIsDefault } = useTemporalDataContext();
@@ -86,6 +94,7 @@ export default function RecordsPage({ settings, auth }: PlatformRouteContext) {
const canWrite = auth.scopes.includes("records:workspace:write") || const canWrite = auth.scopes.includes("records:workspace:write") ||
auth.scopes.includes("records:workspace:admin"); auth.scopes.includes("records:workspace:admin");
const canAdmin = auth.scopes.includes("records:workspace:admin"); const canAdmin = auth.scopes.includes("records:workspace:admin");
const filingPreset = useMemo(() => filingPresetFrom(searchParams), [searchParams]);
const reload = useCallback(() => setReloadKey((value) => value + 1), []); const reload = useCallback(() => setReloadKey((value) => value + 1), []);
@@ -267,6 +276,13 @@ export default function RecordsPage({ settings, auth }: PlatformRouteContext) {
</div> </div>
{error && <DismissibleAlert tone="danger" resetKey={error}>{error}</DismissibleAlert>} {error && <DismissibleAlert tone="danger" resetKey={error}>{error}</DismissibleAlert>}
{filingPreset &&
<DismissibleAlert tone="info" dismissible={false} compact>
{i18nMessage("i18n:govoplan-records.source_ready", {
value0: filingPreset.sourceLabel || "An exact source revision"
})} {i18nMessage("i18n:govoplan-records.select_destination")}
</DismissibleAlert>
}
<div className="records-workspace"> <div className="records-workspace">
<aside className="records-file-plan" data-help-context-id="records.file-plan"> <aside className="records-file-plan" data-help-context-id="records.file-plan">
<div className="records-pane-heading"> <div className="records-pane-heading">
@@ -399,9 +415,11 @@ export default function RecordsPage({ settings, auth }: PlatformRouteContext) {
record={detail?.record ?? null} record={detail?.record ?? null}
volumes={detail?.volumes ?? []} volumes={detail?.volumes ?? []}
providers={sources} providers={sources}
preset={filingPreset}
onClose={() => setFileOpen(false)} onClose={() => setFileOpen(false)}
onSaved={() => { onSaved={() => {
setFileOpen(false); setFileOpen(false);
clearFilingPreset(searchParams, setSearchParams);
reload(); reload();
}} }}
/> />
@@ -691,6 +709,7 @@ function FilingDialog({
record, record,
volumes, volumes,
providers, providers,
preset,
onClose, onClose,
onSaved onSaved
}: { }: {
@@ -699,6 +718,7 @@ function FilingDialog({
record: RecordEntry | null; record: RecordEntry | null;
volumes: RecordDetail["volumes"]; volumes: RecordDetail["volumes"];
providers: RecordSourceProvider[]; providers: RecordSourceProvider[];
preset: FilingPreset | null;
onClose: () => void; onClose: () => void;
onSaved: () => void; onSaved: () => void;
}) { }) {
@@ -715,16 +735,23 @@ function FilingDialog({
useEffect(() => { useEffect(() => {
if (!open) return; if (!open) return;
const first = providers[0]; const requested = preset
? providers.find((item) => item.source_module === preset.sourceModule)
: undefined;
const first = requested ?? providers[0];
setSourceModule(first?.source_module ?? ""); setSourceModule(first?.source_module ?? "");
setResourceType(first?.resource_types[0] ?? ""); setResourceType(
setResourceId(""); requested?.resource_types.includes(preset?.resourceType ?? "")
setSourceRevision(""); ? preset!.resourceType
: first?.resource_types[0] ?? ""
);
setResourceId(requested ? preset?.resourceId ?? "" : "");
setSourceRevision(requested ? preset?.sourceRevision ?? "" : "");
setVolumeId(""); setVolumeId("");
setPurpose(record?.purpose ?? ""); setPurpose(record?.purpose ?? "");
setFilingReason(""); setFilingReason(requested && preset?.sourceLabel ? `File ${preset.sourceLabel}.` : "");
setError(""); setError("");
}, [open, providers, record?.purpose]); }, [open, preset, providers, record?.purpose]);
useEffect(() => { useEffect(() => {
if (provider?.resource_types.includes(resourceType)) return; if (provider?.resource_types.includes(resourceType)) return;
@@ -862,6 +889,32 @@ function randomId(): string {
: `${Date.now()}-${Math.random().toString(16).slice(2)}`; : `${Date.now()}-${Math.random().toString(16).slice(2)}`;
} }
function filingPresetFrom(searchParams: URLSearchParams): FilingPreset | null {
const sourceModule = searchParams.get("sourceModule")?.trim() ?? "";
const resourceType = searchParams.get("resourceType")?.trim() ?? "";
const resourceId = searchParams.get("resourceId")?.trim() ?? "";
const sourceRevision = searchParams.get("sourceRevision")?.trim() ?? "";
if (!sourceModule || !resourceType || !resourceId || !sourceRevision) return null;
return {
sourceModule,
resourceType,
resourceId,
sourceRevision,
sourceLabel: (searchParams.get("sourceLabel")?.trim() ?? "").slice(0, 300)
};
}
function clearFilingPreset(
searchParams: URLSearchParams,
setSearchParams: ReturnType<typeof useSearchParams>[1]
) {
const next = new URLSearchParams(searchParams);
for (const key of ["sourceModule", "resourceType", "resourceId", "sourceRevision", "sourceLabel"]) {
next.delete(key);
}
setSearchParams(next, { replace: true });
}
function errorMessage(reason: unknown, fallback: string): string { function errorMessage(reason: unknown, fallback: string): string {
return reason instanceof Error && reason.message ? reason.message : fallback; return reason instanceof Error && reason.message ? reason.message : fallback;
} }
+4
View File
@@ -10,6 +10,8 @@ const en = {
"i18n:govoplan-records.record_detail": "Record detail", "i18n:govoplan-records.record_detail": "Record detail",
"i18n:govoplan-records.file_source": "File source revision", "i18n:govoplan-records.file_source": "File source revision",
"i18n:govoplan-records.record_count": "{value0} records", "i18n:govoplan-records.record_count": "{value0} records",
"i18n:govoplan-records.source_ready": "{value0} is ready for filing.",
"i18n:govoplan-records.select_destination": "Select the destination record, then choose File item.",
"Records": "Records", "Records": "Records",
"New record": "New record", "New record": "New record",
"Refresh": "Refresh", "Refresh": "Refresh",
@@ -161,6 +163,8 @@ const de: Record<keyof typeof en, string> = {
"i18n:govoplan-records.record_detail": "Aktendetails", "i18n:govoplan-records.record_detail": "Aktendetails",
"i18n:govoplan-records.file_source": "Quellrevision verakten", "i18n:govoplan-records.file_source": "Quellrevision verakten",
"i18n:govoplan-records.record_count": "{value0} Akten", "i18n:govoplan-records.record_count": "{value0} Akten",
"i18n:govoplan-records.source_ready": "{value0} kann veraktet werden.",
"i18n:govoplan-records.select_destination": "Wählen Sie die Zielakte und anschließend Objekt verakten.",
"Records": "Akten", "Records": "Akten",
"New record": "Neue Akte", "New record": "Neue Akte",
"Refresh": "Aktualisieren", "Refresh": "Aktualisieren",