style: format backend with dotnet format

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-03 13:39:31 +02:00
parent e82309786d
commit 1137f59f7b
17 changed files with 1135 additions and 1129 deletions

View File

@@ -12,20 +12,20 @@ namespace BigRegister.Api.Data;
/// </summary>
public sealed class Aanvraag
{
public required string Id { get; init; }
public required string Type { get; init; } // registratie | herregistratie | intake
public required string Owner { get; init; }
public JsonElement? Draft { get; set; } // opaque wizard machine snapshot (Concept only)
public int StepIndex { get; set; }
public int StepCount { get; set; }
public List<string> DocumentIds { get; set; } = new();
public string? Referentie { get; set; } // set on submit
public bool AutoApprovable { get; set; } // set on submit: duo (registratie) / other types
public string? Reden { get; set; } // set on submit when rejected → Afgewezen
public bool Submitted { get; set; }
public DateTimeOffset CreatedAt { get; init; }
public DateTimeOffset UpdatedAt { get; set; }
public DateTimeOffset? SubmittedAt { get; set; }
public required string Id { get; init; }
public required string Type { get; init; } // registratie | herregistratie | intake
public required string Owner { get; init; }
public JsonElement? Draft { get; set; } // opaque wizard machine snapshot (Concept only)
public int StepIndex { get; set; }
public int StepCount { get; set; }
public List<string> DocumentIds { get; set; } = new();
public string? Referentie { get; set; } // set on submit
public bool AutoApprovable { get; set; } // set on submit: duo (registratie) / other types
public string? Reden { get; set; } // set on submit when rejected → Afgewezen
public bool Submitted { get; set; }
public DateTimeOffset CreatedAt { get; init; }
public DateTimeOffset UpdatedAt { get; set; }
public DateTimeOffset? SubmittedAt { get; set; }
}
/// <summary>
@@ -35,76 +35,76 @@ public sealed class Aanvraag
/// </summary>
public static class ApplicationStore
{
/// After this window an auto-approvable submission reports Goedgekeurd (computed on read).
public static readonly TimeSpan ProcessingWindow = TimeSpan.FromSeconds(8);
/// After this window an auto-approvable submission reports Goedgekeurd (computed on read).
public static readonly TimeSpan ProcessingWindow = TimeSpan.FromSeconds(8);
private static readonly Dictionary<string, Aanvraag> _apps = new();
private static readonly object _gate = new();
private static readonly Dictionary<string, Aanvraag> _apps = new();
private static readonly object _gate = new();
public static Aanvraag Create(string type, string owner)
public static Aanvraag Create(string type, string owner)
{
var now = DateTimeOffset.UtcNow;
var a = new Aanvraag { Id = Guid.NewGuid().ToString(), Type = type, Owner = owner, CreatedAt = now, UpdatedAt = now };
lock (_gate) _apps[a.Id] = a;
return a;
}
public static Aanvraag? Get(string id, string owner)
{
lock (_gate) return _apps.TryGetValue(id, out var a) && a.Owner == owner ? a : null;
}
public static IReadOnlyList<Aanvraag> List(string owner)
{
lock (_gate) return _apps.Values.Where(a => a.Owner == owner).ToList();
}
/// Draft sync: idempotent upsert of the wizard snapshot. Only a Concept is mutable.
public static bool SyncDraft(string id, string owner, JsonElement draft, int stepIndex, int stepCount, IReadOnlyList<string>? documentIds)
{
lock (_gate)
{
var now = DateTimeOffset.UtcNow;
var a = new Aanvraag { Id = Guid.NewGuid().ToString(), Type = type, Owner = owner, CreatedAt = now, UpdatedAt = now };
lock (_gate) _apps[a.Id] = a;
return a;
if (!_apps.TryGetValue(id, out var a) || a.Owner != owner || a.Submitted) return false;
a.Draft = draft.Clone(); // detach from the request's JsonDocument (disposed after the call)
a.StepIndex = stepIndex;
a.StepCount = stepCount;
if (documentIds is not null) a.DocumentIds = documentIds.ToList();
a.UpdatedAt = DateTimeOffset.UtcNow;
return true;
}
}
public static Aanvraag? Get(string id, string owner)
/// Cancel a Concept: remove it and delete its (unlinked) documents. Linked docs
/// (belonging to a submitted aanvraag) are left untouched by DocumentStore.
public static bool Delete(string id, string owner)
{
List<string> docs;
lock (_gate)
{
lock (_gate) return _apps.TryGetValue(id, out var a) && a.Owner == owner ? a : null;
if (!_apps.TryGetValue(id, out var a) || a.Owner != owner) return false;
docs = a.DocumentIds.ToList();
_apps.Remove(id);
}
foreach (var d in docs) DocumentStore.DeleteOwned(d, owner);
return true;
}
public static IReadOnlyList<Aanvraag> List(string owner)
/// Submit transition. reject != null → Afgewezen; else accepted (In behandeling,
/// auto-advancing to Goedgekeurd after the window when autoApprovable). Returns null
/// if the aanvraag is gone or already submitted (idempotency guard).
public static Aanvraag? Submit(string id, string owner, string? reject, bool autoApprovable, IReadOnlyList<string>? documentIds)
{
lock (_gate)
{
lock (_gate) return _apps.Values.Where(a => a.Owner == owner).ToList();
}
/// Draft sync: idempotent upsert of the wizard snapshot. Only a Concept is mutable.
public static bool SyncDraft(string id, string owner, JsonElement draft, int stepIndex, int stepCount, IReadOnlyList<string>? documentIds)
{
lock (_gate)
{
if (!_apps.TryGetValue(id, out var a) || a.Owner != owner || a.Submitted) return false;
a.Draft = draft.Clone(); // detach from the request's JsonDocument (disposed after the call)
a.StepIndex = stepIndex;
a.StepCount = stepCount;
if (documentIds is not null) a.DocumentIds = documentIds.ToList();
a.UpdatedAt = DateTimeOffset.UtcNow;
return true;
}
}
/// Cancel a Concept: remove it and delete its (unlinked) documents. Linked docs
/// (belonging to a submitted aanvraag) are left untouched by DocumentStore.
public static bool Delete(string id, string owner)
{
List<string> docs;
lock (_gate)
{
if (!_apps.TryGetValue(id, out var a) || a.Owner != owner) return false;
docs = a.DocumentIds.ToList();
_apps.Remove(id);
}
foreach (var d in docs) DocumentStore.DeleteOwned(d, owner);
return true;
}
/// Submit transition. reject != null → Afgewezen; else accepted (In behandeling,
/// auto-advancing to Goedgekeurd after the window when autoApprovable). Returns null
/// if the aanvraag is gone or already submitted (idempotency guard).
public static Aanvraag? Submit(string id, string owner, string? reject, bool autoApprovable, IReadOnlyList<string>? documentIds)
{
lock (_gate)
{
if (!_apps.TryGetValue(id, out var a) || a.Owner != owner || a.Submitted) return null;
a.Submitted = true;
a.SubmittedAt = DateTimeOffset.UtcNow;
a.UpdatedAt = a.SubmittedAt.Value;
a.Referentie = SubmissionRules.NewReference();
a.AutoApprovable = autoApprovable;
a.Reden = reject;
if (documentIds is not null) a.DocumentIds = documentIds.ToList();
return a;
}
if (!_apps.TryGetValue(id, out var a) || a.Owner != owner || a.Submitted) return null;
a.Submitted = true;
a.SubmittedAt = DateTimeOffset.UtcNow;
a.UpdatedAt = a.SubmittedAt.Value;
a.Referentie = SubmissionRules.NewReference();
a.AutoApprovable = autoApprovable;
a.Reden = reject;
if (documentIds is not null) a.DocumentIds = documentIds.ToList();
return a;
}
}
}

