Builds the four-seam, three-write-path reference demo backend: case-framework (seam D stand-in), legacy-backend/frontend (SQL Server, seams A/B/C targets), and new-backend (Domain/Application/Infrastructure.*/Api implementing the source resolver, take/release-ownership, write-through translator, and owned assessment flow), wired together via docker-compose with a plain placeholder frontend standing in for the Angular portal until Session 2. All 11 Architecture.Tests pass and scripts/smoke.sh passes end-to-end against a fresh `docker compose up`, covering acceptance criteria 1-3 and 7-22. Fixes two real domain bugs found only once the stack ran for real: the BSN eleven-proof checksum trivially passes all-zero digits, and the adoption mapper silently treated a partial legacy address as absent instead of failing loudly. Also fixes several environment-specific integration issues (rootless Podman/SELinux bind-mount permissions, a buildah NuGet layer-caching bug, SqlClient's invariant-globalization incompatibility, and an nginx path-prefix mismatch for the legacy frontend). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
158 lines
6.3 KiB
HTML
158 lines
6.3 KiB
HTML
<!doctype html>
|
|
<html lang="nl">
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<title>Behandel portaal (placeholder)</title>
|
|
</head>
|
|
<body>
|
|
<h1>Behandel portaal — werkvoorraad (placeholder, session 2 replaces this with Angular)</h1>
|
|
<p>
|
|
<label>Bucket: <select id="bucket"><option value="">Alles</option><option value="Open">Te beoordelen</option><option value="Beoordeeld">Beoordeeld</option><option value="Ingetrokken">Ingetrokken</option></select></label>
|
|
<label>Origin: <select id="origin"><option value="">alles</option><option value="Legacy">legacy</option><option value="Owned">nieuw proces</option></select></label>
|
|
<label>Zoek: <input id="search" type="text"></label>
|
|
<button onclick="loadWorklist()">Ververs</button>
|
|
</p>
|
|
<table border="1" cellpadding="4">
|
|
<thead>
|
|
<tr><th>Origin</th><th>Referentie</th><th>Naam</th><th>BSN</th><th>Ontvangen</th><th>Uitkomst</th><th>Processtatus</th></tr>
|
|
</thead>
|
|
<tbody id="rows"></tbody>
|
|
</table>
|
|
|
|
<h2>Detail</h2>
|
|
<pre id="detail">Kies een rij (klik erop) om details te laden.</pre>
|
|
<div id="actions"></div>
|
|
|
|
<script>
|
|
let currentKey = null;
|
|
|
|
async function loadWorklist() {
|
|
const bucket = document.getElementById('bucket').value;
|
|
const origin = document.getElementById('origin').value;
|
|
const search = document.getElementById('search').value;
|
|
const params = new URLSearchParams();
|
|
if (bucket) params.set('bucket', bucket);
|
|
if (origin) params.set('origin', origin);
|
|
if (search) params.set('search', search);
|
|
const res = await fetch('/api/worklist?' + params.toString());
|
|
const data = await res.json();
|
|
const rows = document.getElementById('rows');
|
|
rows.innerHTML = '';
|
|
for (const item of data.items) {
|
|
const tr = document.createElement('tr');
|
|
const key = item.origin === 'Legacy' ? `legacy/${item.legacyAanvraagId}` : `owned/${item.registrationApplicationId}`;
|
|
tr.innerHTML = `<td>${item.origin}</td><td>${item.legacyAanvraagId ?? item.registrationApplicationId}</td>` +
|
|
`<td>${item.surname} ${item.initials}</td><td>${item.bsn}</td><td>${item.receivedOn}</td>` +
|
|
`<td>${item.assessmentOutcome ?? ''}</td><td>${item.processStatus ?? 'n.v.t.'}</td>`;
|
|
tr.style.cursor = 'pointer';
|
|
tr.onclick = () => loadDetail(key);
|
|
rows.appendChild(tr);
|
|
}
|
|
}
|
|
|
|
async function loadDetail(key) {
|
|
currentKey = key;
|
|
const res = await fetch(`/api/worklist/${key}`);
|
|
if (!res.ok) {
|
|
document.getElementById('detail').textContent = `Fout: ${res.status}`;
|
|
document.getElementById('actions').innerHTML = '';
|
|
return;
|
|
}
|
|
const detail = await res.json();
|
|
document.getElementById('detail').textContent = JSON.stringify(detail, null, 2);
|
|
renderActions(detail);
|
|
}
|
|
|
|
function renderActions(detail) {
|
|
const el = document.getElementById('actions');
|
|
el.innerHTML = '';
|
|
|
|
const a = detail.actions;
|
|
|
|
addButton(el, `Gegevens wijzigen (${a.editApplicantDetails.mode})`, async () => {
|
|
const surname = prompt('Surname', detail.surname);
|
|
if (surname === null) return;
|
|
const initials = prompt('Initials', detail.initials);
|
|
const street = prompt('Street (leeg = geen adres)', detail.address?.street ?? '');
|
|
const number = street ? prompt('Number', detail.address?.number ?? '') : null;
|
|
const postalCode = street ? prompt('Postal code', detail.address?.postalCode ?? '') : null;
|
|
const city = street ? prompt('City', detail.address?.city ?? '') : null;
|
|
const email = prompt('Email', detail.email ?? '');
|
|
const phone = prompt('Phone', detail.phone ?? '');
|
|
const preferredChannel = prompt('Preferred channel (Post/Email)', detail.preferredChannel);
|
|
const body = {
|
|
surname, initials,
|
|
address: street ? { street, number, postalCode, city } : null,
|
|
email, phone, preferredChannel,
|
|
};
|
|
const res = await fetch(a.editApplicantDetails.href, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body) });
|
|
const text = await res.text();
|
|
alert(`${res.status}: ${text}`);
|
|
loadDetail(currentKey);
|
|
});
|
|
|
|
if (a.recordAssessment.mode === 'redirect') {
|
|
addLink(el, 'Beoordeling vastleggen (verlaat portaal — legacy valideert)', a.recordAssessment.href);
|
|
} else {
|
|
addButton(el, 'Beoordeling vastleggen', async () => {
|
|
const verified = prompt('Verified items (comma separated: document,land,datum)', 'document,land,datum');
|
|
const exceptionReason = verified ? null : prompt('Exception reason (verplicht als geen items geverifieerd)');
|
|
const outcome = prompt('Outcome (Approved/Rejected)', 'Approved');
|
|
const rejectionCategory = outcome === 'Rejected' ? prompt('Rejection category (onvolledig/niet erkend/niet bevoegd/anders)') : null;
|
|
const motivation = prompt('Motivation (min 20 chars, 50 if "anders")');
|
|
const body = {
|
|
verifiedItems: verified ? verified.split(',').map(s => s.trim()) : [],
|
|
exceptionReason, outcome, rejectionCategory, motivation,
|
|
};
|
|
const res = await fetch(a.recordAssessment.href, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body) });
|
|
const text = await res.text();
|
|
alert(`${res.status}: ${text}`);
|
|
loadDetail(currentKey);
|
|
});
|
|
}
|
|
|
|
if (a.takeOwnership) {
|
|
addButton(el, 'Overnemen in nieuw systeem', async () => {
|
|
if (!confirm('Dit maakt het nieuwe systeem eigenaar van deze zaak. Doorgaan?')) return;
|
|
const res = await fetch(a.takeOwnership.href, { method: 'POST' });
|
|
const text = await res.text();
|
|
alert(`${res.status}: ${text}`);
|
|
loadWorklist();
|
|
if (res.ok) {
|
|
const body = JSON.parse(text);
|
|
loadDetail(`owned/${body.registrationApplicationId}`);
|
|
}
|
|
});
|
|
}
|
|
|
|
if (a.releaseOwnership) {
|
|
addButton(el, 'Overname terugdraaien', async () => {
|
|
const res = await fetch(a.releaseOwnership.href, { method: 'DELETE' });
|
|
const text = await res.text();
|
|
alert(`${res.status}: ${text}`);
|
|
loadWorklist();
|
|
});
|
|
}
|
|
}
|
|
|
|
function addButton(container, label, onClick) {
|
|
const btn = document.createElement('button');
|
|
btn.textContent = label;
|
|
btn.onclick = onClick;
|
|
container.appendChild(btn);
|
|
container.appendChild(document.createElement('br'));
|
|
}
|
|
|
|
function addLink(container, label, href) {
|
|
const a = document.createElement('a');
|
|
a.textContent = label;
|
|
a.href = href;
|
|
container.appendChild(a);
|
|
container.appendChild(document.createElement('br'));
|
|
}
|
|
|
|
loadWorklist();
|
|
</script>
|
|
</body>
|
|
</html>
|