Skip to main content

Meldingen/tickets en beheerafhandeling

14.1 Doel en scope

Dit hoofdstuk beschrijft de technische inrichting van het meldingen- en ticketdomein binnen OefenHub. Het hoofdstuk werkt uit hoe gebruikersmeldingen, beheerafhandeling, discussies, sluitingen, heropeningen, doorzetten naar docent, statusafleiding, logging en periodieke verwerking technisch worden gerealiseerd.

Het meldingen-/ticketdomein wordt in de solution ondergebracht in het project OefenHub.Support.

OefenHub.Support is eigenaar van:

  • het aanmaken en beheren van meldingen/tickets;
  • ticketstatussen en procesovergangen;
  • interne en externe ticketdiscussie;
  • beheerderkoppelingen aan meldingen;
  • formele sluitregistraties en oplossingsinformatie;
  • heropenverzoeken;
  • doorzetten naar docent;
  • ticketgeschiedenis en domeinspecifieke auditregels;
  • ticketgerichte readmodels voor gebruikers- en beheerweergaven;
  • ticketgerichte periodieke verwerking, waarbij OefenHub.Scheduling de technische joblifecycle beheert.

Dit hoofdstuk introduceert geen nieuwe functionele requirements. Functionele regels blijven afkomstig uit Functioneel Ontwerp, Software Requirements Specification, usecases en schermdocumentatie. De de de database-informatie blijft leidend voor tabellen, kolommen, waardelijsten en relationele broninformatie. Dit hoofdstuk van het Technisch Ontwerp beschrijft de technische vertaling naar projectstructuur, services, DbContext, workflowgrenzen, logging, security en integratie met andere modules.

14.2 Project, DbContext en schema

Het supportdomein volgt de algemene modulaire-monolietregel: één project heeft maximaal één eigen DbContext en daarmee één eigen databaseschema.

OnderdeelKeuze
ProjectOefenHub.Support
DbContextSupportDbContext
Schemasupport
Eigenaar van brondataSupportmodule
Cross-module toegangAlleen via publieke contracts
Directe toegang tot supporttabellen vanuit andere modulesNiet toegestaan
Audit/historyDomeinspecifiek binnen support, geen centrale auditmodule

Het schema support bevat de ticketbrondata en ticketgeschiedenis. Concrete tabeldefinities worden niet in dit hoofdstuk gedupliceerd; daarvoor blijft database-informatie leidend. Conceptueel vallen onder het supportdomein minimaal de volgende groepen:

DatagroepTechnische betekenis
TicketsHoofdrecord van een melding/ticket.
Ticketstatussen en resolutietypenWaardelijsten voor processtatus en afsluitstatus.
TicketAssignmentsActieve of historische beheerderkoppelingen aan een ticket.
TicketDiscussionMessagesInterne en externe discussieberichten binnen een ticket.
TicketClosuresFormele sluitingen, oplossingen, heropentermijnen en acceptatie door gebruiker.
TicketReopenRequestsHeropenverzoeken door gebruiker of beheerder.
TicketForwardedToTeacherRegistratie van doorzetten naar docent.
TicketHistoryCompacte domeinhistorie/audit voor ticketacties.
TicketTechnicalSnapshotsTechnische momentopname van de meldingcontext.

Cross-module verwijzingen, zoals CreatedByUserId, AssignedAdminUserId, ForwardedTeacherUserId of verwijzingen naar een gebruiker namens wie een bericht wordt gestuurd, worden standaard als soft link opgeslagen. Waar historische leesbaarheid nodig is, worden aanvullende snapshotvelden gebruikt.

14.3 Moduleprojectstructuur

OefenHub.Support gebruikt dezelfde basisstructuur als andere domeinmodules.

OefenHub.Support/
Contracts/
Models/
Enums/

Data/
SupportDbContext.cs
Entities/
Configurations/
Migrations/

Models/
Commands/
Queries/
ReadModels/
Enums/

Services/
Interfaces/

Events/

Helpers/

Extensions/

14.3.1 Publieke contracts

Andere modules mogen het supportdomein alleen via publieke contracts gebruiken. Voorbeelden:

public interface ITicketCommandService
{
Task<CreateTicketResult> CreateTicketAsync(CreateTicketCommand command, CancellationToken cancellationToken);
Task<CloseTicketResult> CloseTicketAsync(CloseTicketCommand command, CancellationToken cancellationToken);
Task<ReopenTicketResult> ReopenTicketAsync(ReopenTicketCommand command, CancellationToken cancellationToken);
}

