feat(bff): POST /self-service/registrations/{id}/withdraw, owner-scoped (refs #12)
The domain withdraw command carries the caller's bsn and owner-scopes the aggregate; unknown or not-owned is 404 (indistinguishable). The BFF forwards the DigiD token's bsn and relays 204/404. Regenerate the OpenAPI spec + Angular client for the new endpoint. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -2,8 +2,21 @@ using Big.Domain;
|
||||
|
||||
namespace Big.Application;
|
||||
|
||||
/// <summary>A zorgprofessional's request to withdraw their own registration ("trek aanvraag in").</summary>
|
||||
public sealed record WithdrawRegistrationCommand(RegistrationId RegistrationId);
|
||||
/// <summary>A zorgprofessional's request to withdraw their own registration ("trek aanvraag in").
|
||||
/// <paramref name="Bsn"/> is the authenticated caller (from the DigiD token, forwarded by the BFF):
|
||||
/// only the registration's own bsn may withdraw it.</summary>
|
||||
public sealed record WithdrawRegistrationCommand(RegistrationId RegistrationId, string Bsn);
|
||||
|
||||
/// <summary>The outcome of a withdrawal request.</summary>
|
||||
public enum WithdrawOutcome
|
||||
{
|
||||
/// <summary>The registration is now (or already was) INGETROKKEN.</summary>
|
||||
Withdrawn,
|
||||
|
||||
/// <summary>No registration with that id belongs to the caller — unknown, or owned by someone
|
||||
/// else (the two are deliberately indistinguishable, so the endpoint reveals neither).</summary>
|
||||
NotFound,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The withdrawal use case (S-11): a zorgprofessional pulls a still-open registration back. It
|
||||
@@ -16,16 +29,19 @@ public sealed record WithdrawRegistrationCommand(RegistrationId RegistrationId);
|
||||
/// </summary>
|
||||
public sealed class WithdrawRegistration(IRegistrationStore store, IWorkflowClient workflow)
|
||||
{
|
||||
public async Task HandleAsync(WithdrawRegistrationCommand command, CancellationToken ct = default)
|
||||
public async Task<WithdrawOutcome> HandleAsync(WithdrawRegistrationCommand command, CancellationToken ct = default)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(command);
|
||||
|
||||
var registration = await store.GetAsync(command.RegistrationId, ct)
|
||||
?? throw new InvalidOperationException($"No registration {command.RegistrationId} to withdraw.");
|
||||
var registration = await store.GetAsync(command.RegistrationId, ct);
|
||||
|
||||
// Unknown, or not the caller's registration: report NotFound either way (don't reveal which).
|
||||
if (registration is null || registration.Bsn != command.Bsn)
|
||||
return WithdrawOutcome.NotFound;
|
||||
|
||||
// A repeated withdrawal is a no-op: don't persist or cancel the already-withdrawn one again.
|
||||
if (registration.Status == RegistrationStatus.Ingetrokken)
|
||||
return;
|
||||
return WithdrawOutcome.Withdrawn;
|
||||
|
||||
registration.Withdraw();
|
||||
await store.SaveAsync(registration, ct);
|
||||
@@ -33,5 +49,7 @@ public sealed class WithdrawRegistration(IRegistrationStore store, IWorkflowClie
|
||||
// Cancel the running process (if one was started) so its Beoordelen task leaves the werkbak.
|
||||
if (registration.ProcessInstanceId is not null)
|
||||
await workflow.WithdrawProcessAsync(registration.ProcessInstanceId, ct);
|
||||
|
||||
return WithdrawOutcome.Withdrawn;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user