refactor: rename Application → Aanvraag across the wire (Step 1/8)

The wire said Application, the domain said Aanvraag — one aggregate with
two names at every hop. Rename the backend DTOs and the /applications
route to /aanvragen, regenerate the typed client, and rename the frontend
adapter/store to match.

Renamed: ApplicationSummaryDto/DetailDto, CreateApplicationRequest,
SubmitApplicationRequest/Response → Aanvraag* equivalents;
ApplicationsAdapter/Store → AanvragenAdapter/Store;
applications.adapter.ts/applications.store.ts → aanvragen.*.

Left untouched: the admin Case/Zaak vocabulary (/admin/cases,
AdminCasesStore) — a separate read model, not part of this rename; the
internal BigRegister.Domain.Applications namespace and the Applications
EF table (renaming those needs a new EF migration, out of scope here).

Part of the dashboard-readability refactor (see the approved plan).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
eho
2026-09-04 14:33:16 +02:00
co-authored by Claude Opus 5
parent faad772f85
commit 194cccfd02
36 changed files with 400 additions and 412 deletions
@@ -1,8 +1,8 @@
import { TestBed } from '@angular/core/testing';
import { describe, it, expect, vi } from 'vitest';
import { SUBMIT_FAILED } from '@shared/application/submit';
import { ApplicationsAdapter } from '@registratie/infrastructure/applications.adapter';
import { ApplicationsStore } from './applications.store';
import { AanvragenAdapter } from '@registratie/infrastructure/aanvragen.adapter';
import { AanvragenStore } from './aanvragen.store';
const summary = (id: string) => ({
id,
@@ -13,20 +13,20 @@ const summary = (id: string) => ({
updatedAt: '2026-07-23T10:00:00Z',
});
function setup(adapter: Partial<ApplicationsAdapter>): ApplicationsStore {
function setup(adapter: Partial<AanvragenAdapter>): AanvragenStore {
TestBed.configureTestingModule({
providers: [{ provide: ApplicationsAdapter, useValue: adapter }],
providers: [{ provide: AanvragenAdapter, useValue: adapter }],
});
// The store's own constructor kicks off `load()` (dashboard revisit refresh) —
// give every test a `list` so that initial call has something to resolve.
return TestBed.inject(ApplicationsStore);
return TestBed.inject(AanvragenStore);
}
describe('ApplicationsStore', () => {
describe('AanvragenStore', () => {
it('loads and parses the list', async () => {
const store = setup({ list: () => Promise.resolve([summary('a'), summary('b')]) });
await store.load();
const s = store.applications();
const s = store.aanvragen();
expect(s.tag).toBe('Success');
expect(s.tag === 'Success' && s.value.map((a) => a.id)).toEqual(['a', 'b']);
});
@@ -41,7 +41,7 @@ describe('ApplicationsStore', () => {
await store.cancel('a');
expect(cancel).toHaveBeenCalledWith('a');
const s = store.applications();
const s = store.aanvragen();
expect(s.tag === 'Success' && s.value.map((a) => a.id)).toEqual(['b']);
expect(store.lastError()).toBeNull();
});
@@ -55,7 +55,7 @@ describe('ApplicationsStore', () => {
await store.load();
await store.cancel('a');
const s = store.applications();
const s = store.aanvragen();
expect(s.tag === 'Success' && s.value.map((a) => a.id)).toEqual(['a']); // reappears
expect(store.lastError()).toBe(SUBMIT_FAILED);
});
@@ -2,15 +2,12 @@ import { Injectable, inject, signal } from '@angular/core';
import { RemoteData } from '@shared/application/remote-data';
import { runSubmit, SUBMIT_FAILED } from '@shared/application/submit';
import { Aanvraag } from '@registratie/domain/aanvraag';
import {
ApplicationsAdapter,
parseApplications,
} from '@registratie/infrastructure/applications.adapter';
import { AanvragenAdapter, parseAanvragen } from '@registratie/infrastructure/aanvragen.adapter';
type Err = Error | undefined;
/**
* The dashboard's view of the user's applications (aanvragen) the backend is the
* The dashboard's view of the user's aanvragen the backend is the
* system of record (PRD 0001). One root singleton OWNS the list as a writable
* RemoteData signal (CLAUDE.md §3: change state only through methods). Cancel removes
* the row SYNCHRONOUSLY, so the block disappears deterministically no dependence on
@@ -20,11 +17,11 @@ type Err = Error | undefined;
* surfaces `lastError` on failure (RB-20).
*/
@Injectable({ providedIn: 'root' })
export class ApplicationsStore {
private adapter = inject(ApplicationsAdapter);
export class AanvragenStore {
private adapter = inject(AanvragenAdapter);
private state = signal<RemoteData<Err, Aanvraag[]>>({ tag: 'Loading' });
readonly applications = this.state.asReadonly();
readonly aanvragen = this.state.asReadonly();
/** Set on a failed cancel (RB-20): the optimistic removal already rolled back by
then, this is only the message for the alert the page renders above the list. */
@@ -40,7 +37,7 @@ export class ApplicationsStore {
async load() {
if (this.state().tag !== 'Success') this.state.set({ tag: 'Loading' });
try {
const parsed = parseApplications(await this.adapter.list());
const parsed = parseAanvragen(await this.adapter.list());
this.state.set(
parsed.ok
? { tag: 'Success', value: parsed.value }
@@ -1,7 +1,7 @@
import { TestBed } from '@angular/core/testing';
import { describe, it, expect, vi } from 'vitest';
import { SUBMIT_FAILED } from '@shared/application/submit';
import { ApplicationsAdapter } from '@registratie/infrastructure/applications.adapter';
import { AanvragenAdapter } from '@registratie/infrastructure/aanvragen.adapter';
import { AdminCasesStore } from './admin-cases.store';
const summary = (id: string) => ({
@@ -14,9 +14,9 @@ const summary = (id: string) => ({
owner: '19012345601',
});
function setup(adapter: Partial<ApplicationsAdapter>): AdminCasesStore {
function setup(adapter: Partial<AanvragenAdapter>): AdminCasesStore {
TestBed.configureTestingModule({
providers: [{ provide: ApplicationsAdapter, useValue: adapter }],
providers: [{ provide: AanvragenAdapter, useValue: adapter }],
});
return TestBed.inject(AdminCasesStore);
}
@@ -2,16 +2,13 @@ import { Injectable, inject, signal } from '@angular/core';
import { RemoteData } from '@shared/application/remote-data';
import { runSubmit, SUBMIT_FAILED } from '@shared/application/submit';
import { Aanvraag } from '@registratie/domain/aanvraag';
import {
ApplicationsAdapter,
parseApplications,
} from '@registratie/infrastructure/applications.adapter';
import { AanvragenAdapter, parseAanvragen } from '@registratie/infrastructure/aanvragen.adapter';
type Err = Error | undefined;
/**
* Admin view of ALL cases across owners (WP-36; `cases:manage`) — the back-office
* counterpart of the user-facing `ApplicationsStore`. Same shape: one root singleton
* counterpart of the user-facing `AanvragenStore`. Same shape: one root singleton
* owns the list as a writable RemoteData signal, delete removes the row synchronously
* (optimistic), goes through `runSubmit`, and rolls back plus surfaces `lastError` on
* failure (RB-20). Admin delete removes any case (any owner, submitted or not — the
@@ -19,7 +16,7 @@ type Err = Error | undefined;
*/
@Injectable({ providedIn: 'root' })
export class AdminCasesStore {
private adapter = inject(ApplicationsAdapter);
private adapter = inject(AanvragenAdapter);
private state = signal<RemoteData<Err, Aanvraag[]>>({ tag: 'Loading' });
readonly cases = this.state.asReadonly();
@@ -34,7 +31,7 @@ export class AdminCasesStore {
async load() {
if (this.state().tag !== 'Success') this.state.set({ tag: 'Loading' });
try {
const parsed = parseApplications(await this.adapter.listAll());
const parsed = parseAanvragen(await this.adapter.listAll());
this.state.set(
parsed.ok
? { tag: 'Success', value: parsed.value }
@@ -2,14 +2,14 @@ import { ApplicationRef, signal } from '@angular/core';
import { TestBed } from '@angular/core/testing';
import { ActivatedRoute, Router } from '@angular/router';
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { ApplicationsAdapter } from '@registratie/infrastructure/applications.adapter';
import { AanvragenAdapter } from '@registratie/infrastructure/aanvragen.adapter';
import { createDraftSync, DraftSnapshot } from './draft-sync';
function setup(adapter: Partial<ApplicationsAdapter>) {
function setup(adapter: Partial<AanvragenAdapter>) {
const navigate = vi.fn().mockResolvedValue(true);
TestBed.configureTestingModule({
providers: [
{ provide: ApplicationsAdapter, useValue: adapter },
{ provide: AanvragenAdapter, useValue: adapter },
{ provide: Router, useValue: { navigate } },
{ provide: ActivatedRoute, useValue: { snapshot: { queryParamMap: { get: () => null } } } },
],
@@ -4,11 +4,11 @@ import { Result } from '@shared/kernel/fp';
import { runSubmit, SUBMIT_FAILED } from '@shared/application/submit';
import { registerPendingSave } from '@shared/application/pending-saves';
import type {
SubmitApplicationRequest,
SubmitApplicationResponse,
AanvraagIndienenRequest,
AanvraagIndienenResponse,
} from '@shared/infrastructure/api-client';
import { AanvraagType } from '@registratie/domain/aanvraag';
import { ApplicationsAdapter } from '@registratie/infrastructure/applications.adapter';
import { AanvragenAdapter } from '@registratie/infrastructure/aanvragen.adapter';
import { findConcept, loadConcept } from './find-concept';
/** What a wizard persists per step: the opaque machine snapshot + progress + docs. */
@@ -46,7 +46,7 @@ const DEBOUNCE_MS = 600; // ponytail: fixed debounce; tune if the sync feels lag
* Inert without a Router (stories) or when `enabled()` is false — no network, no resume.
*/
export function createDraftSync(deps: DraftSyncDeps) {
const adapter = inject(ApplicationsAdapter);
const adapter = inject(AanvragenAdapter);
const router = inject(Router, { optional: true });
const route = inject(ActivatedRoute, { optional: true });
const active = () => deps.enabled() && !!router && !!route;
@@ -190,7 +190,7 @@ export function createDraftSync(deps: DraftSyncDeps) {
/** 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>> {
submit(body: AanvraagIndienenRequest): Promise<Result<string, AanvraagIndienenResponse>> {
return runSubmit(async () => adapter.submit(await ensureId(), body), SUBMIT_FAILED);
},
@@ -1,11 +1,11 @@
import { describe, it, expect } from 'vitest';
import { ApplicationsAdapter } from '@registratie/infrastructure/applications.adapter';
import { AanvragenAdapter } from '@registratie/infrastructure/aanvragen.adapter';
import { findConcept, loadConcept } from './find-concept';
// Free functions taking the adapter as a parameter (no inject()) — a plain fake
// object is enough, no Angular TestBed needed.
function fakeAdapter(overrides: Partial<ApplicationsAdapter>): ApplicationsAdapter {
return overrides as ApplicationsAdapter;
function fakeAdapter(overrides: Partial<AanvragenAdapter>): AanvragenAdapter {
return overrides as AanvragenAdapter;
}
describe('findConcept', () => {
@@ -1,8 +1,5 @@
import { AanvraagType } from '@registratie/domain/aanvraag';
import {
ApplicationsAdapter,
parseApplications,
} from '@registratie/infrastructure/applications.adapter';
import { AanvragenAdapter, parseAanvragen } from '@registratie/infrastructure/aanvragen.adapter';
/**
* Read half of the Concept lookup that `createDraftSync` (`draft-sync.ts`) needs
@@ -14,11 +11,11 @@ import {
/** Find the user's existing Concept of a given type (at most one), if any. */
export async function findConcept(
adapter: ApplicationsAdapter,
adapter: AanvragenAdapter,
type: AanvraagType,
): Promise<string | undefined> {
try {
const parsed = parseApplications(await adapter.list());
const parsed = parseAanvragen(await adapter.list());
return parsed.ok
? parsed.value.find((a) => a.type === type && a.status.tag === 'Concept')?.id
: undefined;
@@ -34,10 +31,7 @@ export async function findConcept(
export type LoadedConcept = { tag: 'concept'; draft: unknown | null } | { tag: 'not-concept' };
/** Load a specific Concept by id and report whether it is still editable. */
export async function loadConcept(
adapter: ApplicationsAdapter,
id: string,
): Promise<LoadedConcept> {
export async function loadConcept(adapter: AanvragenAdapter, id: string): Promise<LoadedConcept> {
try {
const dto = await adapter.detail(id);
if (dto.status && dto.status.tag !== 'Concept') return { tag: 'not-concept' };