public interface ITicketQueryService
{
Task<UserTicketOverviewReadModel> GetUserTicketsAsync(UserTicketOverviewQuery query, CancellationToken cancellationToken);
Task<AdminTicketOverviewReadModel> GetAdminTicketsAsync(AdminTicketOverviewQuery query, CancellationToken cancellationToken);
}

public interface ITicketActionIndicatorReader
{
Task<TicketActionIndicatorReadModel> GetActionIndicatorAsync(TicketActionIndicatorQuery query, CancellationToken cancellationToken);
}

De concrete service-implementaties, entities, EF-configuraties en interne workflowhelpers zijn internal, tenzij zij bewust onderdeel zijn van het publieke contract.

14.3.2 Readmodels

Module-eigen readmodels staan onder Models/ReadModels.

Voorbeelden:

Models/ReadModels/UserTicketOverviewReadModel.cs
Models/ReadModels/UserTicketDetailReadModel.cs
Models/ReadModels/AdminTicketOverviewReadModel.cs
Models/ReadModels/AdminTicketDetailReadModel.cs
Models/ReadModels/TicketActionIndicatorReadModel.cs

Samengestelde UI-viewmodels voor Blazor-schermen horen in OefenHub.Web/ViewModels of OefenHub.Web/PageComposition. Web stelt zulke viewmodels samen via publieke query-services van Support en andere modules, niet via directe DbContext-toegang.

14.4 Relatie tot andere modules

ModuleRelatie met Support
OefenHub.WebToont gebruikers- en beheerschermen, roept publieke supportcontracts aan en voert geen directe data-access uit.
OefenHub.IdentityLevert interne accountcontext en soft-link identifiers; Support beheert geen credentials.
OefenHub.AuthorizationLevert rolcontext, objecttoegang en policychecks voor gebruiker en beheerder.
OefenHub.CommunicationMaakt systeemberichten en privéberichten aan op verzoek van Support.
OefenHub.RelationshipsWordt gebruikt bij doorzetten naar docent om geldige docentcontext te bepalen.
OefenHub.CatalogKan context leveren wanneer een melding betrekking heeft op niveau, categorie, oefening of moduleconfiguratie.
OefenHub.PracticeKan context leveren wanneer een melding betrekking heeft op een oefenrun of resultaat.
OefenHub.SchedulingBeheert technische lifecycle van jobs rond verlopen heropentermijnen en retrybare ticketverwerking.
OefenHub.AdminKan beheercontext of instellingen leveren, maar wordt geen eigenaar van ticketbrondata.
OefenHub.InfrastructureLevert technische infrastructuur zoals logging, clock, configuratie en externe integratiehelpers.

Support is functioneel eigenaar van ticketflows. Wanneer een ticketflow andere modules raakt, orkestreert Support de workflow via publieke modulecontracts. Andere modules mogen geen supporttabellen direct wijzigen.

14.5 Statusmodel en afgeleide gebruikersstatus

Het supportdomein gebruikt processtatussen als technische bron voor de lifecycle. De gebruikersgerichte status kan daarvan afgeleid worden.

ConceptTechnische betekenis
ProcesstatusBackendstatus van het ticket, zoals New, InProgress, WaitingForUser of Closed.
AfsluitstatusInhoudelijke uitkomst van een sluiting, zoals technisch opgelost, workaround of moduleconfiguratie.
Gebruikersstatus OpgelostAfgeleid uit een actuele sluitregistratie met nog geldige heropentermijn.
Gebruikersstatus GeslotenAfgeleid uit een gesloten ticket waarvan de heropentermijn niet meer relevant is of door de gebruiker is geaccepteerd.

Opgelost is geen aparte backendstatus en geen aparte tabel. De actuele gebruikersstatus wordt afgeleid uit de combinatie van ticketstatus, actuele sluitregistratie, heropentermijn en eventuele acceptatie door de gebruiker.

Voorbeeld:

Ticket.Status = Closed
TicketClosures.ReopenDeadlineUtc > now
TicketClosures.AcceptedAtUtc = null
=> gebruikersstatus: Opgelost
Ticket.Status = Closed
TicketClosures.AcceptedAtUtc != null
=> gebruikersstatus: Gesloten

De afleiding hoort in een supportquery/readmodelservice, niet in Blazor-componenten.

14.6 Ticket lifecycle

De ticket lifecycle wordt technisch verwerkt via supportservices. UI-acties uit OefenHub.Web mogen geen status rechtstreeks zetten.

