Enables dry-run checking before committing to case adoption. The preflight shares the same side-effect-free checks (steps 1–3) as the real take-ownership handler, so it cannot drift from what will actually succeed. Returns the same status codes and error shapes as the real endpoint (200 with wouldSucceed:true, or 409/404/422 if it would fail). Portal renders a "Vooraf controleren" button for legacy cases, surfaced through the existing actions block pattern. Confirmed in smoke.sh with two cases: one where preflight predicts success (and writes nothing), one where it predicts a named invariant failure (matching what the real call reproduces). Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
101 lines
3.1 KiB
TypeScript
101 lines
3.1 KiB
TypeScript
import { HttpErrorResponse } from '@angular/common/http';
|
|
import { Component, EventEmitter, Input, OnInit, Output, inject, signal } from '@angular/core';
|
|
import { FormsModule } from '@angular/forms';
|
|
|
|
import { ErrorsResponse, InvariantViolationResponse, MessageResponse } from '../../shared/api-error.types';
|
|
import { CaseDetailService } from '../case-detail.service';
|
|
import { ActionLink, ApplicantDetailsRequest } from '../case-detail.types';
|
|
|
|
@Component({
|
|
selector: 'app-edit-applicant-details',
|
|
imports: [FormsModule],
|
|
templateUrl: './edit-applicant-details.html',
|
|
styleUrl: './edit-applicant-details.css',
|
|
})
|
|
export class EditApplicantDetails implements OnInit {
|
|
private readonly caseDetailService = inject(CaseDetailService);
|
|
|
|
@Input({ required: true }) actionLink!: ActionLink;
|
|
@Input({ required: true }) initial!: ApplicantDetailsRequest;
|
|
|
|
@Output() readonly saved = new EventEmitter<void>();
|
|
|
|
surname = '';
|
|
initials = '';
|
|
street = '';
|
|
number = '';
|
|
postalCode = '';
|
|
city = '';
|
|
email = '';
|
|
phone = '';
|
|
preferredChannel = '';
|
|
|
|
fieldErrors: Record<string, string> = {};
|
|
banner: string | null = null;
|
|
readonly saving = signal(false);
|
|
|
|
get isWriteThrough(): boolean {
|
|
return this.actionLink.mode === 'writeThrough';
|
|
}
|
|
|
|
ngOnInit(): void {
|
|
this.surname = this.initial.surname;
|
|
this.initials = this.initial.initials;
|
|
this.street = this.initial.address?.street ?? '';
|
|
this.number = this.initial.address?.number ?? '';
|
|
this.postalCode = this.initial.address?.postalCode ?? '';
|
|
this.city = this.initial.address?.city ?? '';
|
|
this.email = this.initial.email ?? '';
|
|
this.phone = this.initial.phone ?? '';
|
|
this.preferredChannel = this.initial.preferredChannel;
|
|
}
|
|
|
|
submit(): void {
|
|
this.fieldErrors = {};
|
|
this.banner = null;
|
|
this.saving.set(true);
|
|
|
|
const body: ApplicantDetailsRequest = {
|
|
surname: this.surname,
|
|
initials: this.initials,
|
|
address: this.street ? { street: this.street, number: this.number, postalCode: this.postalCode, city: this.city } : null,
|
|
email: this.email || null,
|
|
phone: this.phone || null,
|
|
preferredChannel: this.preferredChannel,
|
|
};
|
|
|
|
this.caseDetailService.updateDetails(this.actionLink.href, body).subscribe({
|
|
next: () => {
|
|
this.saving.set(false);
|
|
this.saved.emit();
|
|
},
|
|
error: (error: HttpErrorResponse) => {
|
|
this.saving.set(false);
|
|
this.handleError(error);
|
|
},
|
|
});
|
|
}
|
|
|
|
private handleError(error: HttpErrorResponse): void {
|
|
switch (error.status) {
|
|
case 400: {
|
|
const body = error.error as ErrorsResponse;
|
|
this.fieldErrors = Object.fromEntries(body.errors.map((fieldError) => [fieldError.field, fieldError.message]));
|
|
break;
|
|
}
|
|
case 422: {
|
|
const body = error.error as InvariantViolationResponse;
|
|
this.banner = `${body.invariant}: ${body.message}`;
|
|
break;
|
|
}
|
|
case 409: {
|
|
const body = error.error as MessageResponse;
|
|
this.banner = body.message;
|
|
break;
|
|
}
|
|
default:
|
|
this.banner = 'Onbekende fout bij opslaan.';
|
|
}
|
|
}
|
|
}
|