Compare commits

..

7 Commits

Author SHA1 Message Date
94a506cbfc fix(workflow): read task query variables from 'variables', not 'processVariables' (refs #13)
Some checks are pending
CI / mutation (pull_request) Waiting to run
CI / verify-stack (pull_request) Waiting to run
CI / lint (pull_request) Successful in 1m28s
CI / build (pull_request) Successful in 1m8s
CI / unit (pull_request) Successful in 1m34s
CI / frontend (pull_request) Successful in 2m24s
Flowable's POST query/tasks returns the included process variables under the key
'variables' (the request opts in via includeProcessVariables). The client DTO, its
unit test, and the live-check parser all looked for 'processVariables', so the
werkbak never matched a task's registrationId and verify-domain timed out. Verified
by driving a real Flowable instance end-to-end locally: start -> complete external
job -> the Beoordelen task carries registrationId under 'variables'.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-15 10:29:43 +02:00
c53354cd22 fix(workflow): query Flowable tasks at service/query/tasks, not runtime/tasks/query (refs #13)
Some checks failed
CI / lint (pull_request) Successful in 1m17s
CI / build (pull_request) Successful in 1m14s
CI / unit (pull_request) Successful in 1m17s
CI / frontend (pull_request) Successful in 2m41s
CI / mutation (pull_request) Successful in 5m9s
CI / verify-stack (pull_request) Failing after 7m53s
The task-query endpoint is service/query/tasks; the wrong path 404'd, so verify-domain's
werkbak poll got an empty body and the JSON parser aborted the check. Correct the path
in the Workflow Client (and its unit test) and make the live-check parser tolerant of a
transient empty/non-JSON body so the poll retries instead of crashing.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-15 09:57:11 +02:00
57b8755f9d test(workflow): cover the besluit variable type + start empty-body throw (refs #13)
Some checks failed
CI / lint (pull_request) Successful in 1m14s
CI / build (pull_request) Successful in 58s
CI / unit (pull_request) Successful in 1m5s
CI / frontend (pull_request) Successful in 2m12s
CI / mutation (pull_request) Successful in 4m59s
CI / verify-stack (pull_request) Failing after 6m39s
Kills two mutation survivors: assert the complete besluit variable's type, and add
the missing start empty-response case (the pre-existing baseline survivor). Domain
mutation score reaches 100%.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-15 09:24:53 +02:00
3d561deb05 test(workflow): verify-domain exercises the Beoordelen user task end-to-end (refs #13)
After the worker opens the zaak, the process parks at Beoordelen. The live check now
polls Flowable for the task (werkbak), claims it as merel-behandelaar, completes it
with besluit=goedkeuren, and asserts the process finishes — proving the exact REST
contract (query/claim/complete) the Workflow Client depends on against a real engine.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-15 09:21:35 +02:00
5db55eebf4 feat(workflow): add the Beoordelen behandelaar user task to registratie.bpmn (refs #13)
After OpenZaakAanmaken the process now parks at a Beoordelen user task (candidate
group behandelaar) until the behandelaar claims and completes it. registrationId
rides along as a process variable so the werkbak correlates each task to its
aggregate. The walking skeleton is unaffected: the temporary /approve path sets the
zaak status directly; wiring the decision to complete this task lands in S-12c/d.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-15 09:19:19 +02:00
efd1c06b99 feat(workflow): user-task client for beoordeling — werkbak, claim, complete (refs #13)
Adds the IUserTaskClient port and implements it on the Workflow Client (the only code
that talks to Flowable, §8.2): query open Beoordelen tasks (werkbak) with their
registrationId, claim a task for a behandelaar, and complete it carrying the besluit
into the process. Registered in DI for later wiring (S-12c).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-15 09:18:03 +02:00
57cc3637c9 test(workflow): user-task client — werkbak query, claim, complete beoordeling (refs #13)
Red — GetOpenBeoordelingenAsync/ClaimAsync/CompleteBeoordelingAsync do not exist yet.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-15 09:16:46 +02:00
6 changed files with 279 additions and 14 deletions

View File

@@ -51,18 +51,71 @@ loc="$(docker run --rm --network "$net" curlimages/curl:latest \
echo ">> registration accepted at $loc" echo ">> registration accepted at $loc"
echo ">> polling the domain until the worker records the opened zaak" echo ">> polling the domain until the worker records the opened zaak"
zaak_ok=""
for _ in $(seq 1 30); do for _ in $(seq 1 30); do
body="$(docker run --rm --network "$net" curlimages/curl:latest \ body="$(docker run --rm --network "$net" curlimages/curl:latest \
-fsS "http://$dom_ip:8080$loc" 2>/dev/null || true)" -fsS "http://$dom_ip:8080$loc" 2>/dev/null || true)"
if echo "$body" | grep -q '/zaken/api/v1/zaken/'; then if echo "$body" | grep -q '/zaken/api/v1/zaken/'; then
echo "OK — the domain opened a zaak and recorded it on the registration:" echo "OK — the domain opened a zaak and recorded it on the registration:"
echo "$body" | cut -c1-300 echo "$body" | cut -c1-300
exit 0 zaak_ok=1
break
fi fi
sleep 2 sleep 2
done done
echo "FAIL — the registration never received a zaak URL" >&2 if [ -z "$zaak_ok" ]; then
echo "--- domain log ---" >&2; docker logs "$dom" 2>&1 | tail -15 >&2 echo "FAIL — the registration never received a zaak URL" >&2
acl="$(docker ps -q --filter 'name=[-_]acl[-_]' | head -1)" echo "--- domain log ---" >&2; docker logs "$dom" 2>&1 | tail -15 >&2
[ -n "$acl" ] && { echo "--- acl log ---" >&2; docker logs "$acl" 2>&1 | tail -15 >&2; } acl="$(docker ps -q --filter 'name=[-_]acl[-_]' | head -1)"
exit 1 [ -n "$acl" ] && { echo "--- acl log ---" >&2; docker logs "$acl" 2>&1 | tail -15 >&2; }
exit 1
fi
# ── S-12b: the process now parks at the Beoordelen user task. Exercise the exact Flowable REST
# contract the Workflow Client uses (query/claim/complete) against the live engine, reaching
# flowable-rest by container IP (same in-network constraint as above). ──────────────────────────
fl="$(docker ps -q --filter 'name=flowable-rest' | head -1)"
[ -n "$fl" ] || { echo "ERROR: no running flowable-rest container" >&2; exit 1; }
fl_base="http://$(ip "$fl"):8080/flowable-rest/service"
reg_id="${loc##*/}"
# Extracts the Beoordelen task id for our registration from a Flowable task-query response on stdin.
# Tolerates an empty/non-JSON body (a transient failure during the poll) by printing nothing.
task_for_reg() { REG_ID="$reg_id" python3 -c "import os,sys,json
try:
d=json.load(sys.stdin)
except Exception:
d={}
rid=os.environ['REG_ID']
# Flowable's task-query returns the included process variables under 'variables'.
print(next((t['id'] for t in (d.get('data') or [])
if any(v.get('name')=='registrationId' and v.get('value')==rid for v in (t.get('variables') or []))), ''))"; }
flcurl() { docker run --rm --network "$net" curlimages/curl:latest -fsS -u rest-admin:test "$@"; }
query='{"processDefinitionKey":"registratie","taskDefinitionKey":"Beoordelen","includeProcessVariables":true}'
echo ">> polling Flowable for the Beoordelen user task (werkbak)"
task_id=""
for _ in $(seq 1 30); do
resp="$(flcurl -X POST "$fl_base/query/tasks" -H 'Content-Type: application/json' -d "$query" 2>/dev/null || true)"
task_id="$(printf '%s' "$resp" | task_for_reg)"
[ -n "$task_id" ] && break
sleep 2
done
[ -n "$task_id" ] || { echo "FAIL — no Beoordelen task appeared for registration $reg_id" >&2; docker logs "$dom" 2>&1 | tail -15 >&2; exit 1; }
echo ">> Beoordelen task $task_id is waiting"
echo ">> claiming the task as merel-behandelaar"
flcurl -X POST "$fl_base/runtime/tasks/$task_id" -H 'Content-Type: application/json' \
-d '{"action":"claim","assignee":"merel-behandelaar"}' >/dev/null
echo ">> completing the beoordeling (goedkeuren)"
flcurl -X POST "$fl_base/runtime/tasks/$task_id" -H 'Content-Type: application/json' \
-d '{"action":"complete","variables":[{"name":"besluit","type":"string","value":"goedkeuren"}]}' >/dev/null
echo ">> asserting the process finished (no Beoordelen task remains for the registration)"
resp="$(flcurl -X POST "$fl_base/query/tasks" -H 'Content-Type: application/json' -d "$query")"
still="$(printf '%s' "$resp" | task_for_reg)"
[ -z "$still" ] || { echo "FAIL — Beoordelen task $still still active after completion" >&2; exit 1; }
echo "OK — behandelaar claimed and completed the Beoordelen task; the registratie process finished"
exit 0

View File

@@ -20,6 +20,7 @@ builder.Services.AddSingleton<IRegistrationStore, InMemoryRegistrationStore>();
builder.Services.AddHttpClient<FlowableWorkflowClient>(); builder.Services.AddHttpClient<FlowableWorkflowClient>();
builder.Services.AddTransient<IWorkflowClient>(sp => sp.GetRequiredService<FlowableWorkflowClient>()); builder.Services.AddTransient<IWorkflowClient>(sp => sp.GetRequiredService<FlowableWorkflowClient>());
builder.Services.AddTransient<IExternalWorkerClient>(sp => sp.GetRequiredService<FlowableWorkflowClient>()); builder.Services.AddTransient<IExternalWorkerClient>(sp => sp.GetRequiredService<FlowableWorkflowClient>());
builder.Services.AddTransient<IUserTaskClient>(sp => sp.GetRequiredService<FlowableWorkflowClient>());
builder.Services.AddHttpClient<IAclClient, AclHttpClient>(); builder.Services.AddHttpClient<IAclClient, AclHttpClient>();
builder.Services.AddScoped<SubmitRegistration>(); builder.Services.AddScoped<SubmitRegistration>();

View File

@@ -36,6 +36,30 @@ public interface IAclClient
Task ApproveZaakAsync(Uri zaakUrl, CancellationToken ct = default); Task ApproveZaakAsync(Uri zaakUrl, CancellationToken ct = default);
} }
/// <summary>
/// The port to the behandelaar's user tasks in the workflow engine (S-12). Implemented by the
/// Workflow Client — the only code that talks to Flowable (§8.2). The application lists the open
/// <c>Beoordelen</c> tasks (the werkbak), claims one for a behandelaar, and completes it with the
/// decision; it never names Flowable's REST shapes.
/// </summary>
public interface IUserTaskClient
{
/// <summary>The werkbak: the <c>Beoordelen</c> tasks awaiting a behandelaar, each with the
/// registration it belongs to.</summary>
Task<IReadOnlyList<BeoordelingTask>> GetOpenBeoordelingenAsync(CancellationToken ct = default);
/// <summary>Claim a beoordeling task for a behandelaar (assigns it to them).</summary>
Task ClaimAsync(string taskId, string behandelaar, CancellationToken ct = default);
/// <summary>Complete a beoordeling task, carrying the decision into the process as the
/// <c>besluit</c> variable so the workflow can continue on the chosen branch.</summary>
Task CompleteBeoordelingAsync(string taskId, BeoordelingsBesluit besluit, CancellationToken ct = default);
}
/// <summary>A <c>Beoordelen</c> user task in the werkbak: the Flowable task id (needed to claim and
/// complete it) and the registration it carries as a process variable.</summary>
public sealed record BeoordelingTask(string TaskId, RegistrationId RegistrationId);
/// <summary> /// <summary>
/// Persistence port for the <see cref="Registration"/> aggregate. In-memory for the minimal slice /// Persistence port for the <see cref="Registration"/> aggregate. In-memory for the minimal slice
/// (ADR-0009); an EF-backed store is a documented follow-up, and this port keeps that change additive. /// (ADR-0009); an EF-backed store is a documented follow-up, and this port keeps that change additive.

View File

@@ -15,12 +15,14 @@ namespace Big.Infrastructure;
/// The REST contract here is the one verified against a live flowable-rest engine (ADR-0009). /// The REST contract here is the one verified against a live flowable-rest engine (ADR-0009).
/// </summary> /// </summary>
public sealed class FlowableWorkflowClient(HttpClient http, FlowableOptions options) public sealed class FlowableWorkflowClient(HttpClient http, FlowableOptions options)
: IWorkflowClient, IExternalWorkerClient : IWorkflowClient, IExternalWorkerClient, IUserTaskClient
{ {
private const string Topic = "OpenZaakAanmaken"; private const string Topic = "OpenZaakAanmaken";
private const string ProcessDefinitionKey = "registratie"; private const string ProcessDefinitionKey = "registratie";
private const string BeoordelenTaskKey = "Beoordelen";
private const string RegistrationIdVariable = "registrationId"; private const string RegistrationIdVariable = "registrationId";
private const string ZaakUrlVariable = "zaakUrl"; private const string ZaakUrlVariable = "zaakUrl";
private const string BesluitVariable = "besluit";
public async Task<string> StartRegistrationProcessAsync(RegistrationId registrationId, CancellationToken ct = default) public async Task<string> StartRegistrationProcessAsync(RegistrationId registrationId, CancellationToken ct = default)
{ {
@@ -55,6 +57,34 @@ public sealed class FlowableWorkflowClient(HttpClient http, FlowableOptions opti
response.EnsureSuccessStatusCode(); response.EnsureSuccessStatusCode();
} }
public async Task<IReadOnlyList<BeoordelingTask>> GetOpenBeoordelingenAsync(CancellationToken ct = default)
{
var request = new TaskQueryRequest(ProcessDefinitionKey, BeoordelenTaskKey, IncludeProcessVariables: true);
var page = await PostAsync<TaskQueryRequest, TaskQueryResult>(
"service/query/tasks", request, ct);
var tasks = page?.Data ?? [];
return [.. tasks.Select(t => new BeoordelingTask(t.Id, RegistrationId.Parse(t.RegistrationId())))];
}
public async Task ClaimAsync(string taskId, string behandelaar, CancellationToken ct = default)
{
using var response = await SendAsync(
$"service/runtime/tasks/{taskId}", new ClaimTaskRequest("claim", behandelaar), ct);
response.EnsureSuccessStatusCode();
}
public async Task CompleteBeoordelingAsync(string taskId, BeoordelingsBesluit besluit, CancellationToken ct = default)
{
var request = new CompleteTaskRequest(
"complete",
[new Variable(BesluitVariable, "string", besluit.ToString().ToLowerInvariant())]);
using var response = await SendAsync($"service/runtime/tasks/{taskId}", request, ct);
response.EnsureSuccessStatusCode();
}
private async Task<TResponse?> PostAsync<TRequest, TResponse>(string path, TRequest body, CancellationToken ct) private async Task<TResponse?> PostAsync<TRequest, TResponse>(string path, TRequest body, CancellationToken ct)
{ {
using var response = await SendAsync(path, body, ct); using var response = await SendAsync(path, body, ct);
@@ -89,6 +119,34 @@ public sealed class FlowableWorkflowClient(HttpClient http, FlowableOptions opti
[property: JsonPropertyName("workerId")] string WorkerId, [property: JsonPropertyName("workerId")] string WorkerId,
[property: JsonPropertyName("variables")] IReadOnlyList<Variable> Variables); [property: JsonPropertyName("variables")] IReadOnlyList<Variable> Variables);
private sealed record TaskQueryRequest(
[property: JsonPropertyName("processDefinitionKey")] string ProcessDefinitionKey,
[property: JsonPropertyName("taskDefinitionKey")] string TaskDefinitionKey,
[property: JsonPropertyName("includeProcessVariables")] bool IncludeProcessVariables);
private sealed record ClaimTaskRequest(
[property: JsonPropertyName("action")] string Action,
[property: JsonPropertyName("assignee")] string Assignee);
private sealed record CompleteTaskRequest(
[property: JsonPropertyName("action")] string Action,
[property: JsonPropertyName("variables")] IReadOnlyList<Variable> Variables);
private sealed record TaskQueryResult(
[property: JsonPropertyName("data")] IReadOnlyList<UserTaskDto>? Data);
private sealed record UserTaskDto(
[property: JsonPropertyName("id")] string Id,
// Flowable's task-query returns the (included) process variables under "variables", not
// "processVariables"; the request opts in via includeProcessVariables.
[property: JsonPropertyName("variables")] IReadOnlyList<Variable>? Variables)
{
/// <summary>The registration id this task carries as a process variable.</summary>
public string RegistrationId() =>
Variables?.SingleOrDefault(v => v.Name == "registrationId")?.Value
?? throw new InvalidOperationException($"Beoordelen task {Id} carries no registrationId variable.");
}
private sealed record Variable( private sealed record Variable(
[property: JsonPropertyName("name")] string Name, [property: JsonPropertyName("name")] string Name,
[property: JsonPropertyName("type")] string Type, [property: JsonPropertyName("type")] string Type,

View File

@@ -1,5 +1,6 @@
using System.Net; using System.Net;
using System.Text; using System.Text;
using Big.Application;
using Big.Domain; using Big.Domain;
using Big.Infrastructure; using Big.Infrastructure;
@@ -127,6 +128,17 @@ public class FlowableWorkflowClientTests
() => client.StartRegistrationProcessAsync(RegistrationId.New())); () => client.StartRegistrationProcessAsync(RegistrationId.New()));
} }
[Fact]
public async Task Start_throws_when_flowable_returns_an_empty_body()
{
var capture = new RequestCapture();
var client = Client(capture.Responds(HttpStatusCode.Created, "null"));
var ex = await Assert.ThrowsAsync<InvalidOperationException>(
() => client.StartRegistrationProcessAsync(RegistrationId.New()));
Assert.Contains("empty process-instance", ex.Message);
}
[Fact] [Fact]
public async Task Complete_throws_when_flowable_rejects_the_request() public async Task Complete_throws_when_flowable_rejects_the_request()
{ {
@@ -136,4 +148,107 @@ public class FlowableWorkflowClientTests
await Assert.ThrowsAsync<HttpRequestException>( await Assert.ThrowsAsync<HttpRequestException>(
() => client.CompleteOpenZaakJobAsync("job-1", new Uri("http://openzaak/zaken/api/v1/zaken/abc"))); () => client.CompleteOpenZaakJobAsync("job-1", new Uri("http://openzaak/zaken/api/v1/zaken/abc")));
} }
// ── S-12b: behandelaar user tasks (werkbak / claim / complete) ────────────────────────────────
[Fact]
public async Task Open_beoordelingen_queries_beoordelen_tasks_with_their_registration_id()
{
var rid = RegistrationId.New();
var capture = new RequestCapture();
var client = Client(capture.Responds(HttpStatusCode.OK,
$$"""
{"data":[{"id":"task-1","variables":[{"name":"registrationId","type":"string","value":"{{rid}}"}]}],"total":1}
"""));
var tasks = await client.GetOpenBeoordelingenAsync();
var task = Assert.Single(tasks);
Assert.Equal("task-1", task.TaskId);
Assert.Equal(rid, task.RegistrationId);
Assert.Equal(HttpMethod.Post, capture.Seen!.Method);
Assert.Equal("http://flowable/flowable-rest/service/query/tasks",
capture.Seen.RequestUri!.ToString());
Assert.Equal("rest-admin:test", DecodeBasic(capture.Seen));
Assert.Contains("\"processDefinitionKey\":\"registratie\"", capture.Body);
Assert.Contains("\"taskDefinitionKey\":\"Beoordelen\"", capture.Body);
Assert.Contains("\"includeProcessVariables\":true", capture.Body);
}
[Theory]
[InlineData("""{"data":[],"total":0}""")]
[InlineData("""{"data":null,"total":0}""")]
public async Task Open_beoordelingen_is_empty_when_no_tasks_are_waiting(string body)
{
var capture = new RequestCapture();
var client = Client(capture.Responds(HttpStatusCode.OK, body));
Assert.Empty(await client.GetOpenBeoordelingenAsync());
Assert.NotNull(capture.Seen);
}
[Fact]
public async Task Open_beoordelingen_throws_when_a_task_carries_no_registration_id()
{
var capture = new RequestCapture();
var client = Client(capture.Responds(HttpStatusCode.OK,
"""{"data":[{"id":"task-1","variables":[]}],"total":1}"""));
var ex = await Assert.ThrowsAsync<InvalidOperationException>(() => client.GetOpenBeoordelingenAsync());
Assert.Contains("task-1", ex.Message);
Assert.Contains("registrationId", ex.Message);
}
[Fact]
public async Task Claim_assigns_the_task_to_the_behandelaar()
{
var capture = new RequestCapture();
var client = Client(capture.Responds(HttpStatusCode.OK));
await client.ClaimAsync("task-1", "merel-behandelaar");
Assert.Equal(HttpMethod.Post, capture.Seen!.Method);
Assert.Equal("http://flowable/flowable-rest/service/runtime/tasks/task-1",
capture.Seen.RequestUri!.ToString());
Assert.Contains("\"action\":\"claim\"", capture.Body);
Assert.Contains("\"assignee\":\"merel-behandelaar\"", capture.Body);
}
[Fact]
public async Task Claim_throws_when_flowable_rejects_the_request()
{
var capture = new RequestCapture();
var client = Client(capture.Responds(HttpStatusCode.Conflict));
await Assert.ThrowsAsync<HttpRequestException>(() => client.ClaimAsync("task-1", "merel-behandelaar"));
}
[Theory]
[InlineData(BeoordelingsBesluit.Goedkeuren, "goedkeuren")]
[InlineData(BeoordelingsBesluit.Afwijzen, "afwijzen")]
public async Task Complete_posts_the_besluit_variable_to_the_task(BeoordelingsBesluit besluit, string expected)
{
var capture = new RequestCapture();
var client = Client(capture.Responds(HttpStatusCode.OK));
await client.CompleteBeoordelingAsync("task-1", besluit);
Assert.Equal(HttpMethod.Post, capture.Seen!.Method);
Assert.Equal("http://flowable/flowable-rest/service/runtime/tasks/task-1",
capture.Seen.RequestUri!.ToString());
Assert.Contains("\"action\":\"complete\"", capture.Body);
Assert.Contains("\"name\":\"besluit\"", capture.Body);
Assert.Contains("\"type\":\"string\"", capture.Body);
Assert.Contains($"\"value\":\"{expected}\"", capture.Body);
}
[Fact]
public async Task Complete_beoordeling_throws_when_flowable_rejects_the_request()
{
var capture = new RequestCapture();
var client = Client(capture.Responds(HttpStatusCode.InternalServerError));
await Assert.ThrowsAsync<HttpRequestException>(
() => client.CompleteBeoordelingAsync("task-1", BeoordelingsBesluit.Goedkeuren));
}
} }

View File

@@ -6,9 +6,12 @@
xmlns:omgdi="http://www.omg.org/spec/DD/20100524/DI" xmlns:omgdi="http://www.omg.org/spec/DD/20100524/DI"
targetNamespace="http://respellion.nl/big"> targetNamespace="http://respellion.nl/big">
<!-- S-03: minimal "Registratie ontvangen" flow. <!-- Registratie ontvangen flow.
start -> external-worker task (OpenZaakAanmaken) -> end. start -> external-worker task (OpenZaakAanmaken) -> Beoordelen (behandelaar user task) -> end.
The external task is handled by the Workflow Client / ACL in later slices. --> S-03 added the external task (Workflow Client / ACL). S-12 adds the behandelaar's beoordeling
as a user task: the process parks here until a behandelaar claims and completes it with a
`besluit` (goedkeuren/afwijzen). The registrationId set at start rides along as a process
variable so the werkbak can correlate each task back to its aggregate. -->
<process id="registratie" name="Registratie ontvangen" isExecutable="true"> <process id="registratie" name="Registratie ontvangen" isExecutable="true">
<startEvent id="start" name="Registratie ontvangen"/> <startEvent id="start" name="Registratie ontvangen"/>
@@ -19,9 +22,13 @@
flowable:type="external-worker" flowable:type="external-worker"
flowable:topic="OpenZaakAanmaken"/> flowable:topic="OpenZaakAanmaken"/>
<sequenceFlow id="flow2" sourceRef="OpenZaakAanmaken" targetRef="end"/> <sequenceFlow id="flow2" sourceRef="OpenZaakAanmaken" targetRef="Beoordelen"/>
<endEvent id="end" name="Zaak aangemaakt"/> <userTask id="Beoordelen" name="Beoordelen" flowable:candidateGroups="behandelaar"/>
<sequenceFlow id="flow3" sourceRef="Beoordelen" targetRef="end"/>
<endEvent id="end" name="Registratie beoordeeld"/>
</process> </process>
<bpmndi:BPMNDiagram id="diagram"> <bpmndi:BPMNDiagram id="diagram">
@@ -32,8 +39,11 @@
<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="200" y="85" width="120" height="60"/>
</bpmndi:BPMNShape> </bpmndi:BPMNShape>
<bpmndi:BPMNShape id="s_beoordelen" bpmnElement="Beoordelen">
<omgdc:Bounds x="390" y="85" width="120" height="60"/>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="s_end" bpmnElement="end"> <bpmndi:BPMNShape id="s_end" bpmnElement="end">
<omgdc:Bounds x="400" y="100" width="30" height="30"/> <omgdc:Bounds x="580" y="100" 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"/>
@@ -41,7 +51,11 @@
</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="320" y="115"/>
<omgdi:waypoint x="400" y="115"/> <omgdi:waypoint x="390" y="115"/>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="e_flow3" bpmnElement="flow3">
<omgdi:waypoint x="510" y="115"/>
<omgdi:waypoint x="580" y="115"/>
</bpmndi:BPMNEdge> </bpmndi:BPMNEdge>
</bpmndi:BPMNPlane> </bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram> </bpmndi:BPMNDiagram>