ActieTechnische verwerking
Nieuwe melding makenTicket aanmaken, technische snapshot vastleggen, historyregel schrijven en eventueel systeembericht aan gebruiker maken.
Beheerder koppelenAssignment aanmaken of activeren, status naar behandelbaar brengen en historyregel schrijven.
Extern bericht plaatsenExtern discussiebericht opslaan, status eventueel aanpassen en systeemcommunicatie aan gebruiker triggeren.
Intern bericht plaatsenIntern discussiebericht opslaan zonder communicatie naar gebruiker.
Wachten op gebruikerStatus naar WaitingForUser, extern bericht verplicht, actie-indicator afleidbaar maken.
Gebruiker reageertExtern discussiebericht opslaan en ticket opnieuw behandelbaar maken volgens actieve assignmentcontext.
Oplossen/sluiten door beheerClosure opslaan, status sluiten, heropentermijn bepalen, historyregel schrijven en gebruiker informeren.
Sluiten door gebruikerClosure met resolutietype ClosedByUser, externe reden vastleggen en historyregel schrijven.
Heropenen door gebruikerReopen request, extern discussie-item, historyregel en status opnieuw behandelbaar maken.
Heropenen door beheerReopen request, actieve assignments resetten, status terug naar New en historyregels schrijven.
Doorzetten naar docentSamengestelde supportworkflow met closure, forward-record, communicatie en history.
Verlopen heropentermijnPeriodieke idempotente verwerking via Scheduling/TickerQ.

14.7 Aanmaken van een melding

Het aanmaken van een melding is een supportcommand met server-side validatie.

Minimale technische stappen:

  1. Lees de actuele gebruikers- en rolcontext server-side.
  2. Valideer categorie, onderwerp en beschrijving.
  3. Verzamel technische context, zoals pagina, user agent en rolmomentopname.
  4. Maak het ticketrecord aan.
  5. Sla de technische snapshot op.
  6. Schrijf een compacte TicketHistory-regel.
  7. Maak, wanneer functioneel vereist, een systeembericht via Communication.
  8. Geef een gebruikersgerichte bevestiging terug zonder technische details.

Voorbeeldflow:

Web
-> ITicketCommandService.CreateTicketAsync
-> Support valideert gebruiker en invoer
-> Support schrijft Ticket + TicketTechnicalSnapshot + TicketHistory
-> Support vraagt Communication om SystemMessage te maken
-> resultaat naar Web

Of het systeembericht onderdeel is van dezelfde kritieke transactie wordt per workflow bepaald. Voor een bevestiging aan de melder kan het systeembericht minder kritisch zijn wanneer het ticket ook via Mijn meldingen zichtbaar is. Voor andere flows kan communicatie juist kritiek zijn als het de primaire ingang voor een vervolgactie vormt.

14.8 Gebruikersweergave

De gebruikersweergave toont uitsluitend tickets waarvan de ingelogde gebruiker de melder/eigenaar is. Deze begrenzing wordt server-side afgedwongen in de supportqueryservice.

OnderdeelTechnische invulling
Mijn meldingenQuery op eigen tickets via ITicketQueryService.
OpenAfgeleid readmodel over tickets die nog niet functioneel gesloten zijn en niet op gebruiker wachten.
Wacht op mijAfgeleid uit tickets van gebruiker met WaitingForUser.
GeslotenAfgeleid uit gesloten/opgeloste tickets.
Actie-indicatieAfgeleid via ITicketActionIndicatorReader, niet als losse teller opgeslagen.
DetailpaginaQuery valideert ticket ownership opnieuw.
DiscussietabToont alleen externe discussieberichten.
OplossingstabToont actuele sluitinformatie en heropenmogelijkheid indien toegestaan.

Routeparameters zoals ticket-id of meldingsnummer zijn nooit autorisatiebewijs. Iedere detail-, discussie-, sluit-, heropen- en reactiestap herhaalt server-side de objecttoegangscontrole.

14.9 Beheerweergave

Beheerders mogen tickets raadplegen en behandelen via de beheercontext, maar ook voor beheerders blijft server-side contextcontrole verplicht.

OnderdeelTechnische invulling
BeheeroverzichtQuery over alle tickets binnen beheercontext.
FiltersServer-side toegepast op status, assignment, categorie en zoekterm.
ZoekenQueryservice zoekt op meldingsnummer, onderwerp, categorie en relevante discussie-inhoud.
DetailweergaveBeheerreadmodel met interne en externe informatie.
AssignmentMutatie via commandservice, niet rechtstreeks via UI.
Interne discussieAlleen zichtbaar voor beheerdercontext.
Externe discussieZichtbaar voor melder en beheerders, triggert eventueel systeemcommunicatie.
Geavanceerde metadataAlleen beheerreadmodel, niet zichtbaar voor melder.
GeschiedenisCompacte domeinhistorie uit TicketHistory.

