7.3 KiB
06 - Tickety
Co ticket je a co neni
Ticket je prichozi pozadavek odkudkoliv. Prisla zprava na WhatsApp, prisel e-mail, nekdo zavolal na hlasovou linku, odeslal formular. Z toho vznikne ticket, ktery ma sveho cloveka a dohledatelny prubeh.
Ticket neni bug ani wish. Vyvojarska agenda je jina vec s jinym zivotnim cyklem a v prototypu zatim neexistuje. Michat je do jedne evidence by znamenalo, ze ani jedna nefunguje poradne.
Tri veci, na kterych to stoji
Kanaly ustuji do ticketu. WhatsApp, e-mail, hlasova linka a formular jsou plnohodnotne spoustece. Automatizace zacne prichozi zpravou a skonci zalozenym ticketem.
Ticket ma sveho cloveka. assignee neni volny text, ale odkaz na konkretniho
resitele. Da se rict "hod to na Karla Vomacku" a Karel to ma mezi svymi tickety.
Nad tym je prehled pres cely tym, kde je videt, kdo co u sebe ma.
Kazdy ticket je dohledatelny. Nese si strom zaznamu o tom, co se s nim delo a co ktera sluzba vratila. Kdyz neco nesedi, neni potreba hadat.
Datovy model
src/data/ticketStore.ts
interface Ticket {
id: string;
subject: string;
channel: 'whatsapp' | 'email' | 'voice' | 'form' | 'portal';
customer: TicketCustomer;
status: 'new' | 'open' | 'waiting' | 'resolved';
priority: 'low' | 'normal' | 'high' | 'critical';
assignee: { id: string; name: string } | null; // null = ceka ve fronte
automationId: string | null; // null = zalozeno rucne
createdAt: string;
updatedAt: string;
}
interface TicketCustomer {
id: string | null; // null = firmu se nepodarilo dohledat v CRM
company: string;
contact: string;
reply: string; // adresa nebo cislo, kam se odpovida
}
customer.id je zamerne nullable. Prave na nej se pta podminka "mame zakaznika?"
ve strome automatizace. Bez toho by nesla postavit vetev "firmu neznam, zaloz
obchodni pripad a nekomu to dej".
Uvnitr ulozista se drzi jen assigneeId, jmeno se dopocitava pri cteni. Kdyz
resitel ze seznamu zmizi, ticket nespadne - jen se zaloguje a tvari se jako
neprirazeny.
Resitele
src/data/people.ts
Resitel je oddeleny od uzivatele. Uzivatel je ten, kdo se prihlasi do portalu, resitel je ten, na koho jde ticket. Casto je to tyz clovek, ale ne vzdy - technik muze mit tickety a do portalu se nikdy neprihlasit.
Spojka mezi obojim je e-mail. Podle ni funguje filtr "moje tickety"
(?assignee=me). Kdyz prihlaseny ucet zadnemu resiteli neodpovida, filtr se
v portalu nabidne jako nedostupny misto toho, aby vracel prazdno bez vysvetleni.
Kazdy resitel ma capacity, tedy pocet nevyrizenych ticketu, ktery je pro nej
jeste zdrava zatez. Neni to limit, nic se podle nej neodmita - jen se v prehledu
oznaci, kdo je nad ni.
Prehled nad firmou
GET /api/dashboard/tickets/workload vraci pres cely tym: kolik ma kdo
nevyrizenych, kolik celkem, kolik kritickych, jak stary je jeho nejstarsi ticket
a jestli je nad kapacitu. K tomu pocet ticketu ve fronte bez resitele.
V portalu je to postranni panel na strance Tickety. Radek je zaroven filtr seznamu - kliknutim na cloveka se seznam zuzi na jeho tickety. Bez toho by to byl jen obrazek.
Sirka pruhu se pocita proti nejvytizenejsimu clenovi tymu, ne proti kapacite. Jde o porovnani lidi mezi sebou.
Log ticketu
Log je strom, ne seznam. Vetev podminky visi na zaznamu te podminky, takze je videt i to, ktera cast behu se vubec nespustila.
interface TicketTraceEntry {
id: string;
parentId: string | null; // null = hlavni sekvence
kind: 'trigger' | 'action' | 'condition' | 'note';
connectorId: string | null; // ktera sluzba to byla
operationId: string | null;
label: string;
status: 'ok' | 'error' | 'skipped' | 'info';
response: string | null; // co sluzba vratila
durationMs: number | null;
at: string;
}
response je duvod, proc log existuje. V portalu se zobrazuje rovnou, ne po
rozkliknuti - kvuli nemu se do logu chodi.
Zapisuje se zanorene (TraceInput s children) a uklada zplostele s parentId.
Klient si strom zase poskladá. Zaznam s neznamym rodicem se nezahodi, prida se
do korene a zaloguje - ztratit radek logu je horsi nez ho ukazat spatne zanoreny.
Komentare jsou taky zaznamy logu (kind: 'note'). Diky tomu je vsechno na jedne
casove ose a nemusi se nikde skladat dohromady dva ruzne seznamy.
Konektor Tickety
Kategorie servicedesk, driv byl pod Nastroji. Ma obe strany:
| Spoustec | Kdy |
|---|---|
created |
zalozen ticket, at uz z kanalu nebo rucne |
unknown-customer |
k ticketu se nepodarilo dohledat firmu |
assigned |
ticket dostal konkretniho cloveka |
status-changed |
prechod do jineho stavu vcetne vyreseni |
| Akce | Co dela |
|---|---|
create |
zalozi pozadavek |
assign |
preda ticket cloveku |
set-status |
posune stav |
link-customer |
doplni firmu z CRM |
comment |
zapise komentar do logu |
Typicky retez, ktery z toho jde postavit:
Prijata zprava z WhatsApp
Zaradit do kategorie (AI)
Dohledat firmu podle telefonu (CRM)
knownCustomer?
ANO Zalozit ticket, Priradit resiteli
NE Zalozit ticket, Zalozit obchodni pripad, Upozornit servicedesk
Parametry od sluzby
Nektere spoustece si data urcuji samy. E-mail posila from, subject, body.
WhatsApp posila phone, text. Ticket posila knownCustomer. Uzivatel si je
nevymysli, ale potrebuje nad nimi stavet podminky.
Katalog je proto deklaruje v providedFields u operace. Chovaji se pak takhle:
- builder je ukazuje jen ke cteni, pridat ani prejmenovat nejdou,
- server je pri ulozeni stromu vzdy dosadi z katalogu a to, co poslal klient,
zahodi (
normalizeTriggerFieldsvsrc/routes/dashboard.ts), - dosazeni probiha pred validaci, jinak by podminky odkazujici na katalogova ID vypadaly jako rozbite.
ID techto parametru (email.from, ticket.knownCustomer) musi zustat stabilni.
Odkazuji se na ne podminky v ulozenych stromech, prejmenovani ID je rozbije.
Spoustece bez providedFields (webhook, formular) funguji jako predtim -
parametry si deklaruje uzivatel.
Stranky portalu
/dashboard/tickety seznam, filtry, prehled vytizeni
/dashboard/tickety/:id detail: prubeh a log, resitel, zakaznik, puvod
Co chybi
| Chybi | Poznamka |
|---|---|
| Bugs a wishes | vyvojarska agenda, samostatna evidence |
| Skutecny beh automatizaci | log ticketu zatim plni simulace, ne runtime |
| Napojeni logu na beh | automationId je odkaz, historie behu ale neexistuje |
| Odpoved zakaznikovi z detailu | akce send u kanalu se z portalu nevola |
| SLA a eskalace | zadne lhuty, capacity je jen orientacni |
| Databaze | data v pameti, restart je vrati na vychozi sadu |