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
@@ -229,6 +229,44 @@ public class InvitationRepository : IInvitationRepository
public void Remove(Invitation invitation) => _db.Invitations.Remove(invitation);
}
/// <summary>De soorten kluisitems; zie <see cref="IVaultCategoryRepository"/>.</summary>
public class VaultCategoryRepository : IVaultCategoryRepository
{
private readonly MijnBesluitDbContext _db;
public VaultCategoryRepository(MijnBesluitDbContext db) => _db = db;
public async Task<IReadOnlyList<VaultCategoryDefinition>> ListAsync(CancellationToken ct = default) =>
await _db.VaultCategories.Include(c => c.Velden)
.OrderBy(c => c.Volgorde).ThenBy(c => c.Naam).ToListAsync(ct);
public Task<VaultCategoryDefinition?> FindAsync(int id, CancellationToken ct = default) =>
_db.VaultCategories.Include(c => c.Velden).FirstOrDefaultAsync(c => c.Id == id, ct);
public Task<VaultCategoryField?> FindFieldAsync(long fieldId, CancellationToken ct = default) =>
_db.VaultCategoryFields.FirstOrDefaultAsync(f => f.Id == fieldId, ct);
public async Task<IReadOnlyList<SoortGebruik>> GebruikAsync(CancellationToken ct = default) =>
await _db.VaultItems.Where(v => v.DeletedAt == null)
.GroupBy(v => v.Category)
.Select(g => new SoortGebruik((int)g.Key, g.Count(), g.Select(v => v.OwnerPersonId).Distinct().Count()))
.ToListAsync(ct);
public async Task<IReadOnlySet<int>> InGebruikDoorAsync(long personId, CancellationToken ct = default)
{
var lijst = await _db.VaultItems.Where(v => v.OwnerPersonId == personId && v.DeletedAt == null)
.Select(v => v.Category).Distinct().ToListAsync(ct);
return lijst.Select(c => (int)c).ToHashSet();
}
public Task<int> AantalItemsAsync(int categoryId, CancellationToken ct = default) =>
_db.VaultItems.CountAsync(v => v.Category == (VaultCategory)categoryId && v.DeletedAt == null, ct);
public void Add(VaultCategoryDefinition soort) => _db.VaultCategories.Add(soort);
public void Remove(VaultCategoryDefinition soort) => _db.VaultCategories.Remove(soort);
public void Add(VaultCategoryField veld) => _db.VaultCategoryFields.Add(veld);
public void Remove(VaultCategoryField veld) => _db.VaultCategoryFields.Remove(veld);
}
public class VaultRepository : IVaultRepository
{
private readonly MijnBesluitDbContext _db;