Beheerhistory is niet bedoeld als volledige tekstuele discussie. Vrije toelichtingen horen in discussie, sluiting, heropenverzoek of doorzettoelichting. TicketHistory blijft compact en reconstructief.

14.10 Interne en externe discussie

Ticketdiscussie wordt opgeslagen binnen support, niet als privéberichtthread.

ZichtbaarheidDoelGebruikerszichtbaar
InternBeheeranalyse en interne afstemmingNee
ExternCommunicatie met melderJa
SysteemgegenereerdKorte markering binnen ticketcontextAfhankelijk van zichtbaarheid

Externe beheerberichten worden richting de gebruiker generiek weergegeven als Beheerder. De echte actor blijft technisch herleidbaar via actor-id, actorrolcontext en history/logging.

Gebruikersreacties zijn altijd extern. Een gebruiker kan geen intern bericht plaatsen en kan geen specifieke beheerder kiezen.

Technische regels:

  • Interne berichten veroorzaken geen systeembericht naar de melder.
  • Externe beheerberichten kunnen een systeembericht naar de melder veroorzaken.
  • Discussieberichten worden niet opgeslagen in Communication.PrivateMessages.
  • Een ticket kan via systeemberichten vindbaar worden gemaakt, maar het ticket blijft bronhoudend in Support.
  • Rich text of opgemaakte tekst moet server-side gesanitized worden voordat opslag of rendering plaatsvindt.

14.11 Sluiten, oplossen en accepteren

Een formele sluiting wordt vastgelegd in TicketClosures. Sluiten is geen vrije statusmutatie zonder sluitrecord.

Minimale technische verwerking bij sluiten door beheer:

  1. Controleer beheercontext en behandelbaarheid.
  2. Controleer dat een vereiste actieve assignment aanwezig is wanneer de flow dat vereist.
  3. Valideer oplossingstekst/sluittoelichting en resolutietype.
  4. Maak een TicketClosure aan.
  5. Zet de ticketstatus naar Closed.
  6. Bepaal en bewaar ReopenDeadlineUtc waar van toepassing.
  7. Schrijf TicketHistory.
  8. Vraag Communication om de melder te informeren wanneer dit functioneel nodig is.

Acceptatie door de gebruiker maakt geen tweede closure-record aan. De actuele closure wordt gemarkeerd met acceptatiegegevens, bijvoorbeeld AcceptedByUserId en AcceptedAtUtc, aangevuld met history en eventueel een extern discussie-item.

Als acceptatie technisch faalt, mag het ticket niet gedeeltelijk als geaccepteerd zichtbaar worden. De mutatie op de actuele closure en de bijbehorende historyregel zijn één kritieke supportmutatie.

14.12 Heropenen

14.12.1 Heropenen door gebruiker

Heropenen door de gebruiker is alleen toegestaan wanneer de actuele sluiting nog heropenbaar is en nog niet door de gebruiker is geaccepteerd.

Technische verwerking:

  1. Controleer ticket ownership.
  2. Controleer actuele closure en heropentermijn.
  3. Valideer verplichte toelichting.
  4. Maak TicketReopenRequest met RequestSource = User.
  5. Maak extern zichtbaar discussie-item of afleidbaar tijdlijnitem.
  6. Schrijf TicketHistory.
  7. Maak het ticket opnieuw behandelbaar volgens domeinregels.

Actieve beheerderkoppelingen worden bij gebruikersheropenen niet automatisch ontkoppeld, tenzij het domeinbeleid dit via een expliciet besluit wijzigt.

14.12.2 Heropenen door beheerder

Handmatig heropenen door beheerder is niet afhankelijk van de gebruikersgerichte heropentermijn. De beheerder moet een reden opgeven.

Technische verwerking:

  1. Controleer beheercontext.
  2. Valideer verplichte reden.
  3. Maak TicketReopenRequest met RequestSource = Admin.
  4. Ontkoppel of reset actieve assignments volgens domeinregel.
  5. Zet ticketstatus terug naar New.
  6. Schrijf minimaal twee historyregels: heropenen en assignment-reset.
  7. Laat eerdere closures historisch intact.

De reden hoeft niet als fysiek los discussiebericht te worden opgeslagen, maar mag in beheerweergave als tijdlijninformatie worden getoond op basis van reopen/historydata.

