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:
eho
2026-07-01 14:25:02 +02:00
co-authored by Claude Opus 4.8
parent 6db7f1e673
commit 9f217abe19
15 changed files with 136 additions and 144 deletions
@@ -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);
}