updated tickets
This commit is contained in:
@@ -0,0 +1,185 @@
|
||||
# 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`
|
||||
|
||||
```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.
|
||||
|
||||
```ts
|
||||
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 (`normalizeTriggerFields` v `src/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 |
|
||||
Reference in New Issue
Block a user