style: format backend with dotnet format
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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(); }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"),
|
||||
|
||||
Reference in New Issue
Block a user