feat(stamdata): profession↔diploma map as validated config-as-code
Extract the profession↔diploma table out of DiplomaRules into a dedicated Stamdata.Professions module (business-editable data, separated from the rules that consume it) and add StamdataValidationTests as the build-time gate: every seeded diploma program must resolve to a real profession, no blank entries. A bad edit now fails the build instead of silently rendering "Onbekend". Rules and behaviour unchanged. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -1,29 +1,23 @@
|
|||||||
|
using BigRegister.Stamdata;
|
||||||
|
|
||||||
namespace BigRegister.Domain.Diplomas;
|
namespace BigRegister.Domain.Diplomas;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// SERVER-OWNED business rules for diplomas. This is the single place a policy
|
/// SERVER-OWNED business rules for diplomas: which profession a study program maps to,
|
||||||
/// changes: which profession a study program maps to, and which policy questions
|
/// and which policy questions (geldigheidsvragen) apply to a diploma. The frontend
|
||||||
/// (geldigheidsvragen) apply to a diploma. The frontend renders these; it never
|
/// renders these; it never derives them.
|
||||||
/// derives them.
|
///
|
||||||
|
/// The profession↔program *data* is business-editable stamdata in
|
||||||
|
/// <see cref="Professions"/> (config-as-code, ADR-0004); the *rules* below consume it.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static class DiplomaRules
|
public static class DiplomaRules
|
||||||
{
|
{
|
||||||
// RULE: study program → BIG profession.
|
// RULE: study program → BIG profession (data lives in Stamdata.Professions).
|
||||||
private static readonly Dictionary<string, string> ProfessionByProgram = new(StringComparer.OrdinalIgnoreCase)
|
|
||||||
{
|
|
||||||
["geneeskunde"] = "Arts",
|
|
||||||
["verpleegkunde"] = "Verpleegkundige",
|
|
||||||
["fysiotherapie"] = "Fysiotherapeut",
|
|
||||||
["farmacie"] = "Apotheker",
|
|
||||||
["tandheelkunde"] = "Tandarts",
|
|
||||||
};
|
|
||||||
|
|
||||||
public static string ProfessionFor(Diploma d) =>
|
public static string ProfessionFor(Diploma d) =>
|
||||||
ProfessionByProgram.TryGetValue(d.Opleiding, out var beroep) ? beroep : "Onbekend";
|
Professions.ByProgram.TryGetValue(d.Opleiding, out var beroep) ? beroep : "Onbekend";
|
||||||
|
|
||||||
/// <summary>Professions a user may declare for a manual (unlisted) diploma.</summary>
|
/// <summary>Professions a user may declare for a manual (unlisted) diploma.</summary>
|
||||||
public static IReadOnlyList<string> ManualProfessions() =>
|
public static IReadOnlyList<string> ManualProfessions() => Professions.All();
|
||||||
ProfessionByProgram.Values.Distinct().ToList();
|
|
||||||
|
|
||||||
// --- Policy questions (geldigheidsvragen) ---
|
// --- Policy questions (geldigheidsvragen) ---
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
namespace BigRegister.Stamdata;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// BUSINESS-EDITABLE STAMDATA (config-as-code). Which BIG profession (beroep) each
|
||||||
|
/// study program (opleiding) maps to. This is the one table the business tunes when
|
||||||
|
/// a program starts or stops leading to a registered profession.
|
||||||
|
///
|
||||||
|
/// Change it by editing this file and opening a PR — NOT via a production database.
|
||||||
|
/// The C# compiler catches shape/type mistakes; <c>StamdataValidationTests</c> catches
|
||||||
|
/// the referential integrity it can't (e.g. a seeded diploma whose program has no
|
||||||
|
/// profession here). So a bad edit fails the build, never prod. See ADR-0004
|
||||||
|
/// (docs/reference/architecture/0004-stamdata-as-code.md).
|
||||||
|
///
|
||||||
|
/// This is DATA, not logic: the rules that consume it (which questions a diploma needs,
|
||||||
|
/// how a manual diploma is treated) stay in <c>DiplomaRules</c>.
|
||||||
|
/// </summary>
|
||||||
|
public static class Professions
|
||||||
|
{
|
||||||
|
public static readonly IReadOnlyDictionary<string, string> ByProgram =
|
||||||
|
new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
|
||||||
|
{
|
||||||
|
["geneeskunde"] = "Arts",
|
||||||
|
["verpleegkunde"] = "Verpleegkundige",
|
||||||
|
["fysiotherapie"] = "Fysiotherapeut",
|
||||||
|
["farmacie"] = "Apotheker",
|
||||||
|
["tandheelkunde"] = "Tandarts",
|
||||||
|
};
|
||||||
|
|
||||||
|
/// <summary>Distinct professions, in declaration order — the list a user may declare
|
||||||
|
/// for a manual (unlisted) diploma.</summary>
|
||||||
|
public static IReadOnlyList<string> All() => ByProgram.Values.Distinct().ToList();
|
||||||
|
}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
using BigRegister.Api.Data;
|
||||||
|
using BigRegister.Domain.Diplomas;
|
||||||
|
using BigRegister.Stamdata;
|
||||||
|
|
||||||
|
namespace BigRegister.Tests;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The compile-time gate for business-editable stamdata (ADR-0004). The C# compiler
|
||||||
|
/// already catches shape/type mistakes; these tests catch the referential integrity it
|
||||||
|
/// can't, so a bad config edit fails the build instead of reaching production.
|
||||||
|
/// </summary>
|
||||||
|
public class StamdataValidationTests
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void Every_seeded_diploma_program_maps_to_a_known_profession()
|
||||||
|
{
|
||||||
|
// The dangling-reference guard: a seed program with no entry in Professions would
|
||||||
|
// silently render "Onbekend" to the user. Fail the build instead.
|
||||||
|
foreach (var d in SeedData.Diplomas)
|
||||||
|
Assert.True(DiplomaRules.ProfessionFor(d) != "Onbekend",
|
||||||
|
$"Diploma program '{d.Opleiding}' has no profession in Stamdata.Professions.");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Profession_map_has_no_blank_programs_or_professions()
|
||||||
|
{
|
||||||
|
Assert.All(Professions.ByProgram, kv =>
|
||||||
|
{
|
||||||
|
Assert.False(string.IsNullOrWhiteSpace(kv.Key), "A profession-map program key is blank.");
|
||||||
|
Assert.False(string.IsNullOrWhiteSpace(kv.Value), $"Program '{kv.Key}' maps to a blank profession.");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Manual_professions_are_non_empty_and_distinct()
|
||||||
|
{
|
||||||
|
var professions = DiplomaRules.ManualProfessions();
|
||||||
|
Assert.NotEmpty(professions);
|
||||||
|
Assert.Equal(professions.Count, professions.Distinct().Count());
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user