14.13 Doorzetten naar docent

Doorzetten naar docent is een samengestelde supportworkflow. Support blijft eigenaar van de workflow, maar gebruikt andere modules via publieke contracts.

Betrokken modules:

ModuleRol in de workflow
SupportSluit ticket, registreert doorzetting, schrijft history en bewaakt workflow.
RelationshipsLevert geldige docentcontext rond de melder/leerling.
AuthorizationControleert beheercontext en toegestane actie.
CommunicationMaakt systeembericht aan melder en privébericht namens melder aan docent.
IdentityLevert soft-link gebruikersidentifiers en naamweergave/snapshots waar toegestaan.

Minimale kritieke stappen:

  1. Controleer dat het ticket behandelbaar is.
  2. Controleer dat beheerder bevoegd is.
  3. Controleer dat de gekozen docent binnen de toegestane relatie-/contextset valt.
  4. Leg TicketForwardedToTeacher vast.
  5. Sluit het ticket formeel met resolutietype Module configuratie.
  6. Schrijf TicketHistory.
  7. Maak de functioneel vereiste communicatie aan.

Omdat de systeemberichten/privéberichten in deze workflow de uitleg en opvolging richting melder/docent dragen, kan communicatie hier kritiek zijn. Dit moet in de concrete workflowimplementatie expliciet worden vastgelegd.

Voorbeeld:

Support.ForwardToTeacher
-> Relationships controleert geldige docentcontext
-> Support schrijft forward-record + closure + history
-> Communication maakt systeembericht aan melder
-> Communication maakt privébericht namens melder aan docent

Wanneer één kritieke stap faalt, mag de workflow niet eindigen in een half-doorgezette toestand. De V1.0-boundary voor doorzetten naar docent is: supportbrondata en functioneel vereiste communicatie horen bij dezelfde kritieke workflow; afgeleide teller- en readmodelupdates blijven retrybaar. Afhankelijk van de concrete technische fout wordt de volledige mutatie teruggedraaid of komt de workflow in een beheerbare failed-status terecht zonder dat zij gebruikersgericht als succesvol wordt getoond.

14.14 Transaction boundaries en foutafhandeling

Supportflows bepalen per workflow welke mutaties kritiek zijn. Een mutatie is kritiek wanneer de gebruikersactie zonder die mutatie leidt tot een ongeldige, onbereikbare, misleidende of niet-herstelbare toestand.

WorkflowKritieke mutatiesMogelijk retrybaar
Nieuwe melding aanmakenTicket, snapshot, historyBevestigingsbericht indien Mijn meldingen als primaire toegang bestaat.
Extra informatie vragenExtern bericht, status WaitingForUser, eventueel systeemcommunicatieBadge/readmodel update.
Oplossen/sluitenClosure, status, historyNiet-kritieke badge/readmodel refresh.
Gebruiker accepteert oplossingClosure-acceptatie, historyEventuele bevestigingsnotificatie.
Doorzetten naar docentForward-record, closure, history, functioneel vereiste communicatieAfgeleide tellerupdates.
Verlopen heropentermijnGeen nieuwe closure; eventueel compacte historyGeen gebruikerscommunicatie.

Als een workflow meerdere modules raakt, blijft de modulegrens intact via publieke contracts. De workflow kan wel een gedeelde transactie of expliciete compensatie-/failed-status gebruiken wanneer atomische consistentie functioneel noodzakelijk is.

Foutafhandeling:

  • Gebruikers krijgen geen technische foutdetails.
  • Technische fouten worden gelogd met correlation-id.
  • Een mislukte kritieke workflow wordt niet als succesvol bevestigd.
  • Retrybare stappen zijn idempotent, begrensd en beheerbaar.
  • Failed workflowstappen moeten herleidbaar zijn via logging/jobstatus/history waar passend.

14.15 Scheduling en periodieke verwerking

Support gebruikt OefenHub.Scheduling voor periodieke of uitgestelde verwerking. Scheduling is eigenaar van de technische joblifecycle, niet van de supportbusinessregels.

JobEigenaar domeinlogicaTechnische lifecycle
Verlopen heropentermijnen verwerkenSupportScheduling / TickerQ
Retrybare supportcommunicatie verwerkenSupport + CommunicationScheduling / TickerQ
Technische cleanup van tijdelijke ticketartefactenAfhankelijk van eigenaarScheduling / TickerQ

14.15.1 Verlopen heropentermijnen

Het verlopen van een heropentermijn maakt geen extra TicketClosure aan. De bestaande actuele closure blijft de formele bron.

