Behandelportal: monorepo merge + WP-64..67 backoffice arc (OpenZaak write closes it out) #1
@@ -0,0 +1,16 @@
|
||||
import { inject } from '@angular/core';
|
||||
import { Result } from '@shared/kernel/fp';
|
||||
import { Valid } from '@behandeling/domain/besluit.machine';
|
||||
import { runSubmit, SUBMIT_FAILED } from '@shared/application/submit';
|
||||
import { BesluitAdapter } from '@behandeling/infrastructure/besluit.adapter';
|
||||
|
||||
/**
|
||||
* Command factory: binds the besluit adapter in an injection context and returns the
|
||||
* submit function the decision form calls. Same field-initializer shape as
|
||||
* `createStore` — the UI holds an application command, never the network client.
|
||||
*/
|
||||
export function createSubmitBesluit() {
|
||||
const adapter = inject(BesluitAdapter);
|
||||
return (id: string, data: Valid): Promise<Result<string, void>> =>
|
||||
runSubmit(() => adapter.besluit(id, data), SUBMIT_FAILED);
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { BesluitState, reduce, initial } from './besluit.machine';
|
||||
|
||||
const editingWith = (besluit: string, toelichting = ''): BesluitState => ({
|
||||
tag: 'Editing',
|
||||
draft: { besluit, toelichting },
|
||||
errors: {},
|
||||
});
|
||||
|
||||
describe('besluit reduce', () => {
|
||||
it('SetField updates the draft while editing', () => {
|
||||
const s = reduce(initial, { tag: 'SetField', key: 'besluit', value: 'Goedkeuren' });
|
||||
expect(s.tag).toBe('Editing');
|
||||
expect((s as Extract<BesluitState, { tag: 'Editing' }>).draft.besluit).toBe('Goedkeuren');
|
||||
});
|
||||
|
||||
it('Submit with no besluit chosen stays Editing and reports a field error', () => {
|
||||
const s = reduce(editingWith(''), { tag: 'Submit' });
|
||||
expect(s.tag).toBe('Editing');
|
||||
expect((s as Extract<BesluitState, { tag: 'Editing' }>).errors.besluit).toBeTruthy();
|
||||
});
|
||||
|
||||
it('Submit Afwijzen without a toelichting stays Editing and reports a field error', () => {
|
||||
const s = reduce(editingWith('Afwijzen'), { tag: 'Submit' });
|
||||
expect(s.tag).toBe('Editing');
|
||||
expect((s as Extract<BesluitState, { tag: 'Editing' }>).errors.toelichting).toBeTruthy();
|
||||
});
|
||||
|
||||
it('Submit Goedkeuren with no toelichting moves to Submitting (optional there)', () => {
|
||||
const s = reduce(editingWith('Goedkeuren'), { tag: 'Submit' });
|
||||
expect(s.tag).toBe('Submitting');
|
||||
expect((s as Extract<BesluitState, { tag: 'Submitting' }>).data).toEqual({
|
||||
besluit: 'Goedkeuren',
|
||||
toelichting: undefined,
|
||||
});
|
||||
});
|
||||
|
||||
it('Submit Afwijzen with a toelichting moves to Submitting with the trimmed value', () => {
|
||||
const s = reduce(editingWith('Afwijzen', ' niet erkend '), { tag: 'Submit' });
|
||||
expect(s.tag).toBe('Submitting');
|
||||
expect((s as Extract<BesluitState, { tag: 'Submitting' }>).data).toEqual({
|
||||
besluit: 'Afwijzen',
|
||||
toelichting: 'niet erkend',
|
||||
});
|
||||
});
|
||||
|
||||
it('SubmitConfirmed maps Submitting to Submitted', () => {
|
||||
const submitting = reduce(editingWith('Goedkeuren'), { tag: 'Submit' });
|
||||
expect(reduce(submitting, { tag: 'SubmitConfirmed' }).tag).toBe('Submitted');
|
||||
});
|
||||
|
||||
it('SubmitFailed maps Submitting to Failed with the error', () => {
|
||||
const submitting = reduce(editingWith('Goedkeuren'), { tag: 'Submit' });
|
||||
const failed = reduce(submitting, { tag: 'SubmitFailed', error: 'boom' });
|
||||
expect(failed).toMatchObject({ tag: 'Failed', error: 'boom' });
|
||||
});
|
||||
|
||||
it('Retry re-submits a failure', () => {
|
||||
const submitting = reduce(editingWith('Goedkeuren'), { tag: 'Submit' });
|
||||
const failed = reduce(submitting, { tag: 'SubmitFailed', error: 'boom' });
|
||||
expect(reduce(failed, { tag: 'Retry' }).tag).toBe('Submitting');
|
||||
});
|
||||
|
||||
it('Reset returns to the initial editing state', () => {
|
||||
const submitting = reduce(editingWith('Goedkeuren'), { tag: 'Submit' });
|
||||
expect(reduce(submitting, { tag: 'Reset' })).toEqual(initial);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,92 @@
|
||||
import { Result, assertNever } from '@shared/kernel/fp';
|
||||
|
||||
/** The three actions the beoordeling screen offers a behandelaar (WP-65b) — mirrors the
|
||||
backend's `Besluit` enum member names 1:1 (the wire convention: a string, not a raw
|
||||
enum — see `RecordBesluitRequest`). */
|
||||
const BESLUIT_TAGS = ['Goedkeuren', 'Afwijzen', 'MeerInfoOpvragen'] as const;
|
||||
export type BesluitTag = (typeof BESLUIT_TAGS)[number];
|
||||
|
||||
function isBesluitTag(v: string): v is BesluitTag {
|
||||
return (BESLUIT_TAGS as readonly string[]).includes(v);
|
||||
}
|
||||
|
||||
/** What the user picked (raw, possibly empty while nothing is selected yet). */
|
||||
export interface Draft {
|
||||
besluit: string;
|
||||
toelichting: string;
|
||||
}
|
||||
|
||||
/** After parsing — besluit is the narrow tag; toelichting is present only when given
|
||||
(required for Afwijzen/MeerInfoOpvragen, optional for Goedkeuren — enforced by validate). */
|
||||
export interface Valid {
|
||||
besluit: BesluitTag;
|
||||
toelichting?: string;
|
||||
}
|
||||
|
||||
export type Errors = Partial<Record<keyof Draft, string>>;
|
||||
|
||||
/** The decision form as one tagged union — same idiom as every other form in this
|
||||
house (form-machine skill), single-step. draft/errors exist only while Editing. */
|
||||
export type BesluitState =
|
||||
| { tag: 'Editing'; draft: Draft; errors: Errors }
|
||||
| { tag: 'Submitting'; data: Valid }
|
||||
| { tag: 'Submitted'; data: Valid }
|
||||
| { tag: 'Failed'; data: Valid; error: string };
|
||||
|
||||
export const initial: BesluitState = {
|
||||
tag: 'Editing',
|
||||
draft: { besluit: '', toelichting: '' },
|
||||
errors: {},
|
||||
};
|
||||
|
||||
function validate(draft: Draft): Result<Errors, Valid> {
|
||||
if (!isBesluitTag(draft.besluit)) {
|
||||
return {
|
||||
ok: false,
|
||||
error: { besluit: $localize`:@@besluit.error.verplicht:Kies een besluit.` },
|
||||
};
|
||||
}
|
||||
const toelichting = draft.toelichting.trim();
|
||||
if (draft.besluit !== 'Goedkeuren' && toelichting === '') {
|
||||
return {
|
||||
ok: false,
|
||||
error: {
|
||||
toelichting: $localize`:@@besluit.error.toelichtingVerplicht:Geef een toelichting.`,
|
||||
},
|
||||
};
|
||||
}
|
||||
return { ok: true, value: { besluit: draft.besluit, toelichting: toelichting || undefined } };
|
||||
}
|
||||
|
||||
export type BesluitMsg =
|
||||
| { tag: 'SetField'; key: keyof Draft; value: string }
|
||||
| { tag: 'Submit' }
|
||||
| { tag: 'Retry' }
|
||||
| { tag: 'SubmitConfirmed' }
|
||||
| { tag: 'SubmitFailed'; error: string }
|
||||
| { tag: 'Reset' }
|
||||
| { tag: 'Seed'; state: BesluitState }; // mount a specific state (stories/tests)
|
||||
|
||||
export function reduce(s: BesluitState, m: BesluitMsg): BesluitState {
|
||||
switch (m.tag) {
|
||||
case 'SetField':
|
||||
return s.tag === 'Editing' ? { ...s, draft: { ...s.draft, [m.key]: m.value } } : s;
|
||||
case 'Submit': {
|
||||
if (s.tag !== 'Editing') return s;
|
||||
const r = validate(s.draft);
|
||||
return r.ok ? { tag: 'Submitting', data: r.value } : { ...s, errors: r.error };
|
||||
}
|
||||
case 'Retry':
|
||||
return s.tag === 'Failed' ? { tag: 'Submitting', data: s.data } : s;
|
||||
case 'SubmitConfirmed':
|
||||
return s.tag === 'Submitting' ? { tag: 'Submitted', data: s.data } : s;
|
||||
case 'SubmitFailed':
|
||||
return s.tag === 'Submitting' ? { tag: 'Failed', data: s.data, error: m.error } : s;
|
||||
case 'Reset':
|
||||
return initial;
|
||||
case 'Seed':
|
||||
return m.state;
|
||||
default:
|
||||
return assertNever(m);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
import { Injectable, inject } from '@angular/core';
|
||||
import { ApiClient } from '@shared/infrastructure/api-client';
|
||||
import { Valid } from '@behandeling/domain/besluit.machine';
|
||||
|
||||
/**
|
||||
* Infrastructure adapter for recording a behandelaar's decision (WP-65b) — the single
|
||||
* place its HTTP lives. No return value: a successful call means the server accepted
|
||||
* the transition; the caller reloads `BeoordelingStore` to see the new status (the
|
||||
* server, not this adapter, re-validates and is the authority).
|
||||
*/
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class BesluitAdapter {
|
||||
private client = inject(ApiClient);
|
||||
|
||||
async besluit(id: string, data: Valid): Promise<void> {
|
||||
await this.client.besluit(id, { besluit: data.besluit, toelichting: data.toelichting });
|
||||
}
|
||||
}
|
||||
@@ -10,12 +10,13 @@ import { ASYNC } from '@shared/ui/async/async.component';
|
||||
import { BeoordelingStore } from '@behandeling/application/beoordeling.store';
|
||||
import { detailRows } from '@behandeling/domain/beoordeling-view';
|
||||
import { BeoordelingDocumentenComponent } from '@behandeling/ui/beoordeling-documenten/beoordeling-documenten.component';
|
||||
import { BesluitFormComponent } from '@behandeling/ui/besluit-form/besluit-form.component';
|
||||
|
||||
/**
|
||||
* Page: one aanvraag's beoordeling detail (WP-65, read side). The werkvoorraad list
|
||||
* (WP-64) links here. Recording a decision is this WP's second half — for now the
|
||||
* page only shows status/documents; `canBesluiten` is already carried by the view so
|
||||
* the decision form has zero further backend round-trip to add.
|
||||
* Page: one aanvraag's beoordeling detail (WP-65). The werkvoorraad list (WP-64) links
|
||||
* here. `canBesluiten` (server-computed, ADR-0001) gates the decision form (WP-65b) —
|
||||
* the page never recomputes the lifecycle itself. On a recorded decision the form emits
|
||||
* `decided`, and the page just reloads (the server is the authority on the new status).
|
||||
*/
|
||||
@Component({
|
||||
selector: 'app-beoordeling-page',
|
||||
@@ -27,6 +28,7 @@ import { BeoordelingDocumentenComponent } from '@behandeling/ui/beoordeling-docu
|
||||
DataBlockComponent,
|
||||
DataRowComponent,
|
||||
BeoordelingDocumentenComponent,
|
||||
BesluitFormComponent,
|
||||
...ASYNC,
|
||||
],
|
||||
template: `
|
||||
@@ -49,6 +51,11 @@ import { BeoordelingDocumentenComponent } from '@behandeling/ui/beoordeling-docu
|
||||
<app-data-block [heading]="documentenHeading" class="app-section">
|
||||
<app-beoordeling-documenten [documenten]="v.documenten" />
|
||||
</app-data-block>
|
||||
@if (v.canBesluiten) {
|
||||
<div class="app-section">
|
||||
<app-besluit-form [id]="v.id" (decided)="reload()" />
|
||||
</div>
|
||||
}
|
||||
}
|
||||
</ng-template>
|
||||
</app-async>
|
||||
|
||||
@@ -0,0 +1,140 @@
|
||||
import { Component, computed, input, output } from '@angular/core';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { ButtonComponent } from '@shared/ui/button/button.component';
|
||||
import { HeadingComponent } from '@shared/ui/heading/heading.component';
|
||||
import { AlertComponent } from '@shared/ui/alert/alert.component';
|
||||
import { FormFieldComponent } from '@shared/ui/form-field/form-field.component';
|
||||
import { TextInputComponent } from '@shared/ui/text-input/text-input.component';
|
||||
import { RadioGroupComponent, RadioOption } from '@shared/ui/radio-group/radio-group.component';
|
||||
import { createStore } from '@shared/application/store';
|
||||
import { whenTag } from '@shared/kernel/fp';
|
||||
import { BesluitState, BesluitMsg, initial, reduce } from '@behandeling/domain/besluit.machine';
|
||||
import { createSubmitBesluit } from '@behandeling/application/submit-besluit';
|
||||
|
||||
/**
|
||||
* Organism: the decision form (WP-65b) — goedkeuren/afwijzen/meer-info-opvragen. Same
|
||||
* idiom as every other form in this house (`change-request-form`): all state in one
|
||||
* signal driven by the pure `reduce` (besluit.machine.ts), submitted via a `submit-*`
|
||||
* command returning `Result`. The server re-validates the transition and is the
|
||||
* authority; on success this only emits `decided` — the page reloads the detail
|
||||
* (BeoordelingStore.reload()), it doesn't guess the new state itself.
|
||||
*/
|
||||
@Component({
|
||||
selector: 'app-besluit-form',
|
||||
imports: [
|
||||
FormsModule,
|
||||
ButtonComponent,
|
||||
HeadingComponent,
|
||||
AlertComponent,
|
||||
FormFieldComponent,
|
||||
TextInputComponent,
|
||||
RadioGroupComponent,
|
||||
],
|
||||
template: `
|
||||
@if (state().tag === 'Submitted') {
|
||||
<app-alert type="ok" i18n="@@besluit.success">Het besluit is vastgelegd.</app-alert>
|
||||
} @else {
|
||||
<app-heading [level]="2" i18n="@@besluit.heading">Besluit vastleggen</app-heading>
|
||||
|
||||
<form (ngSubmit)="onSubmit()" class="form-horizontal app-section">
|
||||
<app-form-field
|
||||
i18n-label="@@besluit.besluitLabel"
|
||||
label="Besluit"
|
||||
fieldId="besluit-keuze"
|
||||
required
|
||||
[error]="errors().besluit"
|
||||
>
|
||||
<app-radio-group
|
||||
name="besluit-keuze"
|
||||
[options]="BESLUIT_OPTIONS"
|
||||
[invalid]="!!errors().besluit"
|
||||
[ngModel]="besluit()"
|
||||
(ngModelChange)="dispatch({ tag: 'SetField', key: 'besluit', value: $event })"
|
||||
[ngModelOptions]="{ standalone: true }"
|
||||
/>
|
||||
</app-form-field>
|
||||
|
||||
<app-form-field
|
||||
i18n-label="@@besluit.toelichtingLabel"
|
||||
label="Toelichting"
|
||||
fieldId="besluit-toelichting"
|
||||
[error]="errors().toelichting"
|
||||
>
|
||||
<app-text-input
|
||||
inputId="besluit-toelichting"
|
||||
[invalid]="!!errors().toelichting"
|
||||
[ngModel]="toelichting()"
|
||||
(ngModelChange)="dispatch({ tag: 'SetField', key: 'toelichting', value: $event })"
|
||||
name="toelichting"
|
||||
[ngModelOptions]="{ standalone: true }"
|
||||
/>
|
||||
</app-form-field>
|
||||
|
||||
@if (failedError()) {
|
||||
<app-alert type="error"
|
||||
><ng-container i18n="@@besluit.failed">Het vastleggen is niet gelukt:</ng-container>
|
||||
{{ failedError() }}</app-alert
|
||||
>
|
||||
}
|
||||
|
||||
<app-button type="submit" variant="primary" [disabled]="state().tag === 'Submitting'">
|
||||
{{ state().tag === 'Submitting' ? submitBezigLabel : submitLabel }}
|
||||
</app-button>
|
||||
</form>
|
||||
}
|
||||
`,
|
||||
})
|
||||
export class BesluitFormComponent {
|
||||
private submit = createSubmitBesluit();
|
||||
private store = createStore<BesluitState, BesluitMsg>(initial, reduce);
|
||||
|
||||
id = input.required<string>();
|
||||
decided = output<void>();
|
||||
|
||||
/** Optional seed so Storybook / tests can mount any state directly. */
|
||||
seed = input<BesluitState>(initial);
|
||||
|
||||
readonly state = this.store.model;
|
||||
protected dispatch = this.store.dispatch;
|
||||
|
||||
protected readonly BESLUIT_OPTIONS: RadioOption[] = [
|
||||
{ value: 'Goedkeuren', label: $localize`:@@besluit.optie.goedkeuren:Goedkeuren` },
|
||||
{ value: 'Afwijzen', label: $localize`:@@besluit.optie.afwijzen:Afwijzen` },
|
||||
{
|
||||
value: 'MeerInfoOpvragen',
|
||||
label: $localize`:@@besluit.optie.meerInfoOpvragen:Meer informatie opvragen`,
|
||||
},
|
||||
];
|
||||
|
||||
protected readonly submitLabel = $localize`:@@besluit.submit:Besluit vastleggen`;
|
||||
protected readonly submitBezigLabel = $localize`:@@besluit.submitBezig:Bezig met vastleggen…`;
|
||||
|
||||
private editing = computed(() => whenTag(this.state(), 'Editing'));
|
||||
protected errors = computed(() => this.editing()?.errors ?? {});
|
||||
protected failedError = computed(() => whenTag(this.state(), 'Failed')?.error ?? '');
|
||||
|
||||
protected besluit = computed(() => this.editing()?.draft.besluit ?? '');
|
||||
protected toelichting = computed(() => this.editing()?.draft.toelichting ?? '');
|
||||
|
||||
constructor() {
|
||||
queueMicrotask(() => this.dispatch({ tag: 'Seed', state: this.seed() }));
|
||||
}
|
||||
|
||||
onSubmit() {
|
||||
this.dispatch({ tag: 'Submit' });
|
||||
this.runIfSubmitting();
|
||||
}
|
||||
|
||||
/** Effect: when we entered Submitting, call the command, then dispatch the outcome. */
|
||||
private async runIfSubmitting() {
|
||||
const s = this.state();
|
||||
if (s.tag !== 'Submitting') return;
|
||||
const r = await this.submit(this.id(), s.data);
|
||||
if (r.ok) {
|
||||
this.dispatch({ tag: 'SubmitConfirmed' });
|
||||
this.decided.emit();
|
||||
} else {
|
||||
this.dispatch({ tag: 'SubmitFailed', error: r.error });
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
import type { Meta, StoryObj } from '@storybook/angular';
|
||||
import { applicationConfig } from '@storybook/angular';
|
||||
import { provideHttpClient } from '@angular/common/http';
|
||||
import { BesluitFormComponent } from './besluit-form.component';
|
||||
import { provideApiClient } from '@shared/infrastructure/api-client.provider';
|
||||
import { Valid } from '@behandeling/domain/besluit.machine';
|
||||
|
||||
const validData: Valid = { besluit: 'Afwijzen', toelichting: 'Diploma niet erkend' };
|
||||
|
||||
const meta: Meta<BesluitFormComponent> = {
|
||||
title: 'Domein/Behandeling/Besluit Form',
|
||||
component: BesluitFormComponent,
|
||||
// The form injects ApiClient (over HttpClient) for the submit command.
|
||||
decorators: [applicationConfig({ providers: [provideHttpClient(), provideApiClient()] })],
|
||||
args: { id: 'aanvraag-1' },
|
||||
};
|
||||
export default meta;
|
||||
type Story = StoryObj<BesluitFormComponent>;
|
||||
|
||||
// One render per state of the machine.
|
||||
export const Empty: Story = {
|
||||
args: { seed: { tag: 'Editing', draft: { besluit: '', toelichting: '' }, errors: {} } },
|
||||
};
|
||||
export const WithErrors: Story = {
|
||||
args: {
|
||||
seed: {
|
||||
tag: 'Editing',
|
||||
draft: { besluit: 'Afwijzen', toelichting: '' },
|
||||
errors: { toelichting: 'Geef een toelichting.' },
|
||||
},
|
||||
},
|
||||
};
|
||||
export const Submitting: Story = { args: { seed: { tag: 'Submitting', data: validData } } };
|
||||
export const Submitted: Story = { args: { seed: { tag: 'Submitted', data: validData } } };
|
||||
export const Failed: Story = {
|
||||
args: { seed: { tag: 'Failed', data: validData, error: 'Netwerkfout' } },
|
||||
};
|
||||
@@ -3020,6 +3020,54 @@
|
||||
<source>Opnieuw proberen</source>
|
||||
<target datatype="html">Try again</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="besluit.error.verplicht" datatype="html">
|
||||
<source>Kies een besluit.</source>
|
||||
<target datatype="html">Choose a decision.</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="besluit.error.toelichtingVerplicht" datatype="html">
|
||||
<source>Geef een toelichting.</source>
|
||||
<target datatype="html">Give an explanation.</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="besluit.success" datatype="html">
|
||||
<source>Het besluit is vastgelegd.</source>
|
||||
<target datatype="html">The decision has been recorded.</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="besluit.heading" datatype="html">
|
||||
<source>Besluit vastleggen</source>
|
||||
<target datatype="html">Record a decision</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="besluit.besluitLabel" datatype="html">
|
||||
<source>Besluit</source>
|
||||
<target datatype="html">Decision</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="besluit.toelichtingLabel" datatype="html">
|
||||
<source>Toelichting</source>
|
||||
<target datatype="html">Explanation</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="besluit.failed" datatype="html">
|
||||
<source>Het vastleggen is niet gelukt:</source>
|
||||
<target datatype="html">Recording the decision failed:</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="besluit.optie.goedkeuren" datatype="html">
|
||||
<source>Goedkeuren</source>
|
||||
<target datatype="html">Approve</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="besluit.optie.afwijzen" datatype="html">
|
||||
<source>Afwijzen</source>
|
||||
<target datatype="html">Reject</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="besluit.optie.meerInfoOpvragen" datatype="html">
|
||||
<source>Meer informatie opvragen</source>
|
||||
<target datatype="html">Request more information</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="besluit.submit" datatype="html">
|
||||
<source>Besluit vastleggen</source>
|
||||
<target datatype="html">Record decision</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="besluit.submitBezig" datatype="html">
|
||||
<source>Bezig met vastleggen…</source>
|
||||
<target datatype="html">Recording…</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="beheer.noTables" datatype="html">
|
||||
<source>Er is geen stamdata om te beheren.</source>
|
||||
<target datatype="html">There is no stamdata to manage.</target>
|
||||
|
||||
@@ -156,6 +156,20 @@
|
||||
<context context-type="linenumber">47</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="besluit.error.verplicht" datatype="html">
|
||||
<source>Kies een besluit.</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">apps/behandelportal/src/app/behandeling/domain/besluit.machine.ts</context>
|
||||
<context context-type="linenumber">46</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="besluit.error.toelichtingVerplicht" datatype="html">
|
||||
<source>Geef een toelichting.</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">apps/behandelportal/src/app/behandeling/domain/besluit.machine.ts</context>
|
||||
<context context-type="linenumber">54</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="werkvoorraad.row.ingediend" datatype="html">
|
||||
<source>ingediend op <x id="datum" equiv-text="formatDatumNl(item.submittedAt)"/></source>
|
||||
<context-group purpose="location">
|
||||
@@ -181,35 +195,105 @@
|
||||
<source>Aanvraag</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">apps/behandelportal/src/app/behandeling/ui/beoordeling.page.ts</context>
|
||||
<context context-type="linenumber">62</context>
|
||||
<context context-type="linenumber">69</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="beoordeling.detail.heading" datatype="html">
|
||||
<source>Aanvraaggegevens</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">apps/behandelportal/src/app/behandeling/ui/beoordeling.page.ts</context>
|
||||
<context context-type="linenumber">63</context>
|
||||
<context context-type="linenumber">70</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="beoordeling.documenten.heading" datatype="html">
|
||||
<source>Documenten</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">apps/behandelportal/src/app/behandeling/ui/beoordeling.page.ts</context>
|
||||
<context context-type="linenumber">64</context>
|
||||
<context context-type="linenumber">71</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="beoordeling.failed" datatype="html">
|
||||
<source>De aanvraag kon niet worden geladen.</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">apps/behandelportal/src/app/behandeling/ui/beoordeling.page.ts</context>
|
||||
<context context-type="linenumber">65</context>
|
||||
<context context-type="linenumber">72</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="beoordeling.retry" datatype="html">
|
||||
<source>Opnieuw proberen</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">apps/behandelportal/src/app/behandeling/ui/beoordeling.page.ts</context>
|
||||
<context context-type="linenumber">66</context>
|
||||
<context context-type="linenumber">73</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="besluit.success" datatype="html">
|
||||
<source>Het besluit is vastgelegd.</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">apps/behandelportal/src/app/behandeling/ui/besluit-form/besluit-form.component.ts</context>
|
||||
<context context-type="linenumber">35,37</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="besluit.heading" datatype="html">
|
||||
<source>Besluit vastleggen</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">apps/behandelportal/src/app/behandeling/ui/besluit-form/besluit-form.component.ts</context>
|
||||
<context context-type="linenumber">37,39</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="besluit.besluitLabel" datatype="html">
|
||||
<source>Besluit</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">apps/behandelportal/src/app/behandeling/ui/besluit-form/besluit-form.component.ts</context>
|
||||
<context context-type="linenumber">43</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="besluit.toelichtingLabel" datatype="html">
|
||||
<source>Toelichting</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">apps/behandelportal/src/app/behandeling/ui/besluit-form/besluit-form.component.ts</context>
|
||||
<context context-type="linenumber">59,60</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="besluit.failed" datatype="html">
|
||||
<source>Het vastleggen is niet gelukt:</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">apps/behandelportal/src/app/behandeling/ui/besluit-form/besluit-form.component.ts</context>
|
||||
<context context-type="linenumber">75,76</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="besluit.optie.goedkeuren" datatype="html">
|
||||
<source>Goedkeuren</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">apps/behandelportal/src/app/behandeling/ui/besluit-form/besluit-form.component.ts</context>
|
||||
<context context-type="linenumber">101</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="besluit.optie.afwijzen" datatype="html">
|
||||
<source>Afwijzen</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">apps/behandelportal/src/app/behandeling/ui/besluit-form/besluit-form.component.ts</context>
|
||||
<context context-type="linenumber">102</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="besluit.optie.meerInfoOpvragen" datatype="html">
|
||||
<source>Meer informatie opvragen</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">apps/behandelportal/src/app/behandeling/ui/besluit-form/besluit-form.component.ts</context>
|
||||
<context context-type="linenumber">105</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="besluit.submit" datatype="html">
|
||||
<source>Besluit vastleggen</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">apps/behandelportal/src/app/behandeling/ui/besluit-form/besluit-form.component.ts</context>
|
||||
<context context-type="linenumber">109</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="besluit.submitBezig" datatype="html">
|
||||
<source>Bezig met vastleggen…</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">apps/behandelportal/src/app/behandeling/ui/besluit-form/besluit-form.component.ts</context>
|
||||
<context context-type="linenumber">110</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="werkvoorraad.heading" datatype="html">
|
||||
@@ -870,6 +954,20 @@
|
||||
<context context-type="linenumber">107</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="common.ja" datatype="html">
|
||||
<source>Ja</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">libs/shared/src/ui/radio-group/radio-group.component.ts</context>
|
||||
<context context-type="linenumber">12</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="common.nee" datatype="html">
|
||||
<source>Nee</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">libs/shared/src/ui/radio-group/radio-group.component.ts</context>
|
||||
<context context-type="linenumber">13</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="spinner.aria" datatype="html">
|
||||
<source>Bezig met laden</source>
|
||||
<context-group purpose="location">
|
||||
|
||||
@@ -140,6 +140,15 @@ public sealed record BeoordelingViewDto(
|
||||
IReadOnlyList<BeoordelingDocumentDto> Documenten,
|
||||
BeoordelingDecisionsDto Decisions);
|
||||
|
||||
/// Recording a decision (WP-65b). `Besluit` is the enum member name as a string — same
|
||||
/// wire convention as `AanvraagStatusDto.Tag` (this backend never ships a raw C# enum,
|
||||
/// it round-trips names via Enum.Parse/.ToString() at the Contracts boundary, no
|
||||
/// JsonStringEnumConverter configured). The endpoint 400s an unknown name. Toelichting
|
||||
/// is required for Afwijzen/MeerInfoOpvragen, validated server-side.
|
||||
public sealed record RecordBesluitRequest(string Besluit, string? Toelichting = null);
|
||||
|
||||
public sealed record RecordBesluitResponse(AanvraagStatusDto Status);
|
||||
|
||||
// --- Brief (letter composition) contracts ---
|
||||
// Rich text is a serialisable node tree; the node union is flattened with a `Type`
|
||||
// discriminator + nullable fields, the same wire convention as AanvraagStatusDto.
|
||||
|
||||
@@ -38,17 +38,25 @@ public static class Mappers
|
||||
|
||||
// Aanvraag status is COMPUTED ON READ: an auto-approvable submission reports
|
||||
// Goedgekeurd once past the processing window, else In behandeling; a manual case
|
||||
// stays In behandeling forever (awaits the unbuilt backoffice). Pure — testable
|
||||
// by passing different `now` values without waiting for the wall clock.
|
||||
//
|
||||
// Ingediend/MeerInfoGevraagd (AanvraagStatusTag, WP-63) aren't produced here yet — no
|
||||
// behandelaar action exists to reach them (WP-65 adds the transition endpoint).
|
||||
// stays In behandeling until a behandelaar records a decision (WP-65b — before that
|
||||
// WP, it stayed In behandeling forever, awaiting the then-unbuilt backoffice). Pure —
|
||||
// testable by passing different `now` values without waiting for the wall clock.
|
||||
public static AanvraagStatusDto ToStatusDto(this Aanvraag a, DateTimeOffset now)
|
||||
{
|
||||
if (!a.Submitted)
|
||||
return new("Concept", StepIndex: a.StepIndex, StepCount: a.StepCount);
|
||||
if (a.Reden is not null)
|
||||
return new(AanvraagStatusTag.Afgewezen.ToString(), Referentie: a.Referentie, Reden: a.Reden);
|
||||
// A recorded decision (WP-65b) wins over the auto-approve computation below — a
|
||||
// behandelaar's explicit besluit is authoritative once made.
|
||||
if (a.BesluitStatus is { } besluit)
|
||||
return besluit switch
|
||||
{
|
||||
Besluit.Goedkeuren => new(AanvraagStatusTag.Goedgekeurd.ToString(), Referentie: a.Referentie),
|
||||
Besluit.Afwijzen => new(AanvraagStatusTag.Afgewezen.ToString(), Referentie: a.Referentie, Reden: a.BesluitToelichting),
|
||||
Besluit.MeerInfoOpvragen => new(AanvraagStatusTag.MeerInfoGevraagd.ToString(), Referentie: a.Referentie, Reden: a.BesluitToelichting),
|
||||
_ => throw new InvalidOperationException($"Unknown besluit {besluit}"),
|
||||
};
|
||||
if (a.AutoApprovable && now > a.SubmittedAt!.Value + ApplicationStore.ProcessingWindow)
|
||||
return new(AanvraagStatusTag.Goedgekeurd.ToString(), Referentie: a.Referentie);
|
||||
return new(AanvraagStatusTag.InBehandeling.ToString(), Referentie: a.Referentie, Manual: !a.AutoApprovable);
|
||||
|
||||
@@ -13,6 +13,14 @@ namespace BigRegister.Api.Data;
|
||||
/// </summary>
|
||||
public enum AanvraagStatusTag { Ingediend, InBehandeling, MeerInfoGevraagd, Goedgekeurd, Afgewezen }
|
||||
|
||||
/// <summary>
|
||||
/// A behandelaar's recorded decision (WP-65b) — the three actions the beoordeling screen
|
||||
/// offers, each advancing <see cref="Aanvraag.BesluitStatus"/> and (via
|
||||
/// <see cref="BigRegister.Api.Contracts.Mappers.ToStatusDto"/>) the published
|
||||
/// <see cref="AanvraagStatusTag"/> the FE renders.
|
||||
/// </summary>
|
||||
public enum Besluit { Goedkeuren, Afwijzen, MeerInfoOpvragen }
|
||||
|
||||
/// <summary>
|
||||
/// An application (aanvraag) — the system of record the dashboard reads. A wizard
|
||||
/// creates one as a Concept on its first step, syncs its draft snapshot per step,
|
||||
@@ -50,6 +58,17 @@ public sealed class Aanvraag
|
||||
/// is re-findable by <c>identificatie == Referentie</c>. Cleared by a future repair path;
|
||||
/// none exists yet (see openzaak-integration.md's "Write resilience" section).</summary>
|
||||
public string? ZgwError { get; set; }
|
||||
|
||||
/// <summary>WP-65b: a behandelaar's recorded decision, if any. Non-null wins over the
|
||||
/// auto-approve computation in <see cref="BigRegister.Api.Contracts.Mappers.ToStatusDto"/> —
|
||||
/// "a recorded decision wins". Mutable across <see cref="AanvraagStatusTag.MeerInfoGevraagd"/>
|
||||
/// (a behandelaar may decide again later); frozen once Goedgekeurd/Afgewezen (terminal, per
|
||||
/// <see cref="BigRegister.Domain.Beoordeling.BeoordelingRules.CanDecide"/>).</summary>
|
||||
public Besluit? BesluitStatus { get; set; }
|
||||
|
||||
/// <summary>The behandelaar's toelichting — required for Afwijzen/MeerInfoOpvragen (becomes
|
||||
/// the published status's Reden), optional for Goedkeuren.</summary>
|
||||
public string? BesluitToelichting { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -104,6 +123,17 @@ public static class ApplicationStore
|
||||
}
|
||||
}
|
||||
|
||||
/// Cross-owner single read (WP-65b) — the behandelaar decision endpoint's counterpart of
|
||||
/// <see cref="Get"/>, same "any owner" shape as <see cref="DeleteAny"/>.
|
||||
public static Aanvraag? GetAny(string id)
|
||||
{
|
||||
lock (_gate)
|
||||
{
|
||||
using var db = Db.Create();
|
||||
return db.Applications.Find(id);
|
||||
}
|
||||
}
|
||||
|
||||
/// Admin: every case across all owners (WP-36). The per-owner List is the norm; this
|
||||
/// is the deliberate cross-owner read behind the admin-only /admin/cases endpoint.
|
||||
public static IReadOnlyList<Aanvraag> ListAll()
|
||||
@@ -224,4 +254,24 @@ public static class ApplicationStore
|
||||
db.SaveChanges();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Record a behandelaar's decision (WP-65b). The endpoint has already checked
|
||||
/// <see cref="BigRegister.Domain.Beoordeling.BeoordelingRules.CanDecide"/> against the
|
||||
/// freshly-read status before calling this — cross-owner like <see cref="DeleteAny"/>,
|
||||
/// since a behandelaar decides on any citizen's case. Returns null only if the aanvraag
|
||||
/// is gone (shouldn't happen — this runs right after the endpoint's own read found it).</summary>
|
||||
public static Aanvraag? RecordBesluit(string id, Besluit besluit, string? toelichting)
|
||||
{
|
||||
lock (_gate)
|
||||
{
|
||||
using var db = Db.Create();
|
||||
var a = db.Applications.Find(id);
|
||||
if (a is null) return null;
|
||||
a.BesluitStatus = besluit;
|
||||
a.BesluitToelichting = toelichting;
|
||||
a.UpdatedAt = DateTimeOffset.UtcNow;
|
||||
db.SaveChanges();
|
||||
return a;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+288
@@ -0,0 +1,288 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using BigRegister.Api.Data;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace BigRegister.Api.Data.Migrations
|
||||
{
|
||||
[DbContext(typeof(AppDbContext))]
|
||||
[Migration("20260803070817_BesluitStatus")]
|
||||
partial class BesluitStatus
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder.HasAnnotation("ProductVersion", "10.0.9");
|
||||
|
||||
modelBuilder.Entity("BigRegister.Api.Data.Aanvraag", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("AutoApprovable")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int?>("BesluitStatus")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("BesluitToelichting")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTimeOffset>("CreatedAt")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("DocumentIds")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Draft")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Owner")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Reden")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Referentie")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("StepCount")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("StepIndex")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("Submitted")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<DateTimeOffset?>("SubmittedAt")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Type")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTimeOffset>("UpdatedAt")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("ZaakUrl")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("ZgwError")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("Applications");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BigRegister.Api.Data.AuditEntry", b =>
|
||||
{
|
||||
b.Property<long>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Action")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Actor")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTimeOffset>("At")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("CategoryId")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("DocumentId")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("AuditEntries");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BigRegister.Api.Data.AuthzAuditEntry", b =>
|
||||
{
|
||||
b.Property<long>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Action")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTimeOffset>("At")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("CorrelationId")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Decision")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Resource")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Role")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("AuthzAudit");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BigRegister.Api.Data.BriefEntity", b =>
|
||||
{
|
||||
b.Property<string>("BriefId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("ArchivedHtml")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Beroep")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("DrafterId")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Owner")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Placeholders")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Sections")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int?>("SentOrgTemplateVersion")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Status")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("SubOrgId")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("TemplateId")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("BriefId");
|
||||
|
||||
b.HasIndex("Owner")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("Briefs");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BigRegister.Api.Data.FeatureFlagEntity", b =>
|
||||
{
|
||||
b.Property<string>("Key")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("Enabled")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("Key");
|
||||
|
||||
b.ToTable("FeatureFlags");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BigRegister.Api.Data.OrgTemplateEntity", b =>
|
||||
{
|
||||
b.Property<string>("SubOrgId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Draft")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("History")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("PublishedVersion")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("SubOrgId");
|
||||
|
||||
b.ToTable("OrgTemplates");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BigRegister.Api.Data.StoredDocument", b =>
|
||||
{
|
||||
b.Property<string>("DocumentId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("CategoryId")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<byte[]>("Content")
|
||||
.IsRequired()
|
||||
.HasColumnType("BLOB");
|
||||
|
||||
b.Property<string>("ContentType")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("DrcUrl")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("FileName")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("Linked")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("LocalId")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Owner")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<long>("SizeBytes")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<DateTimeOffset>("UploadedAt")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("WizardId")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("DocumentId");
|
||||
|
||||
b.ToTable("Documents");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace BigRegister.Api.Data.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class BesluitStatus : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<int>(
|
||||
name: "BesluitStatus",
|
||||
table: "Applications",
|
||||
type: "INTEGER",
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "BesluitToelichting",
|
||||
table: "Applications",
|
||||
type: "TEXT",
|
||||
nullable: true);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "BesluitStatus",
|
||||
table: "Applications");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "BesluitToelichting",
|
||||
table: "Applications");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -25,6 +25,12 @@ namespace BigRegister.Api.Data.Migrations
|
||||
b.Property<bool>("AutoApprovable")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int?>("BesluitStatus")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("BesluitToelichting")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTimeOffset>("CreatedAt")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
|
||||
@@ -442,6 +442,40 @@ api.MapGet("/beoordeling/{id}", (string id, HttpContext ctx, IZaakSource zaken)
|
||||
.ProducesProblem(StatusCodes.Status403Forbidden)
|
||||
.Produces(StatusCodes.Status404NotFound);
|
||||
|
||||
// --- Besluit (WP-65b): record a behandelaar's decision, advancing the WP-63 status
|
||||
// lifecycle. Runs against ApplicationStore directly (not the IZaakSource seam) — same
|
||||
// reasoning as the GET above: a new seam method would force an OpenZaakZaakSource
|
||||
// write now, which is WP-66's surface, not this one's. The transition-legality check
|
||||
// (BeoordelingRules.CanDecide) is the SAME function the GET's canBesluiten flag uses,
|
||||
// so the two can never drift.
|
||||
api.MapPost("/beoordeling/{id}/besluit", (string id, RecordBesluitRequest req, HttpContext ctx) =>
|
||||
Beoordelen(ctx, $"aanvraag/{id}/besluit", () =>
|
||||
{
|
||||
if (!Enum.TryParse<Besluit>(req.Besluit, out var besluit))
|
||||
return Results.Problem(detail: $"Onbekend besluit '{req.Besluit}'.", statusCode: StatusCodes.Status400BadRequest);
|
||||
|
||||
var now = DateTimeOffset.UtcNow;
|
||||
var a = ApplicationStore.GetAny(id);
|
||||
var statusTag = a?.ToStatusDto(now).Tag;
|
||||
if (a is null || statusTag == "Concept") return Results.NotFound();
|
||||
var current = Enum.Parse<AanvraagStatusTag>(statusTag!);
|
||||
if (!BeoordelingRules.CanDecide(current))
|
||||
return Results.Problem(
|
||||
detail: "Deze aanvraag staat geen besluit meer toe in de huidige status.",
|
||||
statusCode: StatusCodes.Status409Conflict);
|
||||
if (besluit != Besluit.Goedkeuren && string.IsNullOrWhiteSpace(req.Toelichting))
|
||||
return Results.Problem(detail: "Toelichting is verplicht bij dit besluit.", statusCode: StatusCodes.Status400BadRequest);
|
||||
|
||||
var updated = ApplicationStore.RecordBesluit(id, besluit, req.Toelichting)!;
|
||||
app.Logger.LogInformation("aanvraag besluit id={Id} besluit={Besluit}", id, besluit);
|
||||
return Results.Ok(new RecordBesluitResponse(updated.ToStatusDto(now)));
|
||||
}))
|
||||
.Produces<RecordBesluitResponse>()
|
||||
.ProducesProblem(StatusCodes.Status400BadRequest)
|
||||
.ProducesProblem(StatusCodes.Status403Forbidden)
|
||||
.ProducesProblem(StatusCodes.Status409Conflict)
|
||||
.Produces(StatusCodes.Status404NotFound);
|
||||
|
||||
// OpenZaak's Notificaties API (NRC) calls this on every zaak event once an `abonnement` is
|
||||
// provisioned (WP-52, out-of-band — see openzaak-integration.md, no app code subscribes it).
|
||||
// The caller is NRC, not a user: no Principal, so this audits via AuthzAuditStore directly
|
||||
|
||||
@@ -840,6 +840,78 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/beoordeling/{id}/besluit": {
|
||||
"post": {
|
||||
"tags": [
|
||||
"BigRegister.Api, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"
|
||||
],
|
||||
"parameters": [
|
||||
{
|
||||
"name": "id",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/RecordBesluitRequest"
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": true
|
||||
},
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/RecordBesluitResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad Request",
|
||||
"content": {
|
||||
"application/problem+json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ProblemDetails"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"403": {
|
||||
"description": "Forbidden",
|
||||
"content": {
|
||||
"application/problem+json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ProblemDetails"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"409": {
|
||||
"description": "Conflict",
|
||||
"content": {
|
||||
"application/problem+json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ProblemDetails"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "Not Found"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/admin/cases/{id}": {
|
||||
"delete": {
|
||||
"tags": [
|
||||
@@ -2448,6 +2520,29 @@
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"RecordBesluitRequest": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"besluit": {
|
||||
"type": "string",
|
||||
"nullable": true
|
||||
},
|
||||
"toelichting": {
|
||||
"type": "string",
|
||||
"nullable": true
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"RecordBesluitResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"status": {
|
||||
"$ref": "#/components/schemas/AanvraagStatusDto"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"ReferentieResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
||||
@@ -127,4 +127,113 @@ public class BeoordelingTests(TestWebApplicationFactory factory) : IClassFixture
|
||||
req.Headers.Add("X-Rollen", "geen");
|
||||
Assert.Equal(HttpStatusCode.Forbidden, (await _client.SendAsync(req)).StatusCode);
|
||||
}
|
||||
|
||||
private Task<HttpResponseMessage> PostBesluit(string id, object body)
|
||||
{
|
||||
var req = AsBehandelaar(HttpMethod.Post, $"/api/v1/beoordeling/{id}/besluit");
|
||||
req.Content = JsonContent.Create(body);
|
||||
return _client.SendAsync(req);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Goedkeuren_advances_status_to_Goedgekeurd()
|
||||
{
|
||||
var (a, _) = await CreateManualCaseWithDocument();
|
||||
try
|
||||
{
|
||||
var res = await PostBesluit(a.Id, new { besluit = "Goedkeuren" });
|
||||
res.EnsureSuccessStatusCode();
|
||||
var body = (await res.Content.ReadFromJsonAsync<RecordBesluitResponse>())!;
|
||||
Assert.Equal("Goedgekeurd", body.Status.Tag);
|
||||
|
||||
var detail = await _client.SendAsync(AsBehandelaar(HttpMethod.Get, $"/api/v1/beoordeling/{a.Id}"));
|
||||
var view = (await detail.Content.ReadFromJsonAsync<BeoordelingViewDto>())!;
|
||||
Assert.Equal("Goedgekeurd", view.Aanvraag.Status.Tag);
|
||||
Assert.False(view.Decisions.CanBesluiten); // terminal — no further decision allowed
|
||||
}
|
||||
finally
|
||||
{
|
||||
await DeleteAsAdmin(a.Id);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Afwijzen_requires_a_toelichting()
|
||||
{
|
||||
var (a, _) = await CreateManualCaseWithDocument();
|
||||
try
|
||||
{
|
||||
var missing = await PostBesluit(a.Id, new { besluit = "Afwijzen" });
|
||||
Assert.Equal(HttpStatusCode.BadRequest, missing.StatusCode);
|
||||
|
||||
var res = await PostBesluit(a.Id, new { besluit = "Afwijzen", toelichting = "Diploma niet erkend" });
|
||||
res.EnsureSuccessStatusCode();
|
||||
var body = (await res.Content.ReadFromJsonAsync<RecordBesluitResponse>())!;
|
||||
Assert.Equal("Afgewezen", body.Status.Tag);
|
||||
Assert.Equal("Diploma niet erkend", body.Status.Reden);
|
||||
}
|
||||
finally
|
||||
{
|
||||
await DeleteAsAdmin(a.Id);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task MeerInfoOpvragen_is_still_decidable_afterwards()
|
||||
{
|
||||
var (a, _) = await CreateManualCaseWithDocument();
|
||||
try
|
||||
{
|
||||
var res = await PostBesluit(a.Id, new { besluit = "MeerInfoOpvragen", toelichting = "Stuur een geldig diploma" });
|
||||
res.EnsureSuccessStatusCode();
|
||||
var body = (await res.Content.ReadFromJsonAsync<RecordBesluitResponse>())!;
|
||||
Assert.Equal("MeerInfoGevraagd", body.Status.Tag);
|
||||
|
||||
var detail = await _client.SendAsync(AsBehandelaar(HttpMethod.Get, $"/api/v1/beoordeling/{a.Id}"));
|
||||
var view = (await detail.Content.ReadFromJsonAsync<BeoordelingViewDto>())!;
|
||||
Assert.True(view.Decisions.CanBesluiten); // not terminal — a decision can still follow
|
||||
}
|
||||
finally
|
||||
{
|
||||
await DeleteAsAdmin(a.Id);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Already_decided_case_rejects_a_further_besluit()
|
||||
{
|
||||
var (a, _) = await CreateManualCaseWithDocument();
|
||||
try
|
||||
{
|
||||
(await PostBesluit(a.Id, new { besluit = "Goedkeuren" })).EnsureSuccessStatusCode();
|
||||
var again = await PostBesluit(a.Id, new { besluit = "Afwijzen", toelichting = "te laat" });
|
||||
Assert.Equal(HttpStatusCode.Conflict, again.StatusCode);
|
||||
}
|
||||
finally
|
||||
{
|
||||
await DeleteAsAdmin(a.Id);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Unknown_id_404s_and_zorgverlener_is_forbidden()
|
||||
{
|
||||
var notFound = await PostBesluit("does-not-exist", new { besluit = "Goedkeuren" });
|
||||
Assert.Equal(HttpStatusCode.NotFound, notFound.StatusCode);
|
||||
|
||||
var (a, _) = await CreateManualCaseWithDocument();
|
||||
try
|
||||
{
|
||||
var req = new HttpRequestMessage(HttpMethod.Post, $"/api/v1/beoordeling/{a.Id}/besluit")
|
||||
{
|
||||
Content = JsonContent.Create(new { besluit = "Goedkeuren" }),
|
||||
};
|
||||
req.Headers.Add("X-Role", "admin"); // zorgverlener, no X-Medewerker
|
||||
Assert.Equal(HttpStatusCode.Forbidden, (await _client.SendAsync(req)).StatusCode);
|
||||
}
|
||||
finally
|
||||
{
|
||||
await DeleteAsAdmin(a.Id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# WP-65 — Behandelportal: zaak detail + beoordeling (decision) screen
|
||||
|
||||
Status: in progress (65a — detail read — done; 65b — decision write — not started)
|
||||
Status: done (65a — detail read — done; 65b — decision write — done)
|
||||
Phase: 11 — Behandelportal
|
||||
|
||||
## Why
|
||||
@@ -80,7 +80,7 @@ one gate for every behandelaar endpoint, `resource` feeding the audit row; the o
|
||||
|
||||
**Decision-readiness (`BeoordelingDecisionsDto.canBesluiten`) ships now, not deferred to
|
||||
65b:** `BeoordelingRules.CanDecide(AanvraagStatusTag)` only inspects the aanvraag's current
|
||||
*computed* status tag (`Ingediend`/`InBehandeling`/`MeerInfoGevraagd` → decidable;
|
||||
_computed_ status tag (`Ingediend`/`InBehandeling`/`MeerInfoGevraagd` → decidable;
|
||||
`Goedgekeurd`/`Afgewezen` → not) — no persisted "was a decision recorded" field exists yet,
|
||||
so this pure rule needed nothing from 65b's eventual migration to be correct today. 65b adds
|
||||
the mutation, the `Besluit` enum, and the transition-legality check that reuses this same
|
||||
@@ -110,14 +110,62 @@ yet — 65a is infrastructure the decision screen needs, not a slice of the AC i
|
||||
test, backend test — 197/197 including this WP's 9 new tests). Only the api-client-drift
|
||||
step shows the expected pre-commit diff (this WP's own uncommitted endpoint).
|
||||
|
||||
## Progress notes (65b — done)
|
||||
|
||||
**Backend (`POST /beoordeling/{id}/besluit`):** runs against `ApplicationStore` directly
|
||||
(not the `IZaakSource` seam) — same reasoning as 65a's GET: a new seam method would force
|
||||
an `OpenZaakZaakSource` write now, which stays WP-66's surface. A new `Besluit` enum
|
||||
(`Goedkeuren | Afwijzen | MeerInfoOpvragen`) backs a nullable `Aanvraag.BesluitStatus` +
|
||||
`Aanvraag.BesluitToelichting` column pair (EF migration `BesluitStatus`). Like every other
|
||||
enum in this backend, `Besluit` never crosses the wire as a raw C# enum — no
|
||||
`JsonStringEnumConverter` is configured, so `RecordBesluitRequest.Besluit` is a plain
|
||||
`string`, parsed with `Enum.TryParse` (400 on an unknown name) — the same wire convention
|
||||
`AanvraagStatusDto.Tag` already established. The endpoint reuses
|
||||
`BeoordelingRules.CanDecide` — the SAME function the read side's `canBesluiten` flag calls
|
||||
— as the transition-legality check, so the two can never drift (409 on an illegal
|
||||
transition, e.g. deciding an already-`Goedgekeurd` case again). Toelichting is required
|
||||
(400) for Afwijzen/MeerInfoOpvragen, optional for Goedkeuren — enforced server-side because
|
||||
the published `AanvraagStatusDto`'s `Reden` field is non-optional on those two tags (the
|
||||
FE's existing `parseBeoordelingStatus` already required it). `Mappers.ToStatusDto` gained
|
||||
"a recorded decision wins" between the submit-time `Reden` check and the auto-approve
|
||||
computation — the two never collide in practice (a submit-time-rejected case is already
|
||||
terminal and never reaches the werkvoorraad/beoordeling screens, so no behandelaar ever
|
||||
records a besluit on one). `MeerInfoGevraagd` is not terminal: `CanDecide` still allows a
|
||||
further besluit afterwards, so a behandelaar can ask for info, then later approve/reject
|
||||
once it arrives — the same `BesluitStatus` column is simply overwritten.
|
||||
|
||||
**FE:** `besluit.machine.ts` is the same single-step Editing/Submitting/Submitted/Failed
|
||||
union as `change-request.machine.ts` (form-machine skill) — `Draft.besluit` stays a raw
|
||||
string (parsed into the narrow `BesluitTag` union only in `validate`, "parse, don't
|
||||
validate"), so the generic `SetField` reducer case needs no per-field typing gymnastics.
|
||||
`besluit-form` (organism) composes `RadioGroupComponent` (the three actions) +
|
||||
`FormFieldComponent`/`TextInputComponent` (toelichting, plain single-line — no textarea
|
||||
atom exists and this form doesn't justify adding one) — no new shared atom. On a
|
||||
successful decision it emits `decided`, and `BeoordelingPage` just calls
|
||||
`BeoordelingStore.reload()` — the server is the authority on the new status, the page
|
||||
never guesses it. The form only renders when the server's `canBesluiten` flag is true
|
||||
(ADR-0001: render the decision, don't recompute the lifecycle).
|
||||
|
||||
Re-ran the full acceptance-criteria smoke by hand against `LocalZaakSource`: created a
|
||||
manual registratie case, opened it via werkvoorraad → beoordeling, recorded Afwijzen with a
|
||||
toelichting (status → Afgewezen, reason shown), confirmed a further besluit on that same
|
||||
case now 409s. `npm run ci` green (lint, dep:check ×2, format:check, check:tokens, all four
|
||||
projects' test suites, both apps' localized `nl`+`en` builds, backend `dotnet format
|
||||
--verify-no-changes` + `dotnet test` — 201/201 including this WP's 5 new tests, api-client
|
||||
regenerated). One pre-existing, unrelated finding: `format:check` was already red on this
|
||||
branch before this session touched anything — `docs/project/backlog/README.md` has a
|
||||
long-standing prettier drift (a big markdown table) untouched by this WP; fixed the same
|
||||
class of drift in this file's own body (`*computed*` → `_computed_`) since this WP was
|
||||
already editing it, left `README.md` alone as out of scope.
|
||||
|
||||
## Acceptance criteria
|
||||
|
||||
- [ ] A medewerker can view one aanvraag's detail and record a decision that advances its
|
||||
- [x] A medewerker can view one aanvraag's detail and record a decision that advances its
|
||||
status.
|
||||
- [ ] Illegal transitions are rejected server-side (tested).
|
||||
- [ ] End-to-end smoke: werkvoorraad → detail → decision → status change reflected back
|
||||
- [x] Illegal transitions are rejected server-side (tested).
|
||||
- [x] End-to-end smoke: werkvoorraad → detail → decision → status change reflected back
|
||||
in the queue.
|
||||
- [ ] `npm run ci` (behandelportal app) + `dotnet test` green.
|
||||
- [x] `npm run ci` (behandelportal app) + `dotnet test` green.
|
||||
|
||||
## Verification
|
||||
|
||||
|
||||
@@ -1165,6 +1165,71 @@ export class ApiClient {
|
||||
return Promise.resolve<BeoordelingViewDto>(null as any);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return OK
|
||||
*/
|
||||
besluit(id: string, body: RecordBesluitRequest): Promise<RecordBesluitResponse> {
|
||||
let url_ = this.baseUrl + "/api/v1/beoordeling/{id}/besluit";
|
||||
if (id === undefined || id === null)
|
||||
throw new globalThis.Error("The parameter 'id' must be defined.");
|
||||
url_ = url_.replace("{id}", encodeURIComponent("" + id));
|
||||
url_ = url_.replace(/[?&]$/, "");
|
||||
|
||||
const content_ = JSON.stringify(body);
|
||||
|
||||
let options_: RequestInit = {
|
||||
body: content_,
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"Accept": "application/json"
|
||||
}
|
||||
};
|
||||
|
||||
return this.http.fetch(url_, options_).then((_response: Response) => {
|
||||
return this.processBesluit(_response);
|
||||
});
|
||||
}
|
||||
|
||||
protected processBesluit(response: Response): Promise<RecordBesluitResponse> {
|
||||
const status = response.status;
|
||||
let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); };
|
||||
if (status === 200) {
|
||||
return response.text().then((_responseText) => {
|
||||
let result200: any = null;
|
||||
result200 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver) as RecordBesluitResponse;
|
||||
return result200;
|
||||
});
|
||||
} else if (status === 400) {
|
||||
return response.text().then((_responseText) => {
|
||||
let result400: any = null;
|
||||
result400 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver) as ProblemDetails;
|
||||
return throwException("Bad Request", status, _responseText, _headers, result400);
|
||||
});
|
||||
} else if (status === 403) {
|
||||
return response.text().then((_responseText) => {
|
||||
let result403: any = null;
|
||||
result403 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver) as ProblemDetails;
|
||||
return throwException("Forbidden", status, _responseText, _headers, result403);
|
||||
});
|
||||
} else if (status === 404) {
|
||||
return response.text().then((_responseText) => {
|
||||
return throwException("Not Found", status, _responseText, _headers);
|
||||
});
|
||||
} else if (status === 409) {
|
||||
return response.text().then((_responseText) => {
|
||||
let result409: any = null;
|
||||
result409 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver) as ProblemDetails;
|
||||
return throwException("Conflict", status, _responseText, _headers, result409);
|
||||
});
|
||||
} else if (status !== 200 && status !== 204) {
|
||||
return response.text().then((_responseText) => {
|
||||
return throwException("An unexpected server error occurred.", status, _responseText, _headers);
|
||||
});
|
||||
}
|
||||
return Promise.resolve<RecordBesluitResponse>(null as any);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return No Content
|
||||
*/
|
||||
@@ -2248,6 +2313,15 @@ export interface PublishOrgTemplateResponse {
|
||||
affectedUnsentBriefs?: number;
|
||||
}
|
||||
|
||||
export interface RecordBesluitRequest {
|
||||
besluit?: string | undefined;
|
||||
toelichting?: string | undefined;
|
||||
}
|
||||
|
||||
export interface RecordBesluitResponse {
|
||||
status?: AanvraagStatusDto;
|
||||
}
|
||||
|
||||
export interface ReferentieResponse {
|
||||
referentie?: string | undefined;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user