# 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 |