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.Schedulingde 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.
| Onderdeel | Keuze |
|---|---|
| Project | OefenHub.Support |
| DbContext | SupportDbContext |
| Schema | support |
| Eigenaar van brondata | Supportmodule |
| Cross-module toegang | Alleen via publieke contracts |
| Directe toegang tot supporttabellen vanuit andere modules | Niet toegestaan |
| Audit/history | Domeinspecifiek 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:
| Datagroep | Technische betekenis |
|---|---|
| Tickets | Hoofdrecord van een melding/ticket. |
| Ticketstatussen en resolutietypen | Waardelijsten voor processtatus en afsluitstatus. |
| TicketAssignments | Actieve of historische beheerderkoppelingen aan een ticket. |
| TicketDiscussionMessages | Interne en externe discussieberichten binnen een ticket. |
| TicketClosures | Formele sluitingen, oplossingen, heropentermijnen en acceptatie door gebruiker. |
| TicketReopenRequests | Heropenverzoeken door gebruiker of beheerder. |
| TicketForwardedToTeacher | Registratie van doorzetten naar docent. |
| TicketHistory | Compacte domeinhistorie/audit voor ticketacties. |
| TicketTechnicalSnapshots | Technische 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
| Module | Relatie met Support |
|---|---|
OefenHub.Web | Toont gebruikers- en beheerschermen, roept publieke supportcontracts aan en voert geen directe data-access uit. |
OefenHub.Identity | Levert interne accountcontext en soft-link identifiers; Support beheert geen credentials. |
OefenHub.Authorization | Levert rolcontext, objecttoegang en policychecks voor gebruiker en beheerder. |
OefenHub.Communication | Maakt systeemberichten en privéberichten aan op verzoek van Support. |
OefenHub.Relationships | Wordt gebruikt bij doorzetten naar docent om geldige docentcontext te bepalen. |
OefenHub.Catalog | Kan context leveren wanneer een melding betrekking heeft op niveau, categorie, oefening of moduleconfiguratie. |
OefenHub.Practice | Kan context leveren wanneer een melding betrekking heeft op een oefenrun of resultaat. |
OefenHub.Scheduling | Beheert technische lifecycle van jobs rond verlopen heropentermijnen en retrybare ticketverwerking. |
OefenHub.Admin | Kan beheercontext of instellingen leveren, maar wordt geen eigenaar van ticketbrondata. |
OefenHub.Infrastructure | Levert 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.
| Concept | Technische betekenis |
|---|---|
| Processtatus | Backendstatus van het ticket, zoals New, InProgress, WaitingForUser of Closed. |
| Afsluitstatus | Inhoudelijke uitkomst van een sluiting, zoals technisch opgelost, workaround of moduleconfiguratie. |
Gebruikersstatus Opgelost | Afgeleid uit een actuele sluitregistratie met nog geldige heropentermijn. |
Gebruikersstatus Gesloten | Afgeleid 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.
| Actie | Technische verwerking |
|---|---|
| Nieuwe melding maken | Ticket aanmaken, technische snapshot vastleggen, historyregel schrijven en eventueel systeembericht aan gebruiker maken. |
| Beheerder koppelen | Assignment aanmaken of activeren, status naar behandelbaar brengen en historyregel schrijven. |
| Extern bericht plaatsen | Extern discussiebericht opslaan, status eventueel aanpassen en systeemcommunicatie aan gebruiker triggeren. |
| Intern bericht plaatsen | Intern discussiebericht opslaan zonder communicatie naar gebruiker. |
| Wachten op gebruiker | Status naar WaitingForUser, extern bericht verplicht, actie-indicator afleidbaar maken. |
| Gebruiker reageert | Extern discussiebericht opslaan en ticket opnieuw behandelbaar maken volgens actieve assignmentcontext. |
| Oplossen/sluiten door beheer | Closure opslaan, status sluiten, heropentermijn bepalen, historyregel schrijven en gebruiker informeren. |
| Sluiten door gebruiker | Closure met resolutietype ClosedByUser, externe reden vastleggen en historyregel schrijven. |
| Heropenen door gebruiker | Reopen request, extern discussie-item, historyregel en status opnieuw behandelbaar maken. |
| Heropenen door beheer | Reopen request, actieve assignments resetten, status terug naar New en historyregels schrijven. |
| Doorzetten naar docent | Samengestelde supportworkflow met closure, forward-record, communicatie en history. |
| Verlopen heropentermijn | Periodieke 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:
- Lees de actuele gebruikers- en rolcontext server-side.
- Valideer categorie, onderwerp en beschrijving.
- Verzamel technische context, zoals pagina, user agent en rolmomentopname.
- Maak het ticketrecord aan.
- Sla de technische snapshot op.
- Schrijf een compacte
TicketHistory-regel. - Maak, wanneer functioneel vereist, een systeembericht via
Communication. - 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.
| Onderdeel | Technische invulling |
|---|---|
| Mijn meldingen | Query op eigen tickets via ITicketQueryService. |
| Open | Afgeleid readmodel over tickets die nog niet functioneel gesloten zijn en niet op gebruiker wachten. |
| Wacht op mij | Afgeleid uit tickets van gebruiker met WaitingForUser. |
| Gesloten | Afgeleid uit gesloten/opgeloste tickets. |
| Actie-indicatie | Afgeleid via ITicketActionIndicatorReader, niet als losse teller opgeslagen. |
| Detailpagina | Query valideert ticket ownership opnieuw. |
| Discussietab | Toont alleen externe discussieberichten. |
| Oplossingstab | Toont 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.
| Onderdeel | Technische invulling |
|---|---|
| Beheeroverzicht | Query over alle tickets binnen beheercontext. |
| Filters | Server-side toegepast op status, assignment, categorie en zoekterm. |
| Zoeken | Queryservice zoekt op meldingsnummer, onderwerp, categorie en relevante discussie-inhoud. |
| Detailweergave | Beheerreadmodel met interne en externe informatie. |
| Assignment | Mutatie via commandservice, niet rechtstreeks via UI. |
| Interne discussie | Alleen zichtbaar voor beheerdercontext. |
| Externe discussie | Zichtbaar voor melder en beheerders, triggert eventueel systeemcommunicatie. |
| Geavanceerde metadata | Alleen beheerreadmodel, niet zichtbaar voor melder. |
| Geschiedenis | Compacte 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.
| Zichtbaarheid | Doel | Gebruikerszichtbaar |
|---|---|---|
| Intern | Beheeranalyse en interne afstemming | Nee |
| Extern | Communicatie met melder | Ja |
| Systeemgegenereerd | Korte markering binnen ticketcontext | Afhankelijk 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:
- Controleer beheercontext en behandelbaarheid.
- Controleer dat een vereiste actieve assignment aanwezig is wanneer de flow dat vereist.
- Valideer oplossingstekst/sluittoelichting en resolutietype.
- Maak een
TicketClosureaan. - Zet de ticketstatus naar
Closed. - Bepaal en bewaar
ReopenDeadlineUtcwaar van toepassing. - Schrijf
TicketHistory. - Vraag
Communicationom 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:
- Controleer ticket ownership.
- Controleer actuele closure en heropentermijn.
- Valideer verplichte toelichting.
- Maak
TicketReopenRequestmetRequestSource = User. - Maak extern zichtbaar discussie-item of afleidbaar tijdlijnitem.
- Schrijf
TicketHistory. - 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:
- Controleer beheercontext.
- Valideer verplichte reden.
- Maak
TicketReopenRequestmetRequestSource = Admin. - Ontkoppel of reset actieve assignments volgens domeinregel.
- Zet ticketstatus terug naar
New. - Schrijf minimaal twee historyregels: heropenen en assignment-reset.
- 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:
| Module | Rol in de workflow |
|---|---|
Support | Sluit ticket, registreert doorzetting, schrijft history en bewaakt workflow. |
Relationships | Levert geldige docentcontext rond de melder/leerling. |
Authorization | Controleert beheercontext en toegestane actie. |
Communication | Maakt systeembericht aan melder en privébericht namens melder aan docent. |
Identity | Levert soft-link gebruikersidentifiers en naamweergave/snapshots waar toegestaan. |
Minimale kritieke stappen:
- Controleer dat het ticket behandelbaar is.
- Controleer dat beheerder bevoegd is.
- Controleer dat de gekozen docent binnen de toegestane relatie-/contextset valt.
- Leg
TicketForwardedToTeachervast. - Sluit het ticket formeel met resolutietype
Module configuratie. - Schrijf
TicketHistory. - 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.
| Workflow | Kritieke mutaties | Mogelijk retrybaar |
|---|---|---|
| Nieuwe melding aanmaken | Ticket, snapshot, history | Bevestigingsbericht indien Mijn meldingen als primaire toegang bestaat. |
| Extra informatie vragen | Extern bericht, status WaitingForUser, eventueel systeemcommunicatie | Badge/readmodel update. |
| Oplossen/sluiten | Closure, status, history | Niet-kritieke badge/readmodel refresh. |
| Gebruiker accepteert oplossing | Closure-acceptatie, history | Eventuele bevestigingsnotificatie. |
| Doorzetten naar docent | Forward-record, closure, history, functioneel vereiste communicatie | Afgeleide tellerupdates. |
| Verlopen heropentermijn | Geen nieuwe closure; eventueel compacte history | Geen 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.
| Job | Eigenaar domeinlogica | Technische lifecycle |
|---|---|---|
| Verlopen heropentermijnen verwerken | Support | Scheduling / TickerQ |
| Retrybare supportcommunicatie verwerken | Support + Communication | Scheduling / TickerQ |
| Technische cleanup van tijdelijke ticketartefacten | Afhankelijk van eigenaar | Scheduling / 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:
- Selecteert kandidaten in batches.
- Controleert per ticket opnieuw de actuele sluitcontext.
- Schrijft waar nodig een compacte niet-duplicerende historyregel.
- Verstuurt geen extra systeembericht alleen vanwege het verlopen van de termijn.
- Blokkeert niet de hele batch wanneer één ticket faalt.
- Logt fouten met job-id en correlation-id.
14.16 Logging, correlation en domeinhistorie
Support gebruikt drie soorten herleidbaarheid:
| Type | Doel | Voorbeelden |
|---|---|---|
| Domeinhistorie | Functionele reconstructie binnen het ticketdomein | TicketHistory, TicketClosures, TicketReopenRequests. |
| Technische logging | Analyse van fouten, performance en workflowuitvoering | Structured logs met correlation-id. |
| Joblogging | Herleidbaarheid van periodieke en retrybare verwerking | TickerQ 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.
| Actie | Autorisatiegrens |
|---|---|
| Eigen melding aanmaken | Geldige ingelogde gebruiker met normale applicatietoegang. |
| Eigen meldingen bekijken | Alleen tickets van huidige gebruiker. |
| Ticketdetail gebruiker | Ticket moet van huidige gebruiker zijn. |
| Gebruikersreactie plaatsen | Ticket moet van huidige gebruiker zijn en niet functioneel gesloten zijn. |
| Oplossing accepteren | Ticket moet van huidige gebruiker zijn en actuele closure moet accepteerbaar zijn. |
| Gebruiker heropent | Ticket moet van huidige gebruiker zijn en binnen heropentermijn vallen. |
| Beheerderoverzicht | Actieve beheercontext. |
| Intern bericht plaatsen | Actieve beheercontext. |
| Extern beheerbericht plaatsen | Actieve beheercontext en geldige ticketstatus. |
| Doorzetten naar docent | Actieve 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.
| Testtype | Dekking |
|---|---|
| Unit tests | Statusafleiding, validatie, heropenregels, sluitregels, readmodelmapping. |
| Module integration tests | SupportDbContext, migrations, constraints, seeddata en supportservices. |
| Contract tests | Publieke supportcontracts en verwachte resultaten/foutcodes. |
| Workflowtests | Doorzetten naar docent, extra informatie vragen, oplossen/sluiten, heropenen. |
| Cross-module integratietests | Support + Communication, Support + Relationships, Support + Scheduling. |
| Authorization tests | Gebruiker ziet alleen eigen tickets; beheerder ziet beheeroverzicht. |
| Scheduling tests | Verlopen heropentermijn, idempotentie, retry/failure-status. |
| Privacytests | Geen interne discussie of technische snapshot zichtbaar voor reguliere gebruiker. |
| Architecture tests | Geen 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.Supportheeft precies éénSupportDbContext.- Alle supporttabellen staan in schema
support. - Cross-module userverwijzingen zijn soft links met snapshots waar nodig.
- Entities, DbContext en implementatieclasses zijn
internalwaar mogelijk. - Alleen
Contractsbevat publieke module-ingangen. - Web gebruikt geen supportentities of support-DbContext.
- Gebruikersqueries filteren altijd op huidige gebruiker.
- Beheerqueries vereisen actieve beheercontext.
Opgelostwordt 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
| Punt | Te controleren in implementatiefase |
|---|---|
| Ticketstatus- en resolutietype-seeddata | Welke waarden initieel via migration/seed worden geplaatst en hoe beheerwijzigingen worden voorkomen. |
| Transaction boundary doorzetten naar docent | Controleer dat de implementatie de vastgelegde V1.0-boundary volgt: kritieke supportmutatie plus functioneel vereiste communicatie, met rollback of beheerbare failed-status bij falen. |
| Systeemberichtkritikaliteit | Controleer per supportflow of communicatie werkelijk functioneel vereist is of als retrybare vervolgactie mag worden behandeld. |
| Ticketzoekindex | Bepalen of gewone database-indexen voldoende zijn of een aparte zoekstrategie nodig is. |
| Lange discussies | Bepalen vanaf welk volume discussiepagina’s gepagineerd of lazy geladen worden. |
| TickerQ jobconfiguratie | Retrybeleid, interval en schema scheduling technisch verifiëren. |
| Technische snapshotvelden | Definitieve veldset en anonimisering per veld vastleggen in database-informatie/privacyhoofdstuk. |
| Securitylogging | Controleer dat access-denied pogingen conform de loggingbaseline technisch worden gelogd en alleen domeinhistorie krijgen wanneer dat functioneel bronhoudend is. |
| Doorzetten namens gebruiker | Exacte contractvorm tussen Support en Communication vastleggen. |
| Failed workflow beheer | Controleer dat mislukte cross-module supportflows met correlation-id, status en veilige foutcontext analyseerbaar en herstelbaar zijn. |