View File

@@ -11,134 +11,134 @@ namespace BigRegister.Api.Data;
/// </summary>
public sealed class BriefEntity
{
public required string BriefId { get; init; }
public required string Owner { get; init; }
public required string Beroep { get; init; }
public required string TemplateId { get; init; }
public required string DrafterId { get; init; }
public required IReadOnlyList<PlaceholderDefDto> Placeholders { get; init; }
public List<LetterSectionDto> Sections { get; set; } = new();
public BriefStatusDto Status { get; set; } = new("draft");
public required string BriefId { get; init; }
public required string Owner { get; init; }
public required string Beroep { get; init; }
public required string TemplateId { get; init; }
public required string DrafterId { get; init; }
public required IReadOnlyList<PlaceholderDefDto> Placeholders { get; init; }
public List<LetterSectionDto> Sections { get; set; } = new();
public BriefStatusDto Status { get; set; } = new("draft");
public BriefDto ToDto() => new(BriefId, Beroep, TemplateId, Placeholders, Sections, Status, DrafterId);
public BriefDto ToDto() => new(BriefId, Beroep, TemplateId, Placeholders, Sections, Status, DrafterId);
}
public static class BriefStore
{
// Dev-only role stand-ins (no real identities in this POC — see the ?role= toggle).
public const string DrafterId = "demo-drafter";
public const string ApproverId = "demo-approver";
// Dev-only role stand-ins (no real identities in this POC — see the ?role= toggle).
public const string DrafterId = "demo-drafter";
public const string ApproverId = "demo-approver";
public enum Outcome { Ok, Forbidden, Conflict }
public enum Outcome { Ok, Forbidden, Conflict }
private static readonly Dictionary<string, BriefEntity> _byOwner = new();
private static readonly object _gate = new();
private static readonly Dictionary<string, BriefEntity> _byOwner = new();
private static readonly object _gate = new();
public static BriefEntity GetOrCreate(string owner)
public static BriefEntity GetOrCreate(string owner)
{
lock (_gate)
{
lock (_gate)
{
if (_byOwner.TryGetValue(owner, out var existing)) return existing;
var created = BriefSeed.NewBrief(owner);
_byOwner[owner] = created;
return created;
}
if (_byOwner.TryGetValue(owner, out var existing)) return existing;
var created = BriefSeed.NewBrief(owner);
_byOwner[owner] = created;
return created;
}
}
/// Save draft content. Drafter-only; only editable in draft/rejected; a rejected
/// letter reopens to draft on edit (mirrors the FE reducer).
public static (Outcome, BriefEntity?) Save(string owner, IReadOnlyList<LetterSectionDto> sections, bool isDrafter)
/// Save draft content. Drafter-only; only editable in draft/rejected; a rejected
/// letter reopens to draft on edit (mirrors the FE reducer).
public static (Outcome, BriefEntity?) Save(string owner, IReadOnlyList<LetterSectionDto> sections, bool isDrafter)
{
lock (_gate)
{
lock (_gate)
{
if (!_byOwner.TryGetValue(owner, out var e)) return (Outcome.Conflict, null);
if (!isDrafter) return (Outcome.Forbidden, null);
if (e.Status.Tag is not ("draft" or "rejected")) return (Outcome.Conflict, null);
e.Sections = sections.ToList();
if (e.Status.Tag == "rejected") e.Status = new BriefStatusDto("draft");
return (Outcome.Ok, e);
}
if (!_byOwner.TryGetValue(owner, out var e)) return (Outcome.Conflict, null);
if (!isDrafter) return (Outcome.Forbidden, null);
if (e.Status.Tag is not ("draft" or "rejected")) return (Outcome.Conflict, null);
e.Sections = sections.ToList();
if (e.Status.Tag == "rejected") e.Status = new BriefStatusDto("draft");
return (Outcome.Ok, e);
}
}
public static (Outcome, BriefEntity?) Submit(string owner, bool isDrafter, string at)
public static (Outcome, BriefEntity?) Submit(string owner, bool isDrafter, string at)
{
lock (_gate)
{
lock (_gate)
{
if (!_byOwner.TryGetValue(owner, out var e)) return (Outcome.Conflict, null);
if (!isDrafter) return (Outcome.Forbidden, null);
if (e.Status.Tag != "draft" || !RequiredFilled(e)) return (Outcome.Conflict, null);
e.Status = new BriefStatusDto("submitted", SubmittedBy: e.DrafterId, SubmittedAt: at);
return (Outcome.Ok, e);
}
if (!_byOwner.TryGetValue(owner, out var e)) return (Outcome.Conflict, null);
if (!isDrafter) return (Outcome.Forbidden, null);
if (e.Status.Tag != "draft" || !RequiredFilled(e)) return (Outcome.Conflict, null);
e.Status = new BriefStatusDto("submitted", SubmittedBy: e.DrafterId, SubmittedAt: at);
return (Outcome.Ok, e);
}
}
public static (Outcome, BriefEntity?) Approve(string owner, string actingId, string at) =>
Review(owner, actingId, e => new BriefStatusDto("approved", ApprovedBy: actingId, ApprovedAt: at));
public static (Outcome, BriefEntity?) Approve(string owner, string actingId, string at) =>
Review(owner, actingId, e => new BriefStatusDto("approved", ApprovedBy: actingId, ApprovedAt: at));
public static (Outcome, BriefEntity?) Reject(string owner, string actingId, string comments, string at) =>
Review(owner, actingId, e => new BriefStatusDto("rejected", RejectedBy: actingId, RejectedAt: at, Comments: comments));
public static (Outcome, BriefEntity?) Reject(string owner, string actingId, string comments, string at) =>
Review(owner, actingId, e => new BriefStatusDto("rejected", RejectedBy: actingId, RejectedAt: at, Comments: comments));
public static (Outcome, BriefEntity?) Send(string owner, string at)
public static (Outcome, BriefEntity?) Send(string owner, string at)
{
lock (_gate)
{
lock (_gate)
{
if (!_byOwner.TryGetValue(owner, out var e)) return (Outcome.Conflict, null);
if (e.Status.Tag != "approved") return (Outcome.Conflict, null);
e.Status = new BriefStatusDto("sent", SentAt: at);
return (Outcome.Ok, e);
}
if (!_byOwner.TryGetValue(owner, out var e)) return (Outcome.Conflict, null);
if (e.Status.Tag != "approved") return (Outcome.Conflict, null);
e.Status = new BriefStatusDto("sent", SentAt: at);
return (Outcome.Ok, e);
}
}
/// Test seam: clear the demo brief between tests.
public static void Reset()
/// Test seam: clear the demo brief between tests.
public static void Reset()
{
lock (_gate) _byOwner.Clear();
}
/// Demo affordance: drop the owner's brief and create a fresh one. No guards — a
/// "start over" for the showcase, not a real workflow transition.
public static BriefEntity ResetAndCreate(string owner)
{
lock (_gate)
{
lock (_gate) _byOwner.Clear();
_byOwner.Remove(owner);
var created = BriefSeed.NewBrief(owner);
_byOwner[owner] = created;
return created;
}
}
/// Demo affordance: drop the owner's brief and create a fresh one. No guards — a
/// "start over" for the showcase, not a real workflow transition.
public static BriefEntity ResetAndCreate(string owner)
// Approve/reject share the guard: must be submitted, and the approver must differ
// from the drafter (a drafter cannot approve their own letter).
private static (Outcome, BriefEntity?) Review(string owner, string actingId, Func<BriefEntity, BriefStatusDto> next)
{
lock (_gate)
{
lock (_gate)
{
_byOwner.Remove(owner);
var created = BriefSeed.NewBrief(owner);
_byOwner[owner] = created;
return created;
}
if (!_byOwner.TryGetValue(owner, out var e)) return (Outcome.Conflict, null);
if (actingId == e.DrafterId) return (Outcome.Forbidden, null);
if (e.Status.Tag != "submitted") return (Outcome.Conflict, null);
e.Status = next(e);
return (Outcome.Ok, e);
}
}
// Approve/reject share the guard: must be submitted, and the approver must differ
// from the drafter (a drafter cannot approve their own letter).
private static (Outcome, BriefEntity?) Review(string owner, string actingId, Func<BriefEntity, BriefStatusDto> next)
{
lock (_gate)
{
if (!_byOwner.TryGetValue(owner, out var e)) return (Outcome.Conflict, null);
if (actingId == e.DrafterId) return (Outcome.Forbidden, null);
if (e.Status.Tag != "submitted") return (Outcome.Conflict, null);
e.Status = next(e);
return (Outcome.Ok, e);
}
}
private static bool RequiredFilled(BriefEntity e) => e.Sections.All(s => !s.Required || s.Blocks.Count > 0);
private static bool RequiredFilled(BriefEntity e) => e.Sections.All(s => !s.Required || s.Blocks.Count > 0);
}
/// <summary>Seeded template (sections + placeholder fields) and passage library.</summary>
public static class BriefSeed
{
public const string TemplateId = "besluit-arts";
public const string Beroep = "arts";
public const string TemplateId = "besluit-arts";
public const string Beroep = "arts";
private static RichTextNodeDto T(string t) => new("text", Text: t);
private static RichTextNodeDto P(string key) => new("placeholder", Key: key);
private static RichTextBlockDto Block(params RichTextNodeDto[] nodes) => new(new[] { new ParagraphDto(nodes) });
private static RichTextNodeDto T(string t) => new("text", Text: t);
private static RichTextNodeDto P(string key) => new("placeholder", Key: key);
private static RichTextBlockDto Block(params RichTextNodeDto[] nodes) => new(new[] { new ParagraphDto(nodes) });
// The template's valid placeholder fields. Two are flagged to exercise the linter:
// a deprecated field and one not fillable for this beroep.
private static readonly IReadOnlyList<PlaceholderDefDto> Placeholders = new[]
{
// The template's valid placeholder fields. Two are flagged to exercise the linter:
// a deprecated field and one not fillable for this beroep.
private static readonly IReadOnlyList<PlaceholderDefDto> Placeholders = new[]
{
new PlaceholderDefDto("naam_zorgverlener", "Naam zorgverlener", true),
new PlaceholderDefDto("datum", "Datum", true),
new PlaceholderDefDto("big_nummer", "BIG-nummer", true),
@@ -147,18 +147,18 @@ public static class BriefSeed
new PlaceholderDefDto("specialisme_code", "Specialismecode", true, Fillable: false),
};
public static BriefEntity NewBrief(string owner) => new()
{
BriefId = Guid.NewGuid().ToString(),
Owner = owner,
Beroep = Beroep,
TemplateId = TemplateId,
DrafterId = BriefStore.DrafterId,
Placeholders = Placeholders,
// aanhef + slot are predefined, locked template text (prefilled); the drafter
// composes only the unlocked `kern`. Save trusts the FE not to mutate locked
// sections (FE-authoritative for this POC — the FE reducer also refuses it).
Sections = new()
public static BriefEntity NewBrief(string owner) => new()
{
BriefId = Guid.NewGuid().ToString(),
Owner = owner,
Beroep = Beroep,
TemplateId = TemplateId,
DrafterId = BriefStore.DrafterId,
Placeholders = Placeholders,
// aanhef + slot are predefined, locked template text (prefilled); the drafter
// composes only the unlocked `kern`. Save trusts the FE not to mutate locked
// sections (FE-authoritative for this POC — the FE reducer also refuses it).
Sections = new()
{
new("aanhef", "Aanhef", true, new List<LetterBlockDto>
{
@@ -170,13 +170,13 @@ public static class BriefSeed
new("freeText", "slot-1", Block(T("Met vriendelijke groet,"))),
}, Locked: true),
},
Status = new BriefStatusDto("draft"),
};
Status = new BriefStatusDto("draft"),
};
/// Global passages plus those scoped to the given beroep.
public static IReadOnlyList<LibraryPassageDto> PassagesFor(string beroep)
{
var all = new List<LibraryPassageDto>
/// Global passages plus those scoped to the given beroep.
public static IReadOnlyList<LibraryPassageDto> PassagesFor(string beroep)
{
var all = new List<LibraryPassageDto>
{
new("p-aanhef-1", "global", "aanhef", "Standaard aanhef",
Block(T("Geachte heer/mevrouw "), P("naam_zorgverlener"), T(",")), 1),
@@ -193,6 +193,6 @@ public static class BriefSeed
new("p-slot-code", "global", "slot", "Specialismecode (niet invulbaar)",
Block(T("Specialisme: "), P("specialisme_code"), T(".")), 1),
};
return all.Where(p => p.Scope == "global" || p.Beroep == beroep).ToList();
}
return all.Where(p => p.Scope == "global" || p.Beroep == beroep).ToList();
}
}

View File

@@ -10,7 +10,7 @@ public sealed record StoredDocument(
string DocumentId, string LocalId, string CategoryId, string WizardId,
string FileName, long SizeBytes, string ContentType, byte[] Content, string Owner, DateTimeOffset UploadedAt)
{
public bool Linked { get; set; }
public bool Linked { get; set; }
}
public sealed record AuditEntry(DateTimeOffset At, string Action, string DocumentId, string CategoryId, string Actor);
@@ -22,81 +22,81 @@ public sealed record AuditEntry(DateTimeOffset At, string Action, string Documen
/// </summary>
public static class DocumentStore
{
/// The single seeded user (the demo has no real auth; ownership = this id).
public const string DemoOwner = "19012345601";
/// The single seeded user (the demo has no real auth; ownership = this id).
public const string DemoOwner = "19012345601";
private static readonly Dictionary<string, StoredDocument> _docs = new();
private static readonly List<AuditEntry> _audit = new();
private static readonly object _gate = new();
private static readonly Dictionary<string, StoredDocument> _docs = new();
private static readonly List<AuditEntry> _audit = new();
private static readonly object _gate = new();
public static StoredDocument Add(string localId, string categoryId, string wizardId, string fileName, string contentType, byte[] content, string owner)
public static StoredDocument Add(string localId, string categoryId, string wizardId, string fileName, string contentType, byte[] content, string owner)
{
var doc = new StoredDocument(Guid.NewGuid().ToString(), localId, categoryId, wizardId, fileName, content.LongLength, contentType, content, owner, DateTimeOffset.UtcNow);
lock (_gate) _docs[doc.DocumentId] = doc;
Audit("upload", doc.DocumentId, categoryId, owner);
return doc;
}
public static StoredDocument? Get(string documentId)
{
lock (_gate) return _docs.TryGetValue(documentId, out var d) ? d : null;
}
/// Status for the poll-on-return pattern: a known localId is "complete" (it
/// arrived), an unknown one is still in flight / never started.
public static IReadOnlyList<StoredDocument> ByLocalIds(IEnumerable<string> localIds)
{
var set = localIds.ToHashSet();
lock (_gate) return _docs.Values.Where(d => set.Contains(d.LocalId)).ToList();
}
/// Mark digital documents as linked to a finalised submission (blocks user delete).
public static void Link(IEnumerable<string> documentIds)
{
lock (_gate)
foreach (var id in documentIds)
if (_docs.TryGetValue(id, out var d)) d.Linked = true;
}
public enum DeleteResult { Ok, NotFound, Linked }
/// User delete: owner-scoped; blocked once linked to a finalised submission.
public static DeleteResult DeleteOwned(string documentId, string owner)
{
string categoryId;
lock (_gate)
{
var doc = new StoredDocument(Guid.NewGuid().ToString(), localId, categoryId, wizardId, fileName, content.LongLength, contentType, content, owner, DateTimeOffset.UtcNow);
lock (_gate) _docs[doc.DocumentId] = doc;
Audit("upload", doc.DocumentId, categoryId, owner);
return doc;
if (!_docs.TryGetValue(documentId, out var d) || d.Owner != owner) return DeleteResult.NotFound;
if (d.Linked) return DeleteResult.Linked;
categoryId = d.CategoryId;
_docs.Remove(documentId);
}
Audit("delete-user", documentId, categoryId, owner);
return DeleteResult.Ok;
}
public static StoredDocument? Get(string documentId)
/// Admin delete: bypasses ownership, unlinks, and (seam) flags the submission for
/// review so a caseworker is notified. Returns false if the document is unknown.
public static bool AdminDelete(string documentId, string actor)
{
string categoryId;
lock (_gate)
{
lock (_gate) return _docs.TryGetValue(documentId, out var d) ? d : null;
if (!_docs.TryGetValue(documentId, out var d)) return false;
categoryId = d.CategoryId;
_docs.Remove(documentId);
}
Audit("delete-admin", documentId, categoryId, actor);
return true;
}
/// Status for the poll-on-return pattern: a known localId is "complete" (it
/// arrived), an unknown one is still in flight / never started.
public static IReadOnlyList<StoredDocument> ByLocalIds(IEnumerable<string> localIds)
{
var set = localIds.ToHashSet();
lock (_gate) return _docs.Values.Where(d => set.Contains(d.LocalId)).ToList();
}
public static void Audit(string action, string documentId, string categoryId, string actor)
{
lock (_gate) _audit.Add(new AuditEntry(DateTimeOffset.UtcNow, action, documentId, categoryId, actor));
}
/// Mark digital documents as linked to a finalised submission (blocks user delete).
public static void Link(IEnumerable<string> documentIds)
{
lock (_gate)
foreach (var id in documentIds)
if (_docs.TryGetValue(id, out var d)) d.Linked = true;
}
public enum DeleteResult { Ok, NotFound, Linked }
/// User delete: owner-scoped; blocked once linked to a finalised submission.
public static DeleteResult DeleteOwned(string documentId, string owner)
{
string categoryId;
lock (_gate)
{
if (!_docs.TryGetValue(documentId, out var d) || d.Owner != owner) return DeleteResult.NotFound;
if (d.Linked) return DeleteResult.Linked;
categoryId = d.CategoryId;
_docs.Remove(documentId);
}
Audit("delete-user", documentId, categoryId, owner);
return DeleteResult.Ok;
}
/// Admin delete: bypasses ownership, unlinks, and (seam) flags the submission for
/// review so a caseworker is notified. Returns false if the document is unknown.
public static bool AdminDelete(string documentId, string actor)
{
string categoryId;
lock (_gate)
{
if (!_docs.TryGetValue(documentId, out var d)) return false;
categoryId = d.CategoryId;
_docs.Remove(documentId);
}
Audit("delete-admin", documentId, categoryId, actor);
return true;
}
public static void Audit(string action, string documentId, string categoryId, string actor)
{
lock (_gate) _audit.Add(new AuditEntry(DateTimeOffset.UtcNow, action, documentId, categoryId, actor));
}
public static IReadOnlyList<AuditEntry> AuditLog
{
get { lock (_gate) return _audit.ToList(); }
}
public static IReadOnlyList<AuditEntry> AuditLog
{
get { lock (_gate) return _audit.ToList(); }
}
}

View File

@@ -10,31 +10,31 @@ namespace BigRegister.Api.Data;
/// </summary>
public static class SeedData
{
public static readonly Registration Registration = new(
BigNummer: "19012345601",
Naam: "Dr. A. (Anna) de Vries",
Beroep: "Arts",
Registratiedatum: new DateOnly(2012, 9, 1),
Geboortedatum: new DateOnly(1985, 3, 14),
Status: new RegistrationStatus(StatusTag.Geregistreerd, HerregistratieDatum: new DateOnly(2027, 3, 1)));
public static readonly Registration Registration = new(
BigNummer: "19012345601",
Naam: "Dr. A. (Anna) de Vries",
Beroep: "Arts",
Registratiedatum: new DateOnly(2012, 9, 1),
Geboortedatum: new DateOnly(1985, 3, 14),
Status: new RegistrationStatus(StatusTag.Geregistreerd, HerregistratieDatum: new DateOnly(2027, 3, 1)));
public static readonly Person Person = new(
Naam: "Dr. A. (Anna) de Vries",
Geboortedatum: new DateOnly(1985, 3, 14),
Adres: new Adres("Lange Voorhout 9", "2514 EA", "Den Haag"));
public static readonly Person Person = new(
Naam: "Dr. A. (Anna) de Vries",
Geboortedatum: new DateOnly(1985, 3, 14),
Adres: new Adres("Lange Voorhout 9", "2514 EA", "Den Haag"));
/// <summary>The address BRP returns for the seeded citizen.</summary>
public static readonly Adres BrpAddress = Person.Adres;
/// <summary>The address BRP returns for the seeded citizen.</summary>
public static readonly Adres BrpAddress = Person.Adres;
public static readonly IReadOnlyList<Diploma> Diplomas = new[]
{
public static readonly IReadOnlyList<Diploma> Diplomas = new[]
{
new Diploma("d1", "Geneeskunde", "Universiteit Leiden", 2011, "geneeskunde", Engelstalig: false),
new Diploma("d2", "Medicine (MBChB)", "University of Edinburgh", 2013, "geneeskunde", Engelstalig: true),
new Diploma("d3", "HBO-Verpleegkunde", "Hogeschool Utrecht", 2016, "verpleegkunde", Engelstalig: false),
};
public static readonly IReadOnlyList<(string Type, string Omschrijving, string Datum)> Notes = new[]
{
public static readonly IReadOnlyList<(string Type, string Omschrijving, string Datum)> Notes = new[]
{
("Specialisme", "Huisartsgeneeskunde", "2016-04-12"),
("Aantekening", "Erkend opleider huisartsgeneeskunde", "2019-01-08"),
("Specialisme", "Spoedeisende hulp (kaderopleiding)", "2021-06-30"),