feat(workflow): beoordeling escalation to teamlead after 14 days (S-14, closes #15) (#99)
All checks were successful
All checks were successful
## What & why S-14: a beoordeling a behandelaar does not pick up within **14 days** escalates to the **teamlead**. A non-interrupting `P14D` boundary timer on the `Beoordelen` user task fires an external-worker task (`BeoordelingEscaleren`); the domain's escalation worker reassigns the still-open task's candidate group from `behandelaar` to `teamlead`. The task keeps its identity — only who may claim it changes. The escalation-via-external-worker decision is recorded in **ADR-0015** (proposal #98); it upholds §8.2 (the Workflow Client stays the only code that talks to Flowable) and keeps Flowable a stock image. Closes #15 ## Definition of Done - [x] Linked Gitea issue (above). - [x] Failing test committed before the implementation. - [x] Implementation makes the test pass; refactor commit if structure improved. - [x] Conventional Commits referencing the issue (`refs #NN`). - [x] CI green — all Gitea Actions jobs. - [x] `docker compose up` from a fresh clone reaches green health checks within 3 minutes (no new services; escalation is additive to the domain worker). - [x] Docs updated (ADR-0015, demo note). - [x] ADR added (`docs/architecture/adr-0015-beoordeling-escalation.md`). - [x] Demo note in `docs/demo-script.md`. ## How it was built (TDD) - **Workflow Client** (`IBeoordelingEscalatieClient`): acquire `BeoordelingEscaleren` jobs → find the open `Beoordelen` task in the instance → add `teamlead`/remove `behandelaar` candidate group → complete the job. Red → green. - **Escalation drain loop** (`BeoordelingEscalatieProcessor`) + hosted `BeoordelingEscalatiePump`, mirroring the OpenZaak worker. Red → green. - **BPMN**: non-interrupting `P14D` boundary timer on `Beoordelen` → external task → escalation end. - **Both branches** (escalate after timeout; no-op when completed in time) covered by the `Een beoordeling escaleren` acceptance scenarios + Workflow Client unit tests. - **Live integration**: `verify-domain` fires the timer early via Flowable's management API and asserts the reassignment to teamlead. ## Notes for reviewers - Interface segregation: escalation is on `IBeoordelingEscalatieClient`, separate from the OpenZaak worker's `IExternalWorkerClient`. - Reassignment is two REST hops (add teamlead, remove behandelaar); idempotent on redelivery — see ADR-0015 consequences. - Local checks green: domain unit tests (104), acceptance (13), `dotnet format --verify-no-changes`, Release build (0 errors), **domain mutation 96.69%** (break 90). The `run-domain-check.sh` escalation path is CI-verified on verify-stack (local full-stack run is constrained here). - `BeoordelingEscalatiePump` excluded from mutation, mirroring the existing `OpenZaakJobPump` exclusion. Reviewed-on: #99
This commit was merged in pull request #99.
This commit is contained in:
@@ -298,3 +298,48 @@ interrupting boundary event ends it → the werkbak drops the case.
|
||||
> DigiD submit → trek aanvraag in → ingetrokken is the Playwright happy path
|
||||
> (`tests/e2e/withdrawal.spec.ts`); the owner-scoping + workflow cancellation are covered by the
|
||||
> `Een registratie intrekken` acceptance scenarios and the domain live check.
|
||||
|
||||
## S-14 — Beoordeling escalation: 14 days unclaimed → teamlead (#15, ADR-0015)
|
||||
|
||||
A beoordeling a behandelaar does not pick up within 14 days escalates to the teamlead. A
|
||||
non-interrupting boundary timer on the `Beoordelen` task fires a `BeoordelingEscaleren` external task;
|
||||
the domain's escalation worker reassigns the still-open task's candidate group from `behandelaar` to
|
||||
`teamlead`, so it moves from the behandelaar werkbak into the teamlead's. The `Beoordelen` task keeps
|
||||
its identity throughout — only who may claim it changes.
|
||||
|
||||
The timer is 14 days, so the demo fires it early through Flowable's management API (exactly what the
|
||||
verify-domain check automates):
|
||||
|
||||
```bash
|
||||
# 1. Submit at the self-service portal (http://localhost:8140/, jan-burger / test123). The case
|
||||
# parks at Beoordelen, visible in the behandelaar werkbak (http://localhost:8142/, merel-behandelaar)
|
||||
# but NOT claimed.
|
||||
#
|
||||
# 2. Find the parked instance and its Beoordelen task, then fire the boundary timer early:
|
||||
FL=http://localhost:8090/flowable-rest/service
|
||||
PID=$(curl -s -u rest-admin:test -X POST "$FL/query/tasks" -H 'Content-Type: application/json' \
|
||||
-d '{"processDefinitionKey":"registratie","taskDefinitionKey":"Beoordelen"}' \
|
||||
| python3 -c 'import sys,json;print(json.load(sys.stdin)["data"][0]["processInstanceId"])')
|
||||
TID=$(curl -s -u rest-admin:test -X POST "$FL/query/tasks" -H 'Content-Type: application/json' \
|
||||
-d '{"processDefinitionKey":"registratie","taskDefinitionKey":"Beoordelen"}' \
|
||||
| python3 -c 'import sys,json;print(json.load(sys.stdin)["data"][0]["id"])')
|
||||
TJ=$(curl -s -u rest-admin:test "$FL/management/timer-jobs?processInstanceId=$PID" \
|
||||
| python3 -c 'import sys,json;print(json.load(sys.stdin)["data"][0]["id"])')
|
||||
curl -s -u rest-admin:test -X POST "$FL/management/timer-jobs/$TJ" \
|
||||
-H 'Content-Type: application/json' -d '{"action":"move"}'
|
||||
AJ=$(curl -s -u rest-admin:test "$FL/management/jobs?processInstanceId=$PID" \
|
||||
| python3 -c 'import sys,json;print(json.load(sys.stdin)["data"][0]["id"])')
|
||||
curl -s -u rest-admin:test -X POST "$FL/management/jobs/$AJ" \
|
||||
-H 'Content-Type: application/json' -d '{"action":"execute"}'
|
||||
#
|
||||
# 3. Within a couple of poll cycles the task's candidate group flips to teamlead:
|
||||
curl -s -u rest-admin:test "$FL/runtime/tasks/$TID/identitylinks" # → [{"group":"teamlead","type":"candidate"}]
|
||||
```
|
||||
|
||||
**The path:** BPMN non-interrupting `P14D` boundary timer on `Beoordelen` → `BeoordelingEscaleren`
|
||||
external task → domain escalation worker (`BeoordelingEscalatiePump`) → Workflow Client swaps the task's
|
||||
candidate group behandelaar → teamlead (§8.2).
|
||||
|
||||
> Both branches (escalate after 14 days; no-op when completed in time) are covered by the
|
||||
> `Een beoordeling escaleren` acceptance scenarios and the Workflow Client unit tests; the timer firing
|
||||
> and reassignment are asserted live by the verify-domain check.
|
||||
|
||||
Reference in New Issue
Block a user