Files
register-referentie/services/domain/Big.Application/ProvideDocuments.cs
T
notandClaude Opus 5.5 eaef19747b test(domain): only a clean PDF diploma is stored and unblocks beoordeling (refs #192)
Red: ProvideDocuments takes the new IDocumentScanner port but ignores it, so
infected, non-PDF and scanner-unavailable uploads are still Accepted.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 09:53:23 +02:00

64 lines
3.2 KiB
C#

using Big.Domain;
namespace Big.Application;
/// <summary>A zorgprofessional's upload of the diploma their registration is waiting for ("documenten
/// aanleveren"). <paramref name="Bsn"/> is the authenticated caller (from the DigiD token, forwarded by
/// the BFF): only the registration's own bsn may provide its documents. <paramref name="Content"/> is
/// the raw file, with its <paramref name="FileName"/> and <paramref name="ContentType"/>.</summary>
public sealed record ProvideDocumentsCommand(
RegistrationId RegistrationId, string Bsn, byte[] Content, string FileName, string ContentType);
/// <summary>The outcome of a provide-documents request.</summary>
public enum ProvideDocumentsOutcome
{
/// <summary>The documents were accepted; the process's document wait was completed (if any).</summary>
Accepted,
/// <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 file does not start with the PDF signature (<c>%PDF-</c>); nothing was stored.</summary>
NotAPdf,
/// <summary>The malware scan found something; nothing was stored and the wait stays open.</summary>
Infected,
/// <summary>The scanner could not be reached — refused rather than storing an unscanned file.</summary>
ScannerUnavailable,
}
/// <summary>
/// The provide-documents use case (S-10a/S-10b): a zorgprofessional uploads the diploma their
/// registration is parked waiting for. The document is stored in ZGW via the ACL (§8.1), then the
/// WachtOpDocumenten task is completed so the registratie process leaves the 30-day wait and continues
/// to beoordeling (ADR-0017). Owner-scoped by bsn. Both steps are best-effort about missing preconditions
/// (mirroring <see cref="WithdrawRegistration"/>): storage needs an opened zaak, and completion needs a
/// running process — a request that arrives before either still stands, storing/completing what it can.
/// </summary>
public sealed class ProvideDocuments(IRegistrationStore store, IWorkflowClient workflow, IAclClient acl, IDocumentScanner scanner)
{
public async Task<ProvideDocumentsOutcome> 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;
// Store the diploma against the zaak (once it is opened) — the ACL is the only ZGW caller (§8.1).
if (registration.ZaakUrl is not null)
await acl.StoreDiplomaAsync(
registration.ZaakUrl, command.Content, command.FileName, command.ContentType, ct);
// 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;
}
}