Flexibele kluissoorten: soorten in de database, beheer op de website

- Tabellen vault_categories en vault_category_fields (db/21), startrijen handmatig (db/handmatig/21b)
- VaultCategoryService met regels voor zichtbaarheid, gebruik en eigen velden
- Beheer > Kluissoorten (/beheer/soorten) met gebruikstellers; soort in gebruik niet te verwijderen
- Kluis en Nalatenschap (web en app) tonen soorten en eigen velden uit de database
- Socketprotocol: vault.categories; GRANTs voor mijnbesluit_app in db/02
- db/20 herhaalbaar gemaakt
- App versie 1.1 (versiecode 3)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014MGLqt1uKfNARQHut9ifcx
This commit is contained in:
jf
2026-10-20 08:15:40 +00:00
co-authored by Claude Sonnet 5.5
parent a3a0bcfc05
commit af13d0e533
36 changed files with 2540 additions and 94 deletions
+43 -3
View File
@@ -15,6 +15,12 @@ public class RequestScope
public required LegacyService Legacy { get; init; }
public required OverzichtService Overzicht { get; init; }
/// <summary>
/// De soorten kluisitems. Optioneel: zonder deze dienst werkt de kluis met de ingebouwde soorten
/// en krijgen items geen soortnaam mee, en de app valt terug op haar eigen lijst.
/// </summary>
public VaultCategoryService? Soorten { get; init; }
/// <summary>
/// Voor het samenvoegen van dubbele profielen. Optioneel: zonder deze dienst werkt alles,
/// alleen wordt een tweede uitnodiging dan geweigerd in plaats van samengevoegd.
@@ -76,6 +82,17 @@ public class SocketDispatcher
f.Id, f.FileName, f.ContentType, f.SizeBytes, f.CreatedAt, f.IsProfilePhoto
});
/// <summary>
/// De namen van alle soorten op nummer, om onder elk kluisitem te kunnen zetten hoe het heet.
/// Een soort die de beheerder zelf maakte staat niet in de lijst van de app zelf, dus de naam
/// reist met het item mee. Zonder soortendienst is de lijst leeg en valt de app terug.
/// </summary>
private static async Task<IReadOnlyDictionary<int, string>> SoortNamenAsync(RequestScope scope, CancellationToken ct) =>
scope.Soorten is null ? new Dictionary<int, string>() : await scope.Soorten.NamenAsync(ct);
private static string? SoortNaam(IReadOnlyDictionary<int, string> namen, int soort) =>
namen.TryGetValue(soort, out var naam) ? naam : null;
/// <summary>Voor als er geen klok wordt meegegeven; de overdracht heeft er wel een nodig.</summary>
private sealed class SysteemKlok : MijnBesluit.Application.Abstractions.IClock
{
@@ -451,12 +468,31 @@ public class SocketDispatcher
{
var p = request.PayloadAs<VaultListPayload>();
var items = await scope.Vault.ListAsync(session!.PersonId, p?.Category, ct);
var namen = await SoortNamenAsync(scope, ct);
return SocketResponse.Ok(id, items.Select(i => new
{
i.Id, i.Title, i.Category, i.ReleasePolicy, i.ReleaseAt, i.UpdatedAt, i.SharedManagerPersonId
i.Id, i.Title, i.Category, i.ReleasePolicy, i.ReleaseAt, i.UpdatedAt, i.SharedManagerPersonId,
CategoryName = SoortNaam(namen, (int)i.Category)
}));
}
case SocketActions.VaultCategory:
{
var p = request.PayloadAs<IdPayload>();
if (p is null) return SocketResponse.Error(id, "Geen soort opgegeven.");
var soort = scope.Soorten is null ? null : await scope.Soorten.SoortAsync((int)p.Id, ct);
return soort is null ? SocketResponse.Error(id, "Deze soort bestaat niet.") : SocketResponse.Ok(id, soort);
}
case SocketActions.VaultCategories:
{
// Zonder soortendienst een lege lijst: de app valt dan terug op haar ingebouwde soorten.
var lijst = scope.Soorten is null
? Array.Empty<KluisSoort>()
: (await scope.Soorten.ZichtbaarVoorAsync(session!.PersonId, ct)).ToArray();
return SocketResponse.Ok(id, lijst);
}
case SocketActions.VaultOpen:
{
var p = request.PayloadAs<IdPayload>();
@@ -807,10 +843,12 @@ public class SocketDispatcher
var r = await scope.Legacy.NalatenschapItemsAsync(p.PersonId, session!.UserId, ct);
if (!r.Succeeded) return SocketResponse.Error(id, r.Error!);
var nalatenschapNamen = await SoortNamenAsync(scope, ct);
return SocketResponse.Ok(id, r.Value!.Select(i => new
{
i.Id, i.Title, Category = (int)i.Category, ReleasePolicy = (int)i.ReleasePolicy,
i.ReleaseAt, i.CreatedAt
i.ReleaseAt, i.CreatedAt,
CategoryName = SoortNaam(nalatenschapNamen, (int)i.Category)
}));
}
@@ -828,10 +866,12 @@ public class SocketDispatcher
var r = await scope.Vault.ListSharedWithMeAsync(session!.UserId, ct);
if (!r.Succeeded) return SocketResponse.Error(id, r.Error!);
var gedeeldNamen = await SoortNamenAsync(scope, ct);
return SocketResponse.Ok(id, r.Value!.Select(i => new
{
i.Id, i.Title, Category = (int)i.Category, ReleasePolicy = (int)i.ReleasePolicy,
i.ReleaseAt, i.UpdatedAt, i.SharedManagerPersonId
i.ReleaseAt, i.UpdatedAt, i.SharedManagerPersonId,
CategoryName = SoortNaam(gedeeldNamen, (int)i.Category)
}));
}