Technische selectiecriteria worden idempotent uitgevoerd:

Ticket.Status = Closed
Actuele TicketClosure.ReopenDeadlineUtc <= nowUtc
TicketClosure.AcceptedAtUtc = null
Geen latere heropening actief
Nog niet eerder als verlopen verwerkt, indien een historymarker wordt gebruikt

De verwerking:

  1. Selecteert kandidaten in batches.
  2. Controleert per ticket opnieuw de actuele sluitcontext.
  3. Schrijft waar nodig een compacte niet-duplicerende historyregel.
  4. Verstuurt geen extra systeembericht alleen vanwege het verlopen van de termijn.
  5. Blokkeert niet de hele batch wanneer één ticket faalt.
  6. Logt fouten met job-id en correlation-id.

14.16 Logging, correlation en domeinhistorie

Support gebruikt drie soorten herleidbaarheid:

TypeDoelVoorbeelden
DomeinhistorieFunctionele reconstructie binnen het ticketdomeinTicketHistory, TicketClosures, TicketReopenRequests.
Technische loggingAnalyse van fouten, performance en workflowuitvoeringStructured logs met correlation-id.
JobloggingHerleidbaarheid van periodieke en retrybare verwerkingTickerQ job-id, attemptnummer, jobtype.

Iedere relevante supportmutatie moet minimaal herleidbaar zijn met:

CorrelationId
TicketId
ActorUserId indien beschikbaar
ActorRoleContext indien beschikbaar
ActionType
UtcTimestamp
WorkflowName indien van toepassing
JobId indien via Scheduling uitgevoerd

Technische logs mogen geen wachtwoorden, tokens, credentials, volledige payloads met gevoelige inhoud of onnodige persoonsgegevens bevatten.

TicketHistory is domeininformatie en blijft binnen support. Er komt geen overkoepelende auditmodule.

14.17 Security en autorisatie

Support is gevoelig omdat tickets gebruikersinformatie, technische snapshots en mogelijk foutomschrijvingen bevatten. Daarom gelden strikte autorisatie- en privacyregels.

ActieAutorisatiegrens
Eigen melding aanmakenGeldige ingelogde gebruiker met normale applicatietoegang.
Eigen meldingen bekijkenAlleen tickets van huidige gebruiker.
Ticketdetail gebruikerTicket moet van huidige gebruiker zijn.
Gebruikersreactie plaatsenTicket moet van huidige gebruiker zijn en niet functioneel gesloten zijn.
Oplossing accepterenTicket moet van huidige gebruiker zijn en actuele closure moet accepteerbaar zijn.
Gebruiker heropentTicket moet van huidige gebruiker zijn en binnen heropentermijn vallen.
BeheerderoverzichtActieve beheercontext.
Intern bericht plaatsenActieve beheercontext.
Extern beheerbericht plaatsenActieve beheercontext en geldige ticketstatus.
Doorzetten naar docentActieve beheercontext plus geldige docentcontext.

Routeparameters, zichtbare knoppen, browserstate en oude selectiecontexten zijn nooit autoriserend.

Technische snapshots zijn alleen zichtbaar voor beheerders. Reguliere gebruikers krijgen geen user-agent, IP-adres, technische payload of interne beheerinformatie te zien.

14.18 Privacy, retentie en anonimisering

Supportdata kan persoonsgegevens bevatten. Daarom moet het domein voorbereid zijn op accountanonimisering en retentiebeleid.

Regels:

  • Tickets blijven voor beheeranalyse en auditdoeleinden raadpleegbaar wanneer dat functioneel nodig is.
  • Actuele persoonsgegevens van geanonimiseerde accounts worden niet zichtbaar gehouden.
  • Actorverwijzingen naar gebruikers zijn soft links en kunnen na anonimisering niet meer naar actuele persoonsgegevens leiden.
  • Snapshotvelden met persoonsgegevens moeten volgens het centrale anonimiseringsbeleid worden overschreven of vervangen door vaste geanonimiseerde waarden.
  • Technische snapshots blijven momentopnamen, maar mogen na anonimisering geen actuele persoonsgegevens tonen wanneer dat in strijd is met privacyregels.
  • Interne beheerdiscussie en history blijven alleen toegankelijk voor bevoegde beheerders.

Retentie voor supportrecords wordt niet in dit hoofdstuk definitief vastgesteld wanneer deze functioneel of juridisch nog openstaat. Wel moet de technische implementatie retentie en anonimisering mogelijk maken zonder hard deletes die reconstructie onmogelijk maken.

