Compare commits
5 Commits
main
...
feat/14-dm
| Author | SHA1 | Date | |
|---|---|---|---|
| 4a7dc07786 | |||
| 5e1975ae84 | |||
| dc9cf41dc8 | |||
| 6955e0ff98 | |||
| 1969297c97 |
66
docs/architecture/adr-0016-diploma-eligibility-dmn.md
Normal file
66
docs/architecture/adr-0016-diploma-eligibility-dmn.md
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
# ADR-0016: Diploma eligibility is a DMN evaluated inline as a BPMN businessRuleTask
|
||||||
|
|
||||||
|
- **Status:** Accepted
|
||||||
|
- **Date:** 2026-07-17
|
||||||
|
- **Deciders:** Respellion engineering
|
||||||
|
- **Relates to:** S-13 (#14); proposal #100. Builds on ADR-0009 (external-task worker / Workflow
|
||||||
|
Client), ADR-0014/0015 (the boundary-event and routing constructs on the registratie process).
|
||||||
|
|
||||||
|
## Context
|
||||||
|
|
||||||
|
S-13 adds flow 4: a foreign diploma must get an extra CBGV-advies assessment before beoordeling
|
||||||
|
(PRD §5). The eligibility decision — domestic goes straight to beoordeling, foreign routes through
|
||||||
|
CBGV-advies — needs a home. The Flowable REST app bundles a DMN engine, and the same
|
||||||
|
`repository/deployments` machinery that deploys `registratie.bpmn` can deploy a `.dmn`. §8.2 makes
|
||||||
|
the Workflow Client the only code that talks to Flowable; the PRD frames the workflow as "BPMN + DMN
|
||||||
|
governing the registration workflow" (Flowable as a peer orchestration module).
|
||||||
|
|
||||||
|
The issue's wording ("a DMN decision table evaluated by the Domain Service via Workflow Client")
|
||||||
|
suggests the domain reaches into Flowable's DMN API to evaluate the decision and feeds the result
|
||||||
|
back. That is one option; it is not the only one, and it is not the cleanest.
|
||||||
|
|
||||||
|
## Decision
|
||||||
|
|
||||||
|
**The diploma-eligibility DMN is deployed to Flowable and evaluated inline by the registratie process
|
||||||
|
as a `businessRuleTask`; an exclusive gateway routes on its output. The domain's only new job is to
|
||||||
|
carry the diploma origin and pass it into the process as a start variable.**
|
||||||
|
|
||||||
|
- **The decision lives in the workflow.** `workflows/diploma-eligibility.dmn` maps `diplomaOrigin`
|
||||||
|
→ `route` (`Buitenlands` ⇒ `CBGV_ADVIES`, otherwise `DIRECT`). A `businessRuleTask`
|
||||||
|
(`flowable:type="dmn"`, `decisionTableReferenceKey=diploma-eligibility`) runs it between
|
||||||
|
`OpenZaakAanmaken` and `Beoordelen`, and an exclusive gateway sends `CBGV_ADVIES` through a new
|
||||||
|
`CBGVAdvies` user task before `Beoordelen`, `DIRECT` straight there.
|
||||||
|
- **The domain carries the input, not the decision.** The `Registration` aggregate gains a
|
||||||
|
`DiplomaOrigin` (Binnenlands/Buitenlands); `SubmitRegistration` passes it to
|
||||||
|
`StartRegistrationProcessAsync`, which sets it as the `diplomaOrigin` start variable. The domain
|
||||||
|
never evaluates the DMN and never learns the route — that is the process's concern.
|
||||||
|
- **Deployed like the BPMN.** The DMN is version-controlled in `workflows/` and deployed to the DMN
|
||||||
|
engine by the same `flowable-init` step (via `dmn-api/dmn-repository/deployments`), staged into the
|
||||||
|
`fl-bpmn` volume alongside the BPMN.
|
||||||
|
|
||||||
|
## Consequences
|
||||||
|
|
||||||
|
**Positive**
|
||||||
|
|
||||||
|
- The eligibility rule is a first-class, inspectable workflow artefact (matching the PRD's BPMN+DMN
|
||||||
|
framing); business users can read/adjust the decision table without touching domain code.
|
||||||
|
- §8.2 stays clean: the Workflow Client remains the only code talking to Flowable, and the decision
|
||||||
|
runs inside the process the client already started — no domain→Flowable round-trip for a decision.
|
||||||
|
- The domain change is minimal and additive: one value on the aggregate, one start variable.
|
||||||
|
|
||||||
|
**Negative / costs**
|
||||||
|
|
||||||
|
- Deviates from #14's literal "evaluated by the Domain Service via Workflow Client" wording (noted on
|
||||||
|
the issue). The outcome — DMN decides eligibility, foreign diplomas get the CBGV step — is unchanged.
|
||||||
|
- The DMN and its `businessRuleTask` wiring are validated live (verify-domain drives a foreign
|
||||||
|
registration through CBGV-advies and a domestic one straight to beoordeling, exercising both
|
||||||
|
branches), not in unit tests — consistent with ADR-0009/0014/0015. The domain unit/acceptance tests
|
||||||
|
cover only that the origin is carried into the process.
|
||||||
|
|
||||||
|
## Alternatives considered
|
||||||
|
|
||||||
|
- **Domain evaluates the DMN via the Workflow Client** (the issue's wording). Rejected: it couples
|
||||||
|
the domain to Flowable for a decision and splits the routing across two places (domain computes,
|
||||||
|
BPMN branches), for no benefit over letting the engine that owns the process own the decision.
|
||||||
|
- **Eligibility rules in domain C#.** Rejected: it moves a governable business decision out of the
|
||||||
|
DMN the PRD calls for, and hard-codes what the reference app is meant to demonstrate as data.
|
||||||
@@ -343,3 +343,42 @@ candidate group behandelaar → teamlead (§8.2).
|
|||||||
> Both branches (escalate after 14 days; no-op when completed in time) are covered by the
|
> 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
|
> `Een beoordeling escaleren` acceptance scenarios and the Workflow Client unit tests; the timer firing
|
||||||
> and reassignment are asserted live by the verify-domain check.
|
> and reassignment are asserted live by the verify-domain check.
|
||||||
|
|
||||||
|
## S-13 — Diploma-eligibility: foreign diplomas route through CBGV-advies (#14, ADR-0016)
|
||||||
|
|
||||||
|
A registration's diploma origin decides its route. A DMN `businessRuleTask` in the registratie
|
||||||
|
process evaluates the `diploma-eligibility` decision on the `diplomaOrigin` start variable: a
|
||||||
|
**foreign** (Buitenlands) diploma is routed through an extra **CBGV-advies** user task before
|
||||||
|
beoordeling; a **domestic** (Binnenlands) one goes straight to beoordeling. The decision lives in the
|
||||||
|
DMN, not in code — a beheerder can read and adjust the decision table directly.
|
||||||
|
|
||||||
|
The self-service portal's eIDAS→foreign wiring is a later slice; for now the origin is submitted to
|
||||||
|
the domain directly, so the demo drives it through the domain endpoint:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. Submit a foreign-diploma registration to the domain (note the returned Location/reference):
|
||||||
|
DOM=http://localhost:8080 # domain service
|
||||||
|
curl -s -i -X POST "$DOM/registrations" -H 'Content-Type: application/json' \
|
||||||
|
-d '{"bsn":"123456782","diplomaOrigin":"Buitenlands"}' | grep -i '^location:'
|
||||||
|
#
|
||||||
|
# 2. Once the zaak is opened, the process parks at the CBGV-advies task (NOT Beoordelen). In Flowable:
|
||||||
|
FL=http://localhost:8090/flowable-rest/service
|
||||||
|
curl -s -u rest-admin:test -X POST "$FL/query/tasks" -H 'Content-Type: application/json' \
|
||||||
|
-d '{"processDefinitionKey":"registratie","taskDefinitionKey":"CBGVAdvies"}' | python3 -m json.tool
|
||||||
|
#
|
||||||
|
# 3. Complete the CBGV-advies task; the case then advances to the regular Beoordelen task:
|
||||||
|
TID=$(curl -s -u rest-admin:test -X POST "$FL/query/tasks" -H 'Content-Type: application/json' \
|
||||||
|
-d '{"processDefinitionKey":"registratie","taskDefinitionKey":"CBGVAdvies"}' \
|
||||||
|
| python3 -c 'import sys,json;print(json.load(sys.stdin)["data"][0]["id"])')
|
||||||
|
curl -s -u rest-admin:test -X POST "$FL/runtime/tasks/$TID" \
|
||||||
|
-H 'Content-Type: application/json' -d '{"action":"complete"}'
|
||||||
|
# A domestic submission (default, or "Binnenlands") skips CBGV-advies and parks straight at Beoordelen.
|
||||||
|
```
|
||||||
|
|
||||||
|
**The path:** domain sets the `diplomaOrigin` start variable → registratie process DMN
|
||||||
|
`businessRuleTask` sets `route` → exclusive gateway → foreign: `CBGVAdvies` user task → `Beoordelen`;
|
||||||
|
domestic: `Beoordelen` directly (§8.2, ADR-0016).
|
||||||
|
|
||||||
|
> The domestic/foreign paths are covered by the `Een diploma op herkomst routeren` acceptance
|
||||||
|
> scenarios and unit tests (the origin is carried into the process); the DMN decision and the
|
||||||
|
> foreign→CBGV routing are asserted live by the verify-domain check.
|
||||||
|
|||||||
@@ -259,7 +259,7 @@ services:
|
|||||||
flowable-init:
|
flowable-init:
|
||||||
image: docker.io/curlimages/curl:latest
|
image: docker.io/curlimages/curl:latest
|
||||||
restart: "no"
|
restart: "no"
|
||||||
# registratie.bpmn is streamed into this external volume by infra/seed-config.sh.
|
# registratie.bpmn + diploma-eligibility.dmn are streamed into this external volume by infra/seed-config.sh.
|
||||||
volumes:
|
volumes:
|
||||||
- fl-bpmn:/work:ro
|
- fl-bpmn:/work:ro
|
||||||
command:
|
command:
|
||||||
@@ -267,7 +267,15 @@ services:
|
|||||||
- -c
|
- -c
|
||||||
- |
|
- |
|
||||||
base=http://flowable-rest:8080/flowable-rest/service/repository/deployments
|
base=http://flowable-rest:8080/flowable-rest/service/repository/deployments
|
||||||
|
dmnbase=http://flowable-rest:8080/flowable-rest/dmn-api/dmn-repository/deployments
|
||||||
until curl -sf -u rest-admin:test "$$base" >/dev/null 2>&1; do echo "waiting for flowable-rest..."; sleep 3; done
|
until curl -sf -u rest-admin:test "$$base" >/dev/null 2>&1; do echo "waiting for flowable-rest..."; sleep 3; done
|
||||||
|
# Deploy the diploma-eligibility DMN to the DMN engine so the registratie process's
|
||||||
|
# businessRuleTask can resolve it by key (S-13, ADR-0016).
|
||||||
|
if curl -s -u rest-admin:test "$$dmnbase?name=diploma-eligibility" | grep -q '"name":"diploma-eligibility"'; then
|
||||||
|
echo "diploma-eligibility already deployed; skip"
|
||||||
|
else
|
||||||
|
curl -sf -u rest-admin:test -F 'file=@/work/diploma-eligibility.dmn;filename=diploma-eligibility.dmn' "$$dmnbase" >/dev/null && echo "deployed diploma-eligibility"
|
||||||
|
fi
|
||||||
if curl -s -u rest-admin:test "$$base?name=registratie" | grep -q '"name":"registratie"'; then
|
if curl -s -u rest-admin:test "$$base?name=registratie" | grep -q '"name":"registratie"'; then
|
||||||
echo "registratie already deployed; skip"
|
echo "registratie already deployed; skip"
|
||||||
else
|
else
|
||||||
|
|||||||
@@ -35,12 +35,12 @@ services:
|
|||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
networks: [cg]
|
networks: [cg]
|
||||||
|
|
||||||
# Deploys workflows/registratie.bpmn via the REST API once flowable-rest is up.
|
# Deploys workflows/registratie.bpmn + diploma-eligibility.dmn via the REST API once flowable-rest is up.
|
||||||
# Idempotent: skips if a deployment named "registratie" already exists.
|
# Idempotent: skips each if a deployment of that name already exists.
|
||||||
flowable-init:
|
flowable-init:
|
||||||
image: docker.io/curlimages/curl:latest
|
image: docker.io/curlimages/curl:latest
|
||||||
restart: "no"
|
restart: "no"
|
||||||
# registratie.bpmn is streamed into this external volume by infra/seed-config.sh.
|
# registratie.bpmn + diploma-eligibility.dmn are streamed into this external volume by infra/seed-config.sh.
|
||||||
volumes:
|
volumes:
|
||||||
- fl-bpmn:/work:ro
|
- fl-bpmn:/work:ro
|
||||||
command:
|
command:
|
||||||
@@ -48,7 +48,15 @@ services:
|
|||||||
- -c
|
- -c
|
||||||
- |
|
- |
|
||||||
base=http://flowable-rest:8080/flowable-rest/service/repository/deployments
|
base=http://flowable-rest:8080/flowable-rest/service/repository/deployments
|
||||||
|
dmnbase=http://flowable-rest:8080/flowable-rest/dmn-api/dmn-repository/deployments
|
||||||
until curl -sf -u rest-admin:test "$$base" >/dev/null 2>&1; do echo "waiting for flowable-rest..."; sleep 3; done
|
until curl -sf -u rest-admin:test "$$base" >/dev/null 2>&1; do echo "waiting for flowable-rest..."; sleep 3; done
|
||||||
|
# Deploy the diploma-eligibility DMN to the DMN engine so the registratie process's
|
||||||
|
# businessRuleTask can resolve it by key (S-13, ADR-0016).
|
||||||
|
if curl -s -u rest-admin:test "$$dmnbase?name=diploma-eligibility" | grep -q '"name":"diploma-eligibility"'; then
|
||||||
|
echo "diploma-eligibility already deployed; skip"
|
||||||
|
else
|
||||||
|
curl -sf -u rest-admin:test -F 'file=@/work/diploma-eligibility.dmn;filename=diploma-eligibility.dmn' "$$dmnbase" >/dev/null && echo "deployed diploma-eligibility"
|
||||||
|
fi
|
||||||
if curl -s -u rest-admin:test "$$base?name=registratie" | grep -q '"name":"registratie"'; then
|
if curl -s -u rest-admin:test "$$base?name=registratie" | grep -q '"name":"registratie"'; then
|
||||||
echo "registratie already deployed; skip"
|
echo "registratie already deployed; skip"
|
||||||
else
|
else
|
||||||
|
|||||||
@@ -158,6 +158,49 @@ done
|
|||||||
[ -n "$gone" ] || { echo "FAIL — Beoordelen task for $reg_id2 still active after withdrawal" >&2; docker logs "$dom" 2>&1 | tail -15 >&2; exit 1; }
|
[ -n "$gone" ] || { echo "FAIL — Beoordelen task for $reg_id2 still active after withdrawal" >&2; docker logs "$dom" 2>&1 | tail -15 >&2; exit 1; }
|
||||||
echo "OK — withdrawal cancelled the Beoordelen task; the registratie process ended (ingetrokken)"
|
echo "OK — withdrawal cancelled the Beoordelen task; the registratie process ended (ingetrokken)"
|
||||||
|
|
||||||
|
# ── S-13: diploma-eligibility routing. A registration with a FOREIGN diploma must route through the
|
||||||
|
# extra CBGVAdvies user task before Beoordelen (the DMN businessRuleTask sets route=CBGV_ADVIES and the
|
||||||
|
# gateway branches, ADR-0016). The domestic DIRECT path is already proven by the first registration
|
||||||
|
# above, which parked straight at Beoordelen. ──────────────────────────────────────────────────────
|
||||||
|
cbgv_query='{"processDefinitionKey":"registratie","taskDefinitionKey":"CBGVAdvies","includeProcessVariables":true}'
|
||||||
|
echo ">> submitting a registration with a foreign diploma"
|
||||||
|
locf="$(docker run --rm --network "$net" curlimages/curl:latest \
|
||||||
|
-fsS -D - -o /dev/null -X POST "http://$dom_ip:8080/registrations" \
|
||||||
|
-H 'Content-Type: application/json' -d '{"bsn":"123456782","diplomaOrigin":"Buitenlands"}' \
|
||||||
|
| sed -n 's/\r$//; s/^[Ll]ocation: //p' | head -1)"
|
||||||
|
[ -n "$locf" ] || { echo "FAIL — foreign POST /registrations returned no Location" >&2; exit 1; }
|
||||||
|
reg_idf="${locf##*/}"
|
||||||
|
echo ">> foreign registration $reg_idf"
|
||||||
|
|
||||||
|
echo ">> polling Flowable for its CBGV-advies task (foreign diplomas route here first)"
|
||||||
|
cbgv_task=""
|
||||||
|
for _ in $(seq 1 30); do
|
||||||
|
resp="$(flcurl -X POST "$fl_base/query/tasks" -H 'Content-Type: application/json' -d "$cbgv_query" 2>/dev/null || true)"
|
||||||
|
cbgv_task="$(printf '%s' "$resp" | task_for_reg "$reg_idf")"
|
||||||
|
[ -n "$cbgv_task" ] && break
|
||||||
|
sleep 2
|
||||||
|
done
|
||||||
|
[ -n "$cbgv_task" ] || { echo "FAIL — no CBGVAdvies task appeared for the foreign registration $reg_idf" >&2; docker logs "$dom" 2>&1 | tail -15 >&2; exit 1; }
|
||||||
|
echo ">> CBGVAdvies task $cbgv_task is waiting"
|
||||||
|
|
||||||
|
echo ">> asserting it has NOT reached Beoordelen yet (still awaiting CBGV-advies)"
|
||||||
|
resp="$(flcurl -X POST "$fl_base/query/tasks" -H 'Content-Type: application/json' -d "$query")"
|
||||||
|
early="$(printf '%s' "$resp" | task_for_reg "$reg_idf")"
|
||||||
|
[ -z "$early" ] || { echo "FAIL — foreign registration reached Beoordelen ($early) before CBGV-advies" >&2; exit 1; }
|
||||||
|
|
||||||
|
echo ">> completing the CBGV-advies task"
|
||||||
|
flcurl -X POST "$fl_base/runtime/tasks/$cbgv_task" -H 'Content-Type: application/json' -d '{"action":"complete"}' >/dev/null
|
||||||
|
|
||||||
|
echo ">> asserting it now advances to Beoordelen"
|
||||||
|
onward=""
|
||||||
|
for _ in $(seq 1 15); do
|
||||||
|
resp="$(flcurl -X POST "$fl_base/query/tasks" -H 'Content-Type: application/json' -d "$query" 2>/dev/null || true)"
|
||||||
|
[ -n "$(printf '%s' "$resp" | task_for_reg "$reg_idf")" ] && { onward=1; break; }
|
||||||
|
sleep 2
|
||||||
|
done
|
||||||
|
[ -n "$onward" ] || { echo "FAIL — foreign registration did not reach Beoordelen after CBGV-advies" >&2; exit 1; }
|
||||||
|
echo "OK — foreign diploma routed through CBGV-advies, then on to Beoordelen (DMN + gateway)"
|
||||||
|
|
||||||
# ── S-14: escalation. A third registration parks at Beoordelen. We fire its 14-day boundary timer
|
# ── S-14: escalation. A third registration parks at Beoordelen. We fire its 14-day boundary timer
|
||||||
# early via Flowable's management API (the timer job is moved to executable and run), which routes a
|
# early via Flowable's management API (the timer job is moved to executable and run), which routes a
|
||||||
# parallel token to the BeoordelingEscaleren external task. The domain's escalation worker acquires
|
# parallel token to the BeoordelingEscaleren external task. The domain's escalation worker acquires
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ for key in "$@"; do
|
|||||||
oz) populate rr-oz-config "$here/openzaak/setup_configuration/." ;;
|
oz) populate rr-oz-config "$here/openzaak/setup_configuration/." ;;
|
||||||
nrc) populate rr-nrc-config "$here/opennotificaties/setup_configuration/." ;;
|
nrc) populate rr-nrc-config "$here/opennotificaties/setup_configuration/." ;;
|
||||||
kc) populate rr-kc-realms "$here/keycloak/realms/." ;;
|
kc) populate rr-kc-realms "$here/keycloak/realms/." ;;
|
||||||
fl) populate rr-fl-bpmn "$here/../workflows/registratie.bpmn" ;;
|
fl) populate rr-fl-bpmn "$here/../workflows/." ;; # registratie.bpmn + diploma-eligibility.dmn
|
||||||
*) echo "unknown seed key: $key" >&2; exit 2 ;;
|
*) echo "unknown seed key: $key" >&2; exit 2 ;;
|
||||||
esac
|
esac
|
||||||
done
|
done
|
||||||
|
|||||||
@@ -48,7 +48,12 @@ app.MapGet("/health", () => "Healthy");
|
|||||||
// a location to read the registration's progress (ADR-0009, eventual consistency).
|
// a location to read the registration's progress (ADR-0009, eventual consistency).
|
||||||
app.MapPost("/registrations", async (SubmitRegistrationRequest body, SubmitRegistration submit, CancellationToken ct) =>
|
app.MapPost("/registrations", async (SubmitRegistrationRequest body, SubmitRegistration submit, CancellationToken ct) =>
|
||||||
{
|
{
|
||||||
var id = await submit.HandleAsync(new SubmitRegistrationCommand(body.Bsn), ct);
|
// Diploma origin defaults to domestic; a foreign (eIDAS) submission passes "Buitenlands" so the
|
||||||
|
// workflow's DMN routes it through CBGV-advies (S-13). An unknown value is a bad request.
|
||||||
|
if (!Enum.TryParse<DiplomaOrigin>(body.DiplomaOrigin, ignoreCase: true, out var origin) && body.DiplomaOrigin is not null)
|
||||||
|
return Results.BadRequest(new { error = $"Unknown diplomaOrigin '{body.DiplomaOrigin}'. Expected 'Binnenlands' or 'Buitenlands'." });
|
||||||
|
|
||||||
|
var id = await submit.HandleAsync(new SubmitRegistrationCommand(body.Bsn, origin), ct);
|
||||||
return Results.Accepted($"/registrations/{id}", new RegistrationResponse(id.ToString(), RegistrationStatus.Ingediend.ToString(), null));
|
return Results.Accepted($"/registrations/{id}", new RegistrationResponse(id.ToString(), RegistrationStatus.Ingediend.ToString(), null));
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -117,7 +122,7 @@ app.MapGet("/registrations/{id}", async (string id, IRegistrationStore store, Ca
|
|||||||
|
|
||||||
await app.RunAsync();
|
await app.RunAsync();
|
||||||
|
|
||||||
public sealed record SubmitRegistrationRequest(string Bsn);
|
public sealed record SubmitRegistrationRequest(string Bsn, string? DiplomaOrigin = null);
|
||||||
|
|
||||||
public sealed record DecideRequest(string Besluit);
|
public sealed record DecideRequest(string Besluit);
|
||||||
|
|
||||||
|
|||||||
@@ -11,10 +11,12 @@ public interface IWorkflowClient
|
|||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Start one <c>registratie</c> process instance for the given registration, carrying the
|
/// Start one <c>registratie</c> process instance for the given registration, carrying the
|
||||||
/// registration id so the <c>OpenZaakAanmaken</c> external task can be correlated back to its
|
/// registration id (so the <c>OpenZaakAanmaken</c> external task can be correlated back to its
|
||||||
/// aggregate. Returns the process instance id.
|
/// aggregate) and the diploma origin (so the workflow's DMN can route foreign diplomas through
|
||||||
|
/// CBGV-advies, S-13). Returns the process instance id.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Task<string> StartRegistrationProcessAsync(RegistrationId registrationId, CancellationToken ct = default);
|
Task<string> StartRegistrationProcessAsync(
|
||||||
|
RegistrationId registrationId, DiplomaOrigin diplomaOrigin, CancellationToken ct = default);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Cancel a running <c>registratie</c> process on withdrawal (S-11): correlate the
|
/// Cancel a running <c>registratie</c> process on withdrawal (S-11): correlate the
|
||||||
|
|||||||
@@ -2,8 +2,10 @@ using Big.Domain;
|
|||||||
|
|
||||||
namespace Big.Application;
|
namespace Big.Application;
|
||||||
|
|
||||||
/// <summary>A zorgprofessional's request to register, in domain language. No ZGW concepts.</summary>
|
/// <summary>A zorgprofessional's request to register, in domain language. No ZGW concepts. The
|
||||||
public sealed record SubmitRegistrationCommand(string Bsn);
|
/// diploma origin defaults to domestic (the DigiD path); a foreign (eIDAS) submission sets it to
|
||||||
|
/// <see cref="DiplomaOrigin.Buitenlands"/> so the workflow's DMN routes it through CBGV-advies (S-13).</summary>
|
||||||
|
public sealed record SubmitRegistrationCommand(string Bsn, DiplomaOrigin DiplomaOrigin = DiplomaOrigin.Binnenlands);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The submit use case: create the <see cref="Registration"/> aggregate (INGEDIEND), persist it,
|
/// The submit use case: create the <see cref="Registration"/> aggregate (INGEDIEND), persist it,
|
||||||
@@ -18,13 +20,14 @@ public sealed class SubmitRegistration(IRegistrationStore store, IWorkflowClient
|
|||||||
{
|
{
|
||||||
ArgumentNullException.ThrowIfNull(command);
|
ArgumentNullException.ThrowIfNull(command);
|
||||||
|
|
||||||
var registration = Registration.Submit(command.Bsn);
|
var registration = Registration.Submit(command.Bsn, command.DiplomaOrigin);
|
||||||
|
|
||||||
// Persist before starting the process so the worker can correlate the OpenZaakAanmaken
|
// Persist before starting the process so the worker can correlate the OpenZaakAanmaken
|
||||||
// job back to an aggregate that already exists (ADR-0009).
|
// job back to an aggregate that already exists (ADR-0009).
|
||||||
await store.SaveAsync(registration, ct);
|
await store.SaveAsync(registration, ct);
|
||||||
|
|
||||||
var processInstanceId = await workflow.StartRegistrationProcessAsync(registration.Id, ct);
|
var processInstanceId = await workflow.StartRegistrationProcessAsync(
|
||||||
|
registration.Id, registration.DiplomaOrigin, ct);
|
||||||
registration.RecordProcessStarted(processInstanceId);
|
registration.RecordProcessStarted(processInstanceId);
|
||||||
await store.SaveAsync(registration, ct);
|
await store.SaveAsync(registration, ct);
|
||||||
|
|
||||||
|
|||||||
17
services/domain/Big.Domain/DiplomaOrigin.cs
Normal file
17
services/domain/Big.Domain/DiplomaOrigin.cs
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
namespace Big.Domain;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Where a zorgprofessional's diploma was issued. It is the input to the diploma-eligibility decision
|
||||||
|
/// (S-13): a <see cref="Buitenlands"/> (foreign) diploma routes the registratie through an extra
|
||||||
|
/// CBGV-advies assessment step, a <see cref="Binnenlands"/> (domestic) one goes straight to beoordeling.
|
||||||
|
/// The decision itself lives in the workflow's DMN, not here (ADR-0016); the domain only carries the
|
||||||
|
/// origin and hands it to the process as a start variable.
|
||||||
|
/// </summary>
|
||||||
|
public enum DiplomaOrigin
|
||||||
|
{
|
||||||
|
/// <summary>A Dutch (domestic) diploma. Default for a registration submitted via DigiD.</summary>
|
||||||
|
Binnenlands,
|
||||||
|
|
||||||
|
/// <summary>A foreign diploma (e.g. an eIDAS submission). Triggers the CBGV-advies step.</summary>
|
||||||
|
Buitenlands,
|
||||||
|
}
|
||||||
@@ -7,10 +7,11 @@ namespace Big.Domain;
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed class Registration
|
public sealed class Registration
|
||||||
{
|
{
|
||||||
private Registration(RegistrationId id, string bsn)
|
private Registration(RegistrationId id, string bsn, DiplomaOrigin diplomaOrigin)
|
||||||
{
|
{
|
||||||
Id = id;
|
Id = id;
|
||||||
Bsn = bsn;
|
Bsn = bsn;
|
||||||
|
DiplomaOrigin = diplomaOrigin;
|
||||||
Status = RegistrationStatus.Ingediend;
|
Status = RegistrationStatus.Ingediend;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -20,6 +21,10 @@ public sealed class Registration
|
|||||||
/// as the domain payload; the domain never constructs ZGW concepts from it (§8.1).</summary>
|
/// as the domain payload; the domain never constructs ZGW concepts from it (§8.1).</summary>
|
||||||
public string Bsn { get; }
|
public string Bsn { get; }
|
||||||
|
|
||||||
|
/// <summary>Where the diploma was issued. Rides along to the process as a start variable and
|
||||||
|
/// drives the diploma-eligibility DMN's foreign→CBGV-advies routing (S-13, ADR-0016).</summary>
|
||||||
|
public DiplomaOrigin DiplomaOrigin { get; }
|
||||||
|
|
||||||
public RegistrationStatus Status { get; private set; }
|
public RegistrationStatus Status { get; private set; }
|
||||||
|
|
||||||
/// <summary>The Flowable process instance driving this registration, once started.</summary>
|
/// <summary>The Flowable process instance driving this registration, once started.</summary>
|
||||||
@@ -28,11 +33,13 @@ public sealed class Registration
|
|||||||
/// <summary>The zaak the ACL opened for this registration, once the external task has run.</summary>
|
/// <summary>The zaak the ACL opened for this registration, once the external task has run.</summary>
|
||||||
public Uri? ZaakUrl { get; private set; }
|
public Uri? ZaakUrl { get; private set; }
|
||||||
|
|
||||||
/// <summary>Submit a new registration. It begins in <see cref="RegistrationStatus.Ingediend"/>.</summary>
|
/// <summary>Submit a new registration. It begins in <see cref="RegistrationStatus.Ingediend"/>.
|
||||||
public static Registration Submit(string bsn)
|
/// The diploma origin defaults to <see cref="DiplomaOrigin.Binnenlands"/> — the common DigiD path;
|
||||||
|
/// a foreign (eIDAS) submission passes <see cref="DiplomaOrigin.Buitenlands"/>.</summary>
|
||||||
|
public static Registration Submit(string bsn, DiplomaOrigin diplomaOrigin = DiplomaOrigin.Binnenlands)
|
||||||
{
|
{
|
||||||
ArgumentException.ThrowIfNullOrWhiteSpace(bsn);
|
ArgumentException.ThrowIfNullOrWhiteSpace(bsn);
|
||||||
return new Registration(RegistrationId.New(), bsn);
|
return new Registration(RegistrationId.New(), bsn, diplomaOrigin);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Record that the registratie workflow process has been started for this registration.</summary>
|
/// <summary>Record that the registratie workflow process has been started for this registration.</summary>
|
||||||
|
|||||||
@@ -24,15 +24,20 @@ public sealed class FlowableWorkflowClient(HttpClient http, FlowableOptions opti
|
|||||||
private const string BehandelaarGroup = "behandelaar";
|
private const string BehandelaarGroup = "behandelaar";
|
||||||
private const string TeamleadGroup = "teamlead";
|
private const string TeamleadGroup = "teamlead";
|
||||||
private const string RegistrationIdVariable = "registrationId";
|
private const string RegistrationIdVariable = "registrationId";
|
||||||
|
private const string DiplomaOriginVariable = "diplomaOrigin";
|
||||||
private const string ZaakUrlVariable = "zaakUrl";
|
private const string ZaakUrlVariable = "zaakUrl";
|
||||||
private const string BesluitVariable = "besluit";
|
private const string BesluitVariable = "besluit";
|
||||||
private const string IngetrokkenMessage = "RegistratieIngetrokken";
|
private const string IngetrokkenMessage = "RegistratieIngetrokken";
|
||||||
|
|
||||||
public async Task<string> StartRegistrationProcessAsync(RegistrationId registrationId, CancellationToken ct = default)
|
public async Task<string> StartRegistrationProcessAsync(
|
||||||
|
RegistrationId registrationId, DiplomaOrigin diplomaOrigin, CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
var request = new StartProcessRequest(
|
var request = new StartProcessRequest(
|
||||||
ProcessDefinitionKey,
|
ProcessDefinitionKey,
|
||||||
[new Variable(RegistrationIdVariable, "string", registrationId.ToString())]);
|
[
|
||||||
|
new Variable(RegistrationIdVariable, "string", registrationId.ToString()),
|
||||||
|
new Variable(DiplomaOriginVariable, "string", diplomaOrigin.ToString()),
|
||||||
|
]);
|
||||||
|
|
||||||
var created = await PostAsync<StartProcessRequest, ProcessInstance>(
|
var created = await PostAsync<StartProcessRequest, ProcessInstance>(
|
||||||
"service/runtime/process-instances", request, ct)
|
"service/runtime/process-instances", request, ct)
|
||||||
|
|||||||
@@ -32,12 +32,15 @@ internal sealed class FakeWorkflowClient(string processInstanceId = "proc-1", Ac
|
|||||||
: IWorkflowClient
|
: IWorkflowClient
|
||||||
{
|
{
|
||||||
public RegistrationId? StartedFor { get; private set; }
|
public RegistrationId? StartedFor { get; private set; }
|
||||||
|
public DiplomaOrigin? StartedWithOrigin { get; private set; }
|
||||||
public string? WithdrawnProcessInstanceId { get; private set; }
|
public string? WithdrawnProcessInstanceId { get; private set; }
|
||||||
|
|
||||||
public Task<string> StartRegistrationProcessAsync(RegistrationId registrationId, CancellationToken ct = default)
|
public Task<string> StartRegistrationProcessAsync(
|
||||||
|
RegistrationId registrationId, DiplomaOrigin diplomaOrigin, CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
onStart?.Invoke(registrationId);
|
onStart?.Invoke(registrationId);
|
||||||
StartedFor = registrationId;
|
StartedFor = registrationId;
|
||||||
|
StartedWithOrigin = diplomaOrigin;
|
||||||
return Task.FromResult(processInstanceId);
|
return Task.FromResult(processInstanceId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ public class FlowableWorkflowClientTests
|
|||||||
var client = Client(capture.Responds(HttpStatusCode.Created, """{"id":"pi-1"}"""));
|
var client = Client(capture.Responds(HttpStatusCode.Created, """{"id":"pi-1"}"""));
|
||||||
var rid = RegistrationId.New();
|
var rid = RegistrationId.New();
|
||||||
|
|
||||||
var pid = await client.StartRegistrationProcessAsync(rid);
|
var pid = await client.StartRegistrationProcessAsync(rid, DiplomaOrigin.Binnenlands);
|
||||||
|
|
||||||
Assert.Equal("pi-1", pid);
|
Assert.Equal("pi-1", pid);
|
||||||
Assert.Equal(HttpMethod.Post, capture.Seen!.Method);
|
Assert.Equal(HttpMethod.Post, capture.Seen!.Method);
|
||||||
@@ -38,6 +38,22 @@ public class FlowableWorkflowClientTests
|
|||||||
Assert.Contains($"\"value\":\"{rid}\"", capture.Body);
|
Assert.Contains($"\"value\":\"{rid}\"", capture.Body);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData(DiplomaOrigin.Binnenlands, "Binnenlands")]
|
||||||
|
[InlineData(DiplomaOrigin.Buitenlands, "Buitenlands")]
|
||||||
|
public async Task Start_posts_the_diploma_origin_as_a_process_variable(DiplomaOrigin origin, string expected)
|
||||||
|
{
|
||||||
|
// The diploma origin rides along as a start variable so the workflow's DMN can route foreign
|
||||||
|
// diplomas through CBGV-advies (S-13, ADR-0016).
|
||||||
|
var capture = new RequestCapture();
|
||||||
|
var client = Client(capture.Responds(HttpStatusCode.Created, """{"id":"pi-1"}"""));
|
||||||
|
|
||||||
|
await client.StartRegistrationProcessAsync(RegistrationId.New(), origin);
|
||||||
|
|
||||||
|
Assert.Contains("\"name\":\"diplomaOrigin\"", capture.Body);
|
||||||
|
Assert.Contains($"\"value\":\"{expected}\"", capture.Body);
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task Start_uses_the_configured_worker_credentials_and_defaults()
|
public async Task Start_uses_the_configured_worker_credentials_and_defaults()
|
||||||
{
|
{
|
||||||
@@ -125,7 +141,7 @@ public class FlowableWorkflowClientTests
|
|||||||
var client = Client(capture.Responds(HttpStatusCode.InternalServerError));
|
var client = Client(capture.Responds(HttpStatusCode.InternalServerError));
|
||||||
|
|
||||||
await Assert.ThrowsAsync<HttpRequestException>(
|
await Assert.ThrowsAsync<HttpRequestException>(
|
||||||
() => client.StartRegistrationProcessAsync(RegistrationId.New()));
|
() => client.StartRegistrationProcessAsync(RegistrationId.New(), DiplomaOrigin.Binnenlands));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
@@ -135,7 +151,7 @@ public class FlowableWorkflowClientTests
|
|||||||
var client = Client(capture.Responds(HttpStatusCode.Created, "null"));
|
var client = Client(capture.Responds(HttpStatusCode.Created, "null"));
|
||||||
|
|
||||||
var ex = await Assert.ThrowsAsync<InvalidOperationException>(
|
var ex = await Assert.ThrowsAsync<InvalidOperationException>(
|
||||||
() => client.StartRegistrationProcessAsync(RegistrationId.New()));
|
() => client.StartRegistrationProcessAsync(RegistrationId.New(), DiplomaOrigin.Binnenlands));
|
||||||
Assert.Contains("empty process-instance", ex.Message);
|
Assert.Contains("empty process-instance", ex.Message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,6 +16,15 @@ public class RegistrationTests
|
|||||||
Assert.Null(registration.ProcessInstanceId);
|
Assert.Null(registration.ProcessInstanceId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void A_registration_defaults_to_a_domestic_diploma()
|
||||||
|
=> Assert.Equal(DiplomaOrigin.Binnenlands, Registration.Submit("123456782").DiplomaOrigin);
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void A_foreign_diploma_submission_records_its_origin()
|
||||||
|
=> Assert.Equal(DiplomaOrigin.Buitenlands,
|
||||||
|
Registration.Submit("123456782", DiplomaOrigin.Buitenlands).DiplomaOrigin);
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[InlineData("")]
|
[InlineData("")]
|
||||||
[InlineData(" ")]
|
[InlineData(" ")]
|
||||||
|
|||||||
@@ -25,6 +25,19 @@ public class SubmitRegistrationTests
|
|||||||
Assert.Equal(2, store.SaveCount);
|
Assert.Equal(2, store.SaveCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task Submitting_a_foreign_diploma_carries_its_origin_to_the_process()
|
||||||
|
{
|
||||||
|
var store = new FakeRegistrationStore();
|
||||||
|
var workflow = new FakeWorkflowClient();
|
||||||
|
var handler = new SubmitRegistration(store, workflow);
|
||||||
|
|
||||||
|
var id = await handler.HandleAsync(new SubmitRegistrationCommand("123456782", DiplomaOrigin.Buitenlands));
|
||||||
|
|
||||||
|
Assert.Equal(DiplomaOrigin.Buitenlands, (await store.GetAsync(id))!.DiplomaOrigin);
|
||||||
|
Assert.Equal(DiplomaOrigin.Buitenlands, workflow.StartedWithOrigin);
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task Rejects_a_null_command_without_touching_the_store_or_workflow()
|
public async Task Rejects_a_null_command_without_touching_the_store_or_workflow()
|
||||||
{
|
{
|
||||||
|
|||||||
19
tests/acceptance/Features/EenDiplomaRouteren.feature
Normal file
19
tests/acceptance/Features/EenDiplomaRouteren.feature
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
# language: en
|
||||||
|
# Drives S-13 (#14). A registration's diploma origin decides its route: a domestic (Binnenlands)
|
||||||
|
# diploma goes straight to beoordeling, a foreign (Buitenlands) one is routed through an extra
|
||||||
|
# CBGV-advies step (PRD flow 4). The decision itself is a DMN evaluated inside the workflow
|
||||||
|
# (ADR-0016); the domain's part — verified here — is carrying the origin into the process so the DMN
|
||||||
|
# can route on it. The DMN evaluation and the CBGV routing are verified live (verify-domain).
|
||||||
|
Feature: Een diploma op herkomst routeren
|
||||||
|
Als register wil ik een aanvraag met een buitenlands diploma extra laten toetsen
|
||||||
|
zodat een CBGV-advies wordt ingewonnen voordat een behandelaar beoordeelt.
|
||||||
|
|
||||||
|
Scenario: Een binnenlands diploma start de registratie als binnenlands
|
||||||
|
Given a zorgprofessional with a "Binnenlands" diploma
|
||||||
|
When they submit their registration
|
||||||
|
Then the registratie process is started carrying a "Binnenlands" diploma
|
||||||
|
|
||||||
|
Scenario: Een buitenlands diploma start de registratie als buitenlands
|
||||||
|
Given a zorgprofessional with a "Buitenlands" diploma
|
||||||
|
When they submit their registration
|
||||||
|
Then the registratie process is started carrying a "Buitenlands" diploma
|
||||||
36
tests/acceptance/Steps/EenDiplomaRouterenSteps.cs
Normal file
36
tests/acceptance/Steps/EenDiplomaRouterenSteps.cs
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
using Acceptance.Support;
|
||||||
|
using Big.Application;
|
||||||
|
using Big.Domain;
|
||||||
|
using Reqnroll;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Acceptance.Steps;
|
||||||
|
|
||||||
|
/// <summary>Bindings for <c>EenDiplomaRouteren.feature</c> (S-13). Drives the submit use case against
|
||||||
|
/// in-memory ports and asserts the registratie process is started carrying the diploma origin — the
|
||||||
|
/// domain's contribution to flow 4. The DMN evaluation and the foreign→CBGV-advies routing it drives
|
||||||
|
/// are verified live (verify-domain); one instance per scenario.</summary>
|
||||||
|
[Binding]
|
||||||
|
[Scope(Feature = "Een diploma op herkomst routeren")]
|
||||||
|
public sealed class EenDiplomaRouterenSteps
|
||||||
|
{
|
||||||
|
private readonly InMemoryRegistrationStore _store = new();
|
||||||
|
private readonly InMemoryWorkflowClient _workflow = new();
|
||||||
|
private DiplomaOrigin _origin;
|
||||||
|
|
||||||
|
[Given("a zorgprofessional with a \"(.*)\" diploma")]
|
||||||
|
public void GivenAZorgprofessionalWithADiploma(string origin)
|
||||||
|
=> _origin = Enum.Parse<DiplomaOrigin>(origin, ignoreCase: true);
|
||||||
|
|
||||||
|
[When("they submit their registration")]
|
||||||
|
public async Task WhenTheySubmitTheirRegistration()
|
||||||
|
=> await new SubmitRegistration(_store, _workflow).HandleAsync(
|
||||||
|
new SubmitRegistrationCommand("123456782", _origin));
|
||||||
|
|
||||||
|
[Then("the registratie process is started carrying a \"(.*)\" diploma")]
|
||||||
|
public void ThenTheProcessIsStartedCarryingTheDiploma(string expected)
|
||||||
|
{
|
||||||
|
Assert.NotNull(_workflow.StartedFor);
|
||||||
|
Assert.Equal(Enum.Parse<DiplomaOrigin>(expected, ignoreCase: true), _workflow.StartedWithOrigin);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -12,11 +12,14 @@ public sealed class InMemoryWorkflowClient : IWorkflowClient
|
|||||||
public const string StartedProcessInstanceId = "proc-acc-1";
|
public const string StartedProcessInstanceId = "proc-acc-1";
|
||||||
|
|
||||||
public RegistrationId? StartedFor { get; private set; }
|
public RegistrationId? StartedFor { get; private set; }
|
||||||
|
public DiplomaOrigin? StartedWithOrigin { get; private set; }
|
||||||
public string? WithdrawnProcessInstanceId { get; private set; }
|
public string? WithdrawnProcessInstanceId { get; private set; }
|
||||||
|
|
||||||
public Task<string> StartRegistrationProcessAsync(RegistrationId registrationId, CancellationToken ct = default)
|
public Task<string> StartRegistrationProcessAsync(
|
||||||
|
RegistrationId registrationId, DiplomaOrigin diplomaOrigin, CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
StartedFor = registrationId;
|
StartedFor = registrationId;
|
||||||
|
StartedWithOrigin = diplomaOrigin;
|
||||||
return Task.FromResult(StartedProcessInstanceId);
|
return Task.FromResult(StartedProcessInstanceId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
38
workflows/diploma-eligibility.dmn
Normal file
38
workflows/diploma-eligibility.dmn
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!-- Diploma-eligibility decision (S-13, ADR-0016). Evaluated inline by the registratie process as a
|
||||||
|
BPMN businessRuleTask: given the diploma's origin, it sets the `route` the process should take.
|
||||||
|
A foreign (Buitenlands) diploma routes through the extra CBGV-advies assessment step; a domestic
|
||||||
|
one (or anything else) goes DIRECT to beoordeling. FIRST hit policy: the foreign rule wins, and
|
||||||
|
the empty-input catch-all is the default. -->
|
||||||
|
<definitions xmlns="https://www.omg.org/spec/DMN/20191111/MODEL/"
|
||||||
|
xmlns:flowable="http://flowable.org/dmn"
|
||||||
|
id="diplomaEligibilityDefinitions"
|
||||||
|
name="Diploma eligibility"
|
||||||
|
namespace="http://respellion.nl/big/dmn">
|
||||||
|
<decision id="diploma-eligibility" name="Diploma eligibility">
|
||||||
|
<decisionTable id="dt-diploma-eligibility" hitPolicy="FIRST">
|
||||||
|
<input id="in-origin" label="Diploma origin">
|
||||||
|
<inputExpression id="ie-origin" typeRef="string">
|
||||||
|
<text>diplomaOrigin</text>
|
||||||
|
</inputExpression>
|
||||||
|
</input>
|
||||||
|
<output id="out-route" label="Route" name="route" typeRef="string"/>
|
||||||
|
<rule id="rule-foreign">
|
||||||
|
<inputEntry id="rule-foreign-in">
|
||||||
|
<text>"Buitenlands"</text>
|
||||||
|
</inputEntry>
|
||||||
|
<outputEntry id="rule-foreign-out">
|
||||||
|
<text>"CBGV_ADVIES"</text>
|
||||||
|
</outputEntry>
|
||||||
|
</rule>
|
||||||
|
<rule id="rule-default">
|
||||||
|
<inputEntry id="rule-default-in">
|
||||||
|
<text></text>
|
||||||
|
</inputEntry>
|
||||||
|
<outputEntry id="rule-default-out">
|
||||||
|
<text>"DIRECT"</text>
|
||||||
|
</outputEntry>
|
||||||
|
</rule>
|
||||||
|
</decisionTable>
|
||||||
|
</decision>
|
||||||
|
</definitions>
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL"
|
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL"
|
||||||
xmlns:flowable="http://flowable.org/bpmn"
|
xmlns:flowable="http://flowable.org/bpmn"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI"
|
xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI"
|
||||||
xmlns:omgdc="http://www.omg.org/spec/DD/20100524/DC"
|
xmlns:omgdc="http://www.omg.org/spec/DD/20100524/DC"
|
||||||
xmlns:omgdi="http://www.omg.org/spec/DD/20100524/DI"
|
xmlns:omgdi="http://www.omg.org/spec/DD/20100524/DI"
|
||||||
@@ -20,7 +21,11 @@
|
|||||||
has not picked the task up within 14 days it fires a parallel token to an external-worker task
|
has not picked the task up within 14 days it fires a parallel token to an external-worker task
|
||||||
(BeoordelingEscaleren); the Workflow Client reassigns the still-open Beoordelen task from the
|
(BeoordelingEscaleren); the Workflow Client reassigns the still-open Beoordelen task from the
|
||||||
behandelaar group to teamlead (ADR-0015). The Beoordelen task stays open throughout — the timer
|
behandelaar group to teamlead (ADR-0015). The Beoordelen task stays open throughout — the timer
|
||||||
only changes who may claim it. -->
|
only changes who may claim it.
|
||||||
|
S-13 adds diploma-eligibility routing: between OpenZaakAanmaken and Beoordelen a DMN
|
||||||
|
businessRuleTask evaluates the `diploma-eligibility` decision on the diplomaOrigin start
|
||||||
|
variable; an exclusive gateway routes a foreign diploma through the CBGV-advies user task
|
||||||
|
before Beoordelen, a domestic one straight there (ADR-0016). -->
|
||||||
<message id="Message_Ingetrokken" name="RegistratieIngetrokken"/>
|
<message id="Message_Ingetrokken" name="RegistratieIngetrokken"/>
|
||||||
|
|
||||||
<process id="registratie" name="Registratie ontvangen" isExecutable="true">
|
<process id="registratie" name="Registratie ontvangen" isExecutable="true">
|
||||||
@@ -33,7 +38,35 @@
|
|||||||
flowable:type="external-worker"
|
flowable:type="external-worker"
|
||||||
flowable:topic="OpenZaakAanmaken"/>
|
flowable:topic="OpenZaakAanmaken"/>
|
||||||
|
|
||||||
<sequenceFlow id="flow2" sourceRef="OpenZaakAanmaken" targetRef="Beoordelen"/>
|
<sequenceFlow id="flow2" sourceRef="OpenZaakAanmaken" targetRef="DiplomaEligibiliteit"/>
|
||||||
|
|
||||||
|
<!-- S-13: evaluate the diploma-eligibility DMN inline (ADR-0016). Flowable runs the deployed
|
||||||
|
`diploma-eligibility` decision against the diplomaOrigin start variable and sets the `route`
|
||||||
|
output as a process variable. The gateway then routes a foreign diploma through CBGV-advies,
|
||||||
|
a domestic one straight to Beoordelen. -->
|
||||||
|
<businessRuleTask id="DiplomaEligibiliteit" name="Diploma-eligibiliteit bepalen" flowable:type="dmn">
|
||||||
|
<extensionElements>
|
||||||
|
<flowable:field name="decisionTableReferenceKey">
|
||||||
|
<flowable:string><![CDATA[diploma-eligibility]]></flowable:string>
|
||||||
|
</flowable:field>
|
||||||
|
</extensionElements>
|
||||||
|
</businessRuleTask>
|
||||||
|
|
||||||
|
<sequenceFlow id="flow2a" sourceRef="DiplomaEligibiliteit" targetRef="RouteOpDiploma"/>
|
||||||
|
|
||||||
|
<exclusiveGateway id="RouteOpDiploma" name="Buitenlands diploma?" default="flowDirect"/>
|
||||||
|
|
||||||
|
<sequenceFlow id="flowCbgv" sourceRef="RouteOpDiploma" targetRef="CBGVAdvies">
|
||||||
|
<conditionExpression xsi:type="tFormalExpression"><![CDATA[${route == 'CBGV_ADVIES'}]]></conditionExpression>
|
||||||
|
</sequenceFlow>
|
||||||
|
|
||||||
|
<!-- The extra CBGV-style assessment for foreign diplomas (PRD flow 4). A CBGV medewerker
|
||||||
|
completes it, after which the case continues to the regular beoordeling. -->
|
||||||
|
<userTask id="CBGVAdvies" name="CBGV-advies" flowable:candidateGroups="cbgv"/>
|
||||||
|
|
||||||
|
<sequenceFlow id="flowCbgvNaarBeoordelen" sourceRef="CBGVAdvies" targetRef="Beoordelen"/>
|
||||||
|
|
||||||
|
<sequenceFlow id="flowDirect" sourceRef="RouteOpDiploma" targetRef="Beoordelen"/>
|
||||||
|
|
||||||
<userTask id="Beoordelen" name="Beoordelen" flowable:candidateGroups="behandelaar"/>
|
<userTask id="Beoordelen" name="Beoordelen" flowable:candidateGroups="behandelaar"/>
|
||||||
|
|
||||||
@@ -77,53 +110,79 @@
|
|||||||
<omgdc:Bounds x="100" y="100" width="30" height="30"/>
|
<omgdc:Bounds x="100" y="100" width="30" height="30"/>
|
||||||
</bpmndi:BPMNShape>
|
</bpmndi:BPMNShape>
|
||||||
<bpmndi:BPMNShape id="s_task" bpmnElement="OpenZaakAanmaken">
|
<bpmndi:BPMNShape id="s_task" bpmnElement="OpenZaakAanmaken">
|
||||||
<omgdc:Bounds x="200" y="85" width="120" height="60"/>
|
<omgdc:Bounds x="180" y="85" width="120" height="60"/>
|
||||||
|
</bpmndi:BPMNShape>
|
||||||
|
<bpmndi:BPMNShape id="s_dmn" bpmnElement="DiplomaEligibiliteit">
|
||||||
|
<omgdc:Bounds x="350" y="85" width="120" height="60"/>
|
||||||
|
</bpmndi:BPMNShape>
|
||||||
|
<bpmndi:BPMNShape id="s_route" bpmnElement="RouteOpDiploma" isMarkerVisible="true">
|
||||||
|
<omgdc:Bounds x="520" y="90" width="40" height="40"/>
|
||||||
|
</bpmndi:BPMNShape>
|
||||||
|
<bpmndi:BPMNShape id="s_cbgv" bpmnElement="CBGVAdvies">
|
||||||
|
<omgdc:Bounds x="500" y="200" width="120" height="60"/>
|
||||||
</bpmndi:BPMNShape>
|
</bpmndi:BPMNShape>
|
||||||
<bpmndi:BPMNShape id="s_beoordelen" bpmnElement="Beoordelen">
|
<bpmndi:BPMNShape id="s_beoordelen" bpmnElement="Beoordelen">
|
||||||
<omgdc:Bounds x="390" y="85" width="120" height="60"/>
|
<omgdc:Bounds x="630" y="85" width="120" height="60"/>
|
||||||
</bpmndi:BPMNShape>
|
</bpmndi:BPMNShape>
|
||||||
<bpmndi:BPMNShape id="s_end" bpmnElement="end">
|
<bpmndi:BPMNShape id="s_end" bpmnElement="end">
|
||||||
<omgdc:Bounds x="580" y="100" width="30" height="30"/>
|
<omgdc:Bounds x="810" y="100" width="30" height="30"/>
|
||||||
|
</bpmndi:BPMNShape>
|
||||||
|
<bpmndi:BPMNShape id="s_ingetrokken" bpmnElement="Ingetrokken">
|
||||||
|
<omgdc:Bounds x="690" y="135" width="30" height="30"/>
|
||||||
|
</bpmndi:BPMNShape>
|
||||||
|
<bpmndi:BPMNShape id="s_endIngetrokken" bpmnElement="endIngetrokken">
|
||||||
|
<omgdc:Bounds x="690" y="250" width="30" height="30"/>
|
||||||
|
</bpmndi:BPMNShape>
|
||||||
|
<bpmndi:BPMNShape id="s_escaleerTimer" bpmnElement="EscaleerTimer">
|
||||||
|
<omgdc:Bounds x="700" y="70" width="30" height="30"/>
|
||||||
|
</bpmndi:BPMNShape>
|
||||||
|
<bpmndi:BPMNShape id="s_escaleerBeoordeling" bpmnElement="EscaleerBeoordeling">
|
||||||
|
<omgdc:Bounds x="800" y="20" width="120" height="60"/>
|
||||||
|
</bpmndi:BPMNShape>
|
||||||
|
<bpmndi:BPMNShape id="s_endEscaleren" bpmnElement="endEscaleren">
|
||||||
|
<omgdc:Bounds x="970" y="35" width="30" height="30"/>
|
||||||
</bpmndi:BPMNShape>
|
</bpmndi:BPMNShape>
|
||||||
<bpmndi:BPMNEdge id="e_flow1" bpmnElement="flow1">
|
<bpmndi:BPMNEdge id="e_flow1" bpmnElement="flow1">
|
||||||
<omgdi:waypoint x="130" y="115"/>
|
<omgdi:waypoint x="130" y="115"/>
|
||||||
<omgdi:waypoint x="200" y="115"/>
|
<omgdi:waypoint x="180" y="115"/>
|
||||||
</bpmndi:BPMNEdge>
|
</bpmndi:BPMNEdge>
|
||||||
<bpmndi:BPMNEdge id="e_flow2" bpmnElement="flow2">
|
<bpmndi:BPMNEdge id="e_flow2" bpmnElement="flow2">
|
||||||
<omgdi:waypoint x="320" y="115"/>
|
<omgdi:waypoint x="300" y="115"/>
|
||||||
<omgdi:waypoint x="390" y="115"/>
|
<omgdi:waypoint x="350" y="115"/>
|
||||||
|
</bpmndi:BPMNEdge>
|
||||||
|
<bpmndi:BPMNEdge id="e_flow2a" bpmnElement="flow2a">
|
||||||
|
<omgdi:waypoint x="470" y="115"/>
|
||||||
|
<omgdi:waypoint x="520" y="110"/>
|
||||||
|
</bpmndi:BPMNEdge>
|
||||||
|
<bpmndi:BPMNEdge id="e_flowDirect" bpmnElement="flowDirect">
|
||||||
|
<omgdi:waypoint x="560" y="110"/>
|
||||||
|
<omgdi:waypoint x="630" y="115"/>
|
||||||
|
</bpmndi:BPMNEdge>
|
||||||
|
<bpmndi:BPMNEdge id="e_flowCbgv" bpmnElement="flowCbgv">
|
||||||
|
<omgdi:waypoint x="540" y="130"/>
|
||||||
|
<omgdi:waypoint x="540" y="200"/>
|
||||||
|
</bpmndi:BPMNEdge>
|
||||||
|
<bpmndi:BPMNEdge id="e_flowCbgvNaarBeoordelen" bpmnElement="flowCbgvNaarBeoordelen">
|
||||||
|
<omgdi:waypoint x="620" y="230"/>
|
||||||
|
<omgdi:waypoint x="660" y="230"/>
|
||||||
|
<omgdi:waypoint x="660" y="145"/>
|
||||||
</bpmndi:BPMNEdge>
|
</bpmndi:BPMNEdge>
|
||||||
<bpmndi:BPMNEdge id="e_flow3" bpmnElement="flow3">
|
<bpmndi:BPMNEdge id="e_flow3" bpmnElement="flow3">
|
||||||
<omgdi:waypoint x="510" y="115"/>
|
<omgdi:waypoint x="750" y="115"/>
|
||||||
<omgdi:waypoint x="580" y="115"/>
|
<omgdi:waypoint x="810" y="115"/>
|
||||||
</bpmndi:BPMNEdge>
|
</bpmndi:BPMNEdge>
|
||||||
<bpmndi:BPMNShape id="s_ingetrokken" bpmnElement="Ingetrokken">
|
|
||||||
<omgdc:Bounds x="435" y="135" width="30" height="30"/>
|
|
||||||
</bpmndi:BPMNShape>
|
|
||||||
<bpmndi:BPMNShape id="s_endIngetrokken" bpmnElement="endIngetrokken">
|
|
||||||
<omgdc:Bounds x="435" y="220" width="30" height="30"/>
|
|
||||||
</bpmndi:BPMNShape>
|
|
||||||
<bpmndi:BPMNEdge id="e_flow4" bpmnElement="flow4">
|
<bpmndi:BPMNEdge id="e_flow4" bpmnElement="flow4">
|
||||||
<omgdi:waypoint x="450" y="165"/>
|
<omgdi:waypoint x="705" y="165"/>
|
||||||
<omgdi:waypoint x="450" y="220"/>
|
<omgdi:waypoint x="705" y="250"/>
|
||||||
</bpmndi:BPMNEdge>
|
</bpmndi:BPMNEdge>
|
||||||
<bpmndi:BPMNShape id="s_escaleerTimer" bpmnElement="EscaleerTimer">
|
|
||||||
<omgdc:Bounds x="480" y="70" width="30" height="30"/>
|
|
||||||
</bpmndi:BPMNShape>
|
|
||||||
<bpmndi:BPMNShape id="s_escaleerBeoordeling" bpmnElement="EscaleerBeoordeling">
|
|
||||||
<omgdc:Bounds x="580" y="20" width="120" height="60"/>
|
|
||||||
</bpmndi:BPMNShape>
|
|
||||||
<bpmndi:BPMNShape id="s_endEscaleren" bpmnElement="endEscaleren">
|
|
||||||
<omgdc:Bounds x="760" y="35" width="30" height="30"/>
|
|
||||||
</bpmndi:BPMNShape>
|
|
||||||
<bpmndi:BPMNEdge id="e_flow5" bpmnElement="flow5">
|
<bpmndi:BPMNEdge id="e_flow5" bpmnElement="flow5">
|
||||||
<omgdi:waypoint x="495" y="70"/>
|
<omgdi:waypoint x="715" y="70"/>
|
||||||
<omgdi:waypoint x="495" y="50"/>
|
<omgdi:waypoint x="715" y="50"/>
|
||||||
<omgdi:waypoint x="580" y="50"/>
|
<omgdi:waypoint x="800" y="50"/>
|
||||||
</bpmndi:BPMNEdge>
|
</bpmndi:BPMNEdge>
|
||||||
<bpmndi:BPMNEdge id="e_flow6" bpmnElement="flow6">
|
<bpmndi:BPMNEdge id="e_flow6" bpmnElement="flow6">
|
||||||
<omgdi:waypoint x="700" y="50"/>
|
<omgdi:waypoint x="920" y="50"/>
|
||||||
<omgdi:waypoint x="760" y="50"/>
|
<omgdi:waypoint x="970" y="50"/>
|
||||||
</bpmndi:BPMNEdge>
|
</bpmndi:BPMNEdge>
|
||||||
</bpmndi:BPMNPlane>
|
</bpmndi:BPMNPlane>
|
||||||
</bpmndi:BPMNDiagram>
|
</bpmndi:BPMNDiagram>
|
||||||
|
|||||||
Reference in New Issue
Block a user