Mijn aanvragen (E): two-flow submit through the aanvraag + all-wizard persistence
All three wizards now submit through the backend aanvraag lifecycle, so a
submitted Concept actually transitions (dashboard shows it correctly in F).
- blockActions(status) (domain + spec): the pure per-status action decision
(Concept → resume/cancel; In behandeling → viewDocuments; resolved → none).
- createDraftSync.submit(): ensure the Concept exists, then
POST /applications/{id}/submit; folded into a Result like the old commands.
- registratie: submit via draftSync (duo → auto, handmatig → manual pending — the
old 422 path is gone from the wizard).
- intake + herregistratie: adopt createDraftSync (persistence + resume-by-link);
intake retires sessionStorage `intake-v3`; herregistratie gains persistence.
Both submit through the aanvraag too. hasProgress added to each machine (+spec).
- Delete now-dead submit-registratie/submit-intake/submit-herregistratie commands.
Deferred: the old /registrations, /intakes, /herregistraties backend endpoints +
RejectRegistratie are now unused by the FE but still present (+ tested) — retiring
them cascades into backend test rewrites, so it's a focused follow-up cleanup.
Gates green: vitest 128, lint, build; backend unchanged (dotnet 56).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,8 @@
|
||||
import { DestroyRef, effect, inject } from '@angular/core';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { Result } from '@shared/kernel/fp';
|
||||
import { runSubmit, SUBMIT_FAILED } from '@shared/application/submit';
|
||||
import { SubmitApplicationRequest, SubmitApplicationResponse } from '@shared/infrastructure/api-client';
|
||||
import { AanvraagType } from '@registratie/domain/aanvraag';
|
||||
import { ApplicationsAdapter } from '@registratie/infrastructure/applications.adapter';
|
||||
|
||||
@@ -94,6 +97,13 @@ export function createDraftSync(deps: DraftSyncDeps) {
|
||||
.catch(() => deps.onResume(null)); // unknown/deleted id → start fresh
|
||||
},
|
||||
|
||||
/** Submit through the aanvraag lifecycle: ensure the Concept exists, then
|
||||
`POST /applications/{id}/submit` (server sets autoApprovable + transitions).
|
||||
Folded into a Result like the old submit-* commands. */
|
||||
submit(body: SubmitApplicationRequest): Promise<Result<string, SubmitApplicationResponse>> {
|
||||
return runSubmit(async () => adapter.submit(await ensureId(), body), SUBMIT_FAILED);
|
||||
},
|
||||
|
||||
/** Detach from the current Concept (a new one is created on next progress) and
|
||||
drop the `?aanvraag` link. Used when the wizard restarts. */
|
||||
reset() {
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { submitRegistratie } from './submit-registratie';
|
||||
import { ApiClient } from '@shared/infrastructure/api-client';
|
||||
import { ValidRegistratie } from '@registratie/domain/registratie-wizard.machine';
|
||||
|
||||
// Mocked at the client boundary: the rule itself lives server-side now, so these
|
||||
// tests only assert that the command maps the client's response onto a Result.
|
||||
const data = { diplomaHerkomst: 'duo' } as unknown as ValidRegistratie;
|
||||
|
||||
describe('submitRegistratie', () => {
|
||||
it('returns ok with the server reference on success', async () => {
|
||||
const client = { registrations: async () => ({ referentie: 'BIG-2026-123456' }) } as unknown as ApiClient;
|
||||
const r = await submitRegistratie(client, data);
|
||||
expect(r).toEqual({ ok: true, value: 'BIG-2026-123456' });
|
||||
});
|
||||
|
||||
it('returns err with the ProblemDetails detail when the server rejects (422)', async () => {
|
||||
const client = {
|
||||
registrations: async () => {
|
||||
throw { detail: 'Handmatig diploma kan niet automatisch worden geverifieerd.', status: 422 };
|
||||
},
|
||||
} as unknown as ApiClient;
|
||||
const r = await submitRegistratie(client, data);
|
||||
expect(r.ok).toBe(false);
|
||||
expect(r).toMatchObject({ error: expect.stringContaining('Handmatig diploma') });
|
||||
});
|
||||
});
|
||||
@@ -1,17 +0,0 @@
|
||||
import { Result } from '@shared/kernel/fp';
|
||||
import { ValidRegistratie } from '@registratie/domain/registratie-wizard.machine';
|
||||
import { ApiClient } from '@shared/infrastructure/api-client';
|
||||
import { runSubmit, SUBMIT_FAILED } from '@shared/application/submit';
|
||||
|
||||
/**
|
||||
* Command: POST the completed registration to the backend (`/api/registrations`),
|
||||
* which re-validates and decides. The rule that a manually entered diploma cannot
|
||||
* be auto-verified lives server-side (surfaced as a 422 by `runSubmit`). On
|
||||
* success it yields the server-generated confirmation reference (PRD §9).
|
||||
*/
|
||||
export function submitRegistratie(client: ApiClient, data: ValidRegistratie): Promise<Result<string, string>> {
|
||||
return runSubmit(async () => {
|
||||
const res = await client.registrations({ diplomaHerkomst: data.diplomaHerkomst, documents: data.documents });
|
||||
return res.referentie ?? '';
|
||||
}, SUBMIT_FAILED);
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { blockActions } from './block-actions';
|
||||
|
||||
describe('blockActions', () => {
|
||||
it('a Concept can be resumed or cancelled', () => {
|
||||
expect(blockActions({ tag: 'Concept', stepIndex: 1, stepCount: 3 })).toEqual(['resume', 'cancel']);
|
||||
});
|
||||
|
||||
it('an in-behandeling aanvraag only exposes its documents', () => {
|
||||
expect(blockActions({ tag: 'InBehandeling', referentie: 'BIG-1', manual: true })).toEqual(['viewDocuments']);
|
||||
});
|
||||
|
||||
it('resolved aanvragen have no actions', () => {
|
||||
expect(blockActions({ tag: 'Goedgekeurd', referentie: 'BIG-1' })).toEqual([]);
|
||||
expect(blockActions({ tag: 'Afgewezen', referentie: 'BIG-1', reden: 'x' })).toEqual([]);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,18 @@
|
||||
import { AanvraagStatus } from './aanvraag';
|
||||
|
||||
/** What a dashboard "Mijn aanvragen" block offers per status. The badge itself
|
||||
follows directly from `status.tag` (the UI maps tag → colour + label), so this
|
||||
pure function owns only the *actions* decision. */
|
||||
export type BlockAction = 'resume' | 'cancel' | 'viewDocuments';
|
||||
|
||||
export function blockActions(status: AanvraagStatus): BlockAction[] {
|
||||
switch (status.tag) {
|
||||
case 'Concept':
|
||||
return ['resume', 'cancel'];
|
||||
case 'InBehandeling':
|
||||
return ['viewDocuments'];
|
||||
case 'Goedgekeurd':
|
||||
case 'Afgewezen':
|
||||
return [];
|
||||
}
|
||||
}
|
||||
@@ -28,9 +28,7 @@ import {
|
||||
hasProgress,
|
||||
STEPS,
|
||||
} from '@registratie/domain/registratie-wizard.machine';
|
||||
import { submitRegistratie } from '@registratie/application/submit-registratie';
|
||||
import { createDraftSync } from '@registratie/application/draft-sync';
|
||||
import { ApiClient } from '@shared/infrastructure/api-client';
|
||||
import { DocumentUploadComponent } from '@shared/ui/upload/document-upload/document-upload.component';
|
||||
import { createUploadController } from '@shared/upload/upload-controller';
|
||||
import { UploadState, initialUpload, deliveryRefs } from '@shared/upload/upload.machine';
|
||||
@@ -185,7 +183,6 @@ const HANDMATIG = '__handmatig__'; // sentinel option: "my diploma isn't listed"
|
||||
export class RegistratieWizardComponent {
|
||||
private brp = inject(BrpAdapter);
|
||||
private duo = inject(DuoAdapter);
|
||||
private apiClient = inject(ApiClient);
|
||||
private store = createStore<RegistratieState, RegistratieMsg>(initial, reduce);
|
||||
|
||||
protected adresRes = this.brp.adresResource();
|
||||
@@ -375,13 +372,13 @@ export class RegistratieWizardComponent {
|
||||
this.adresRes.reload();
|
||||
}
|
||||
|
||||
/** The effect: when we enter Indienen, call the backend, then dispatch the
|
||||
outcome (success carries the confirmation reference). */
|
||||
/** The effect: when we enter Indienen, submit through the aanvraag lifecycle
|
||||
(duo → auto-approve, handmatig → manual), then dispatch the outcome. */
|
||||
private async runIfIndienen() {
|
||||
const s = this.state();
|
||||
if (s.tag !== 'Indienen') return;
|
||||
const r = await submitRegistratie(this.apiClient, s.data);
|
||||
if (r.ok) this.dispatch({ tag: 'SubmitConfirmed', referentie: r.value });
|
||||
const r = await this.draftSync.submit({ diplomaHerkomst: s.data.diplomaHerkomst, documents: s.data.documents });
|
||||
if (r.ok) this.dispatch({ tag: 'SubmitConfirmed', referentie: r.value.referentie ?? '' });
|
||||
else this.dispatch({ tag: 'SubmitFailed', error: r.error });
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user