14.19 Technische snapshots

Bij het aanmaken van een melding kan Support een technische snapshot opslaan. Deze snapshot helpt beheerders bij analyse, maar is geen live afleiding.

Voorbeelden van snapshotinformatie:

PageUrl
UserAgent
IpAddress indien toegestaan
RoleContextSnapshot
Browser/Platform indicatie
CreatedAtUtc

Snapshotregels:

  • Snapshot wordt vastgelegd op het moment van melden.
  • Snapshot wordt niet achteraf live herberekend.
  • Snapshot is alleen zichtbaar in beheercontext.
  • Snapshot mag geen tokens, cookies, wachtwoorden of volledige requestpayloads bevatten.
  • Snapshotvelden met persoonsgegevens vallen onder anonimisering en privacybeleid.

14.20 Publieke supportcontracts

Publieke contracts vormen de enige module-ingang voor andere projecten.

Voorbeelden:

public interface ITicketCommandService
{
Task<CreateTicketResult> CreateTicketAsync(CreateTicketCommand command, CancellationToken cancellationToken);
Task<AddTicketDiscussionMessageResult> AddDiscussionMessageAsync(AddTicketDiscussionMessageCommand command, CancellationToken cancellationToken);
Task<CloseTicketResult> CloseTicketAsync(CloseTicketCommand command, CancellationToken cancellationToken);
Task<ForwardTicketToTeacherResult> ForwardToTeacherAsync(ForwardTicketToTeacherCommand command, CancellationToken cancellationToken);
}
public interface ITicketQueryService
{
Task<UserTicketOverviewReadModel> GetUserOverviewAsync(UserTicketOverviewQuery query, CancellationToken cancellationToken);
Task<UserTicketDetailReadModel> GetUserDetailAsync(UserTicketDetailQuery query, CancellationToken cancellationToken);
Task<AdminTicketOverviewReadModel> GetAdminOverviewAsync(AdminTicketOverviewQuery query, CancellationToken cancellationToken);
Task<AdminTicketDetailReadModel> GetAdminDetailAsync(AdminTicketDetailQuery query, CancellationToken cancellationToken);
}
public interface ITicketMaintenanceService
{
Task<ProcessExpiredReopenDeadlinesResult> ProcessExpiredReopenDeadlinesAsync(ProcessExpiredReopenDeadlinesCommand command, CancellationToken cancellationToken);
}

ITicketMaintenanceService wordt gebruikt door OefenHub.Scheduling. De concrete implementatie blijft in OefenHub.Support.

14.21 Voorbeeld: extra informatie vragen

1. Beheerder opent ticketdetail.
2. Web roept ITicketCommandService.RequestAdditionalInformationAsync aan.
3. Support controleert beheercontext en ticketstatus.
4. Support schrijft extern discussiebericht.
5. Support zet status naar WaitingForUser.
6. Support schrijft TicketHistory.
7. Support vraagt Communication om een SystemMessage te maken.
8. Web toont succes wanneer kritieke stappen geslaagd zijn.

Transactioneel aandachtspunt: wanneer het systeembericht de primaire ingang is waarmee de gebruiker terug naar het ticket wordt geleid, kan dit onderdeel zijn van de kritieke workflow. Wanneer de gebruiker het ticket ook via Mijn meldingen > Wacht op mij kan vinden, kan communicatie retrybaar zijn. Deze keuze moet in de concrete workflow worden vastgelegd.

14.22 Voorbeeld: verlopen heropentermijn

1. TickerQ triggert support maintenance job.
2. Scheduling maakt jobcontext met JobId en CorrelationId.
3. Scheduling roept ITicketMaintenanceService.ProcessExpiredReopenDeadlinesAsync aan.
4. Support selecteert gesloten tickets met verlopen actuele heropentermijn.
5. Support controleert per ticket of geen latere heropening of acceptatie bestaat.
6. Support schrijft eventueel een compacte niet-duplicerende historyregel.
7. Support retourneert aantallen verwerkt/overgeslagen/gefaald.
8. Scheduling registreert jobresultaat en eventuele retrystatus.

Deze verwerking maakt geen extra closure-record en stuurt geen nieuw systeembericht alleen vanwege het verlopen van de termijn.

14.23 Performance en concurrency

