Second template axis (org identity: letterhead, footer, signature, margins) server-side: OrgTemplateStore with JSON version history, publish/rollback, sent-brief version pinning, admin role + capability, 5 admin endpoints, org-logo upload category. FE seam widened only (Role/Capability unions, interceptor); WP-24/26 consume it. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
92 lines
4.3 KiB
C#
92 lines
4.3 KiB
C#
namespace BigRegister.Domain.Documents;
|
|
|
|
/// <summary>
|
|
/// SERVER-OWNED document category config. The frontend renders these; it never
|
|
/// hardcodes file types, size limits, labels, required-ness or the post-delivery
|
|
/// flag. Adding a new category or changing a rule is a backend-only change.
|
|
/// </summary>
|
|
public sealed record DocumentCategory(
|
|
string CategoryId,
|
|
string Label,
|
|
string Description,
|
|
bool Required,
|
|
IReadOnlyList<string> AcceptedTypes,
|
|
int MaxSizeMb,
|
|
bool Multiple,
|
|
bool AllowPostDelivery);
|
|
|
|
public static class DocumentRules
|
|
{
|
|
private static readonly string[] Pdf = { "application/pdf" };
|
|
private static readonly string[] PdfImage = { "application/pdf", "image/jpeg", "image/png" };
|
|
private static readonly string[] Image = { "image/jpeg", "image/png" };
|
|
|
|
private static readonly DocumentCategory Diploma = new("diploma", "Diplomabewijs",
|
|
"Upload uw diploma als PDF-bestand.", true, Pdf, 10, false, false);
|
|
private static readonly DocumentCategory Identiteit = new("identiteit", "Identiteitsbewijs",
|
|
"Upload een kopie van uw paspoort of ID-kaart.", true, PdfImage, 10, false, true);
|
|
private static readonly DocumentCategory Taalvaardigheid = new("taalvaardigheid", "Bewijs Nederlandse taalvaardigheid",
|
|
"Upload een bewijs van uw Nederlandse taalvaardigheid op het vereiste niveau (B2).", true, PdfImage, 10, false, true);
|
|
|
|
/// <summary>
|
|
/// The MAXIMAL set of categories a wizard can ever ask for. Used to validate an
|
|
/// upload POST (any real category must resolve) — <see cref="CategoriesFor"/>
|
|
/// decides which subset is actually presented for a given set of answers.
|
|
/// </summary>
|
|
public static IReadOnlyList<DocumentCategory> AllCategoriesFor(string wizardId) => wizardId switch
|
|
{
|
|
"registratie" => new[] { Diploma, Identiteit, Taalvaardigheid },
|
|
"herregistratie" => new[]
|
|
{
|
|
new DocumentCategory("werkervaring", "Bewijs van werkervaring",
|
|
"Upload bewijs van uw gewerkte uren (bijv. een werkgeversverklaring).", true, Pdf, 10, true, true),
|
|
new DocumentCategory("nascholing", "Nascholingscertificaten",
|
|
"Upload uw nascholingscertificaten (optioneel).", false, PdfImage, 10, true, true),
|
|
},
|
|
// WP-23: the admin's org-template logo rides the same upload machinery as the
|
|
// wizard documents — one category under its own "wizard" id.
|
|
"org-template" => new[]
|
|
{
|
|
new DocumentCategory("org-logo", "Organisatielogo",
|
|
"Upload het logo van de organisatie (PNG of JPG).", false, Image, 1, false, false),
|
|
},
|
|
_ => Array.Empty<DocumentCategory>(),
|
|
};
|
|
|
|
/// <summary>
|
|
/// The categories to PRESENT for a wizard, given the answers that affect required
|
|
/// documents. RULES (registratie): a diploma upload is required ONLY for a manually
|
|
/// entered diploma (a DUO diploma is verified digitally, and nothing is required
|
|
/// before a diploma is chosen); proof of Dutch taalvaardigheid is required only when
|
|
/// the applicant confirms ("ja") they meet the language requirement. Answer-agnostic
|
|
/// wizards get their full set.
|
|
/// </summary>
|
|
public static IReadOnlyList<DocumentCategory> CategoriesFor(
|
|
string wizardId, string? diplomaHerkomst = null, string? taalvaardigheid = null)
|
|
{
|
|
if (wizardId != "registratie") return AllCategoriesFor(wizardId);
|
|
var result = new List<DocumentCategory>();
|
|
if (diplomaHerkomst == "handmatig") result.Add(Diploma);
|
|
result.Add(Identiteit);
|
|
if (taalvaardigheid == "ja") result.Add(Taalvaardigheid);
|
|
return result;
|
|
}
|
|
|
|
public static DocumentCategory? Find(string wizardId, string categoryId) =>
|
|
AllCategoriesFor(wizardId).FirstOrDefault(c => c.CategoryId == categoryId);
|
|
|
|
/// <summary>
|
|
/// Authoritative upload validation (the client check is UX-only). Returns a
|
|
/// rejection reason, or null when the file is acceptable.
|
|
/// </summary>
|
|
public static string? RejectUpload(DocumentCategory? category, string contentType, long sizeBytes)
|
|
{
|
|
if (category is null) return "Onbekende documentcategorie.";
|
|
if (!category.AcceptedTypes.Contains(contentType))
|
|
return $"Bestandstype niet toegestaan voor {category.Label}.";
|
|
if (sizeBytes > (long)category.MaxSizeMb * 1024 * 1024)
|
|
return $"Bestand is groter dan {category.MaxSizeMb} MB.";
|
|
return null;
|
|
}
|
|
}
|