diff --git a/services/domain/Big.Api/Program.cs b/services/domain/Big.Api/Program.cs index 627725e..c78e79b 100644 --- a/services/domain/Big.Api/Program.cs +++ b/services/domain/Big.Api/Program.cs @@ -29,6 +29,7 @@ builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); +builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); @@ -107,6 +108,23 @@ app.MapPost("/registrations/{id}/withdraw", async (string id, WithdrawRequest bo return outcome == WithdrawOutcome.Withdrawn ? Results.NoContent() : Results.NotFound(); }); +// Provide documents (S-10a): the zorgprofessional supplies the documents their registration is parked +// waiting for, completing the WachtOpDocumenten task so the process advances to beoordeling (ADR-0017). +// Owner-scoped by the caller's bsn (the BFF forwards it from the DigiD token); unknown or not-the- +// caller's is 404 (indistinguishable). Idempotent — completing an already-left wait is a no-op. The +// real file upload + ZGW storage is S-10b; this endpoint is the trigger that unblocks the process. +app.MapPost("/registrations/{id}/documents", async (string id, ProvideDocumentsRequest body, ProvideDocuments provide, CancellationToken ct) => +{ + if (!Guid.TryParse(id, out var guid)) + return Results.NotFound(); + + if (string.IsNullOrWhiteSpace(body?.Bsn)) + return Results.BadRequest(new { error = "A bsn is required to provide documents." }); + + var outcome = await provide.HandleAsync(new ProvideDocumentsCommand(new RegistrationId(guid), body.Bsn), ct); + return outcome == ProvideDocumentsOutcome.Accepted ? Results.NoContent() : Results.NotFound(); +}); + // The behandelaar's werkbak (S-12): the registrations awaiting beoordeling, read from the open // Beoordelen user tasks (§8.2) and enriched with bsn + status. The BFF proxies this behind // medewerker-realm + behandelaar-role authorization; the domain trusts its callers (§8.3). @@ -134,6 +152,8 @@ public sealed record DecideRequest(string Besluit); public sealed record WithdrawRequest(string Bsn); +public sealed record ProvideDocumentsRequest(string Bsn); + public sealed record RegistrationResponse(string RegistrationId, string Status, string? ZaakUrl); public partial class Program; diff --git a/services/domain/Big.Application/ProvideDocuments.cs b/services/domain/Big.Application/ProvideDocuments.cs new file mode 100644 index 0000000..c5cdcce --- /dev/null +++ b/services/domain/Big.Application/ProvideDocuments.cs @@ -0,0 +1,47 @@ +using Big.Domain; + +namespace Big.Application; + +/// A zorgprofessional's signal that they have supplied the documents their registration is +/// waiting for ("documenten aanleveren"). is the authenticated caller (from the +/// DigiD token, forwarded by the BFF): only the registration's own bsn may provide its documents. +public sealed record ProvideDocumentsCommand(RegistrationId RegistrationId, string Bsn); + +/// The outcome of a provide-documents request. +public enum ProvideDocumentsOutcome +{ + /// The documents were accepted; the process's document wait was completed (if any). + Accepted, + + /// 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). + NotFound, +} + +/// +/// The provide-documents use case (S-10a): a zorgprofessional supplies the documents their registration +/// is parked waiting for, completing the WachtOpDocumenten task so the registratie process leaves the +/// 30-day wait and continues to beoordeling (ADR-0017). Owner-scoped by bsn. Completing the wait is +/// best-effort: if the registration never started a process (or already left the wait), the request +/// still stands, mirroring how cancels best-effort. The actual file +/// upload and its ZGW storage via the ACL is S-10b; this is the trigger that unblocks the process. +/// +public sealed class ProvideDocuments(IRegistrationStore store, IWorkflowClient workflow) +{ + public async Task HandleAsync(ProvideDocumentsCommand command, CancellationToken ct = default) + { + ArgumentNullException.ThrowIfNull(command); + + 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 ProvideDocumentsOutcome.NotFound; + + // Complete the document wait (if a process is running) so beoordeling can proceed. + if (registration.ProcessInstanceId is not null) + await workflow.CompleteDocumentWaitAsync(registration.ProcessInstanceId, ct); + + return ProvideDocumentsOutcome.Accepted; + } +}