Supportoverzichten kunnen groeien. Daarom gelden technische randvoorwaarden:

  • Beheeroverzichten gebruiken server-side paginering.
  • Zoekfilters worden server-side toegepast.
  • Detailreadmodels laden alleen de benodigde discussie-, closure- en historyinformatie.
  • Lange discussies worden gepagineerd of beperkt geladen wanneer nodig.
  • Mutaties op ticketstatus en actuele closure gebruiken concurrencycontrole.
  • Heropenen, sluiten en accepteren moeten idempotentie- of concurrencyregels hebben om dubbele acties te voorkomen.
  • Periodieke verwerking werkt batchgewijs en mag bij één fout niet de volledige batch blokkeren.

14.24 Teststrategie

Support krijgt eigen testdekking in tests/OefenHub.Support.Tests.

TesttypeDekking
Unit testsStatusafleiding, validatie, heropenregels, sluitregels, readmodelmapping.
Module integration testsSupportDbContext, migrations, constraints, seeddata en supportservices.
Contract testsPublieke supportcontracts en verwachte resultaten/foutcodes.
WorkflowtestsDoorzetten naar docent, extra informatie vragen, oplossen/sluiten, heropenen.
Cross-module integratietestsSupport + Communication, Support + Relationships, Support + Scheduling.
Authorization testsGebruiker ziet alleen eigen tickets; beheerder ziet beheeroverzicht.
Scheduling testsVerlopen heropentermijn, idempotentie, retry/failure-status.
PrivacytestsGeen interne discussie of technische snapshot zichtbaar voor reguliere gebruiker.
Architecture testsGeen directe toegang tot supportentities buiten module; alleen contracts publiek.

Kritieke workflows krijgen tests waarin een tussenstap faalt, zodat gecontroleerd wordt dat geen halve functionele toestand als succesvol wordt teruggegeven.

14.25 Implementatiechecklist

Bij implementatie van het supportdomein moet minimaal gecontroleerd worden:

  • OefenHub.Support heeft precies één SupportDbContext.
  • Alle supporttabellen staan in schema support.
  • Cross-module userverwijzingen zijn soft links met snapshots waar nodig.
  • Entities, DbContext en implementatieclasses zijn internal waar mogelijk.
  • Alleen Contracts bevat publieke module-ingangen.
  • Web gebruikt geen supportentities of support-DbContext.
  • Gebruikersqueries filteren altijd op huidige gebruiker.
  • Beheerqueries vereisen actieve beheercontext.
  • Opgelost wordt afgeleid en niet als backendstatus opgeslagen.
  • Sluiten gebruikt TicketClosures.
  • Gebruikersacceptatie maakt geen tweede closure-record.
  • Heropenen gebruikt TicketReopenRequests.
  • Doorzetten naar docent gebruikt publieke contracts van Relationships en Communication.
  • Verlopen heropentermijnen worden via Scheduling/TickerQ verwerkt.
  • Retrybare acties zijn begrensd, idempotent en beheerbaar.
  • Correlation-id wordt doorgegeven aan logs, jobs en cross-module calls.
  • Technische snapshots bevatten geen secrets, tokens of credentials.
  • Interne discussie is nooit zichtbaar voor reguliere gebruiker.
  • Testdekking bevat statusafleiding, autorisatie, workflows, scheduling en privacygrenzen.

14.26 Implementatieverificaties

PuntTe controleren in implementatiefase
Ticketstatus- en resolutietype-seeddataWelke waarden initieel via migration/seed worden geplaatst en hoe beheerwijzigingen worden voorkomen.
Transaction boundary doorzetten naar docentControleer dat de implementatie de vastgelegde V1.0-boundary volgt: kritieke supportmutatie plus functioneel vereiste communicatie, met rollback of beheerbare failed-status bij falen.
SysteemberichtkritikaliteitControleer per supportflow of communicatie werkelijk functioneel vereist is of als retrybare vervolgactie mag worden behandeld.
TicketzoekindexBepalen of gewone database-indexen voldoende zijn of een aparte zoekstrategie nodig is.
Lange discussiesBepalen vanaf welk volume discussiepagina’s gepagineerd of lazy geladen worden.
TickerQ jobconfiguratieRetrybeleid, interval en schema scheduling technisch verifiëren.
Technische snapshotveldenDefinitieve veldset en anonimisering per veld vastleggen in database-informatie/privacyhoofdstuk.
SecurityloggingControleer dat access-denied pogingen conform de loggingbaseline technisch worden gelogd en alleen domeinhistorie krijgen wanneer dat functioneel bronhoudend is.
Doorzetten namens gebruikerExacte contractvorm tussen Support en Communication vastleggen.
Failed workflow beheerControleer dat mislukte cross-module supportflows met correlation-id, status en veilige foutcontext analyseerbaar en herstelbaar zijn.