Registratie: answer-driven required document uploads

Categories stay server-owned (ADR-0001); the FE sends its answers to
/uploads/categories and re-fetches reactively when they change:
- Diplomabewijs required only for a handmatig diploma (DUO is verified digitally;
  nothing required before a diploma is chosen).
- Bewijs Nederlandse taalvaardigheid required only when the applicant answers "ja"
  to the nl-taalvaardigheid (B2) policy question.
CategoriesFor(wizardId, diplomaHerkomst, taalvaardigheid) decides; Find uses the
maximal set so uploads still validate. CategoriesLoaded drops orphaned uploads
when a category disappears. Also: show the foreground-only upload banner only when
there is at least one category.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-01 18:35:37 +02:00
parent 9822a45d9a
commit 6a61c179cd
12 changed files with 145 additions and 24 deletions

View File

@@ -39,6 +39,9 @@ const KANALEN = [
{ value: 'post', label: $localize`:@@registratie.kanaalPost:Post` },
];
const HANDMATIG = '__handmatig__'; // sentinel option: "my diploma isn't listed"
/** The server-owned geldigheidsvraag whose "ja" answer requires a Dutch-taalvaardigheid
upload (proof of the confirmed B2 level). Stable id shared with the backend. */
const NL_TAALVAARDIGHEID_VRAAG = 'nl-taalvaardigheid';
/** Organism: the BIG-registration wizard. All state lives in one signal driven by
the pure `reduce` (registratie-wizard.machine.ts). The BRP address prefills the
@@ -213,6 +216,13 @@ export class RegistratieWizardComponent {
wizardId: 'registratie',
getUpload: () => this.upload(),
dispatch: (msg) => this.dispatch({ tag: 'Upload', msg }),
// Required documents depend on answers (server decides): a diploma upload only for a
// manual diploma; a Dutch-taalvaardigheid upload only once the applicant confirms
// ("ja") the B2 language requirement.
getCategoryParams: () => ({
diplomaHerkomst: this.draft().diplomaHerkomst,
taalvaardigheid: this.draft().antwoorden[NL_TAALVAARDIGHEID_VRAAG],
}),
});
// Backend draft-sync (replaces sessionStorage): create a Concept once the user has
// made progress, then debounced-sync the whole machine snapshot; resume by `?aanvraag`.
@@ -224,7 +234,7 @@ export class RegistratieWizardComponent {
const documentIds = deliveryRefs(s.upload).filter((r) => r.channel === 'digital' && r.documentId).map((r) => r.documentId!);
return { draft: s, stepIndex: s.cursor, stepCount: STEPS.length, documentIds };
},
onResume: (draft) => this.dispatch({ tag: 'Seed', state: (draft as RegistratieState | null) ?? initial }),
onResume: (draft) => this.dispatch({ tag: 'Seed', state: draft as RegistratieState }),
enabled: () => this.seed() === initial,
});
protected step = computed<StepId>(() => STEPS[Math.min(this.cursor(), STEPS.length - 1)]);