Karta Resitel ma tlacitko Uzavrit ticket a Znovu otevrit; meni jen priznak closed, text stavu zustava a meni se v poli Stav nezavisle. Komentar je vlastni druh radku logu (kind comment) s autorem; detail ma sekci Komentare se seznamem (jmeno, cas, text) a formularem, log ho ukazuje s autorem. Test tests/data/comments.test.ts. Sekce Zakaznik jen popisuje: pryc odznak "nedohledan", varovne zbarveni, text o CRM a garantovi i zvyrazneni v tabulce a na karte; ID v CRM jen kdyz existuje. Uzivatelske texty: Portal a Prehled -> Dashboard (zalozka Prehled v Lidech je Souhrn), v auditu "odepreni" -> "zamitnute pokusy". Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
27 KiB
06 - Tickety
Co ticket je a co neni
Ticket je prichozi pozadavek odkudkoliv. Prisla zprava na WhatsApp, Messenger nebo Instagram, 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/tickets/model.ts (fasada src/data/ticketStore.ts)
interface Ticket {
id: string;
subject: string;
body: string; // cely text pozadavku, prazdny = krok ho nenaplnil
sourceRef: string | null; // odkaz na zdrojovou zpravu u poskytovatele
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".
subject je kratke shrnuti do seznamu, body je cely text pozadavku. Do body
patri telo e-mailu, zprava z WhatsApp nebo prepis hovoru. Kdyz je prazdne,
znamena to, ze krok "Zalozit ticket" nemel nastavene pole Obsah - detail ticketu
to napise nahlas misto toho, aby ukazal prazdne misto.
body se zapisuje pri kazde udalosti, ne jen pri zalozeni, stejne jako
skoro vsechno ostatni - podrobnosti nize v "Zalozeni a doplneni delaji totez".
Cela historie zustava v udalostech ticketu, at uz je v body cokoliv.
Obsah se na detailu cte, ne jen vypisuje
body je jeden retezec, ale co v nem stoji, urcuje ten, kdo ho naplnil. Casto
je to veta od cloveka, casto ale cela struktura z cizi aplikace: krok dosadi
{{data}} a sablona objekt prevede na JSON. Vypsat takovou zavorku jako
odstavec je necitelne, i kdyz jsou v ni presne ty udaje, kvuli kterym ticket
vznikl.
TicketBody proto telo prectе a podle toho vykresli:
| Co v tele je | Jak se to ukaze |
|---|---|
| veta od cloveka | text tak, jak prisel |
| cely retezec je JSON | tabulka klic a hodnota |
| seznam objektu | tabulka se sloupci podle klicu |
| text a za nim JSON | odstavec a pod nim tabulka |
| cokoliv, co se neprecte | text tak, jak prisel |
Ta ctvrta radka je bezny pripad: sablona {{voicebotId}}, {{data}} da presne
tohle. Deli se az od prvni zavorky, od ktere je zbytek platny JSON - jinak
by veta s jednou slozenou zavorkou skoncila rozpulena.
Zanoreni se kresli o uroven niz jako vnorena tabulka, hloubeji uz jako JSON. Tabulka v tabulce v tabulce se necte a odesilatele umi zanorit data hluboko.
Nic se nezahazuje. Kdyz se struktura precist neda, ukaze se puvodni text.
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 clenstvi uctu ve firme, ne vlastni zaznam. Kazdy clen firmy
muze mit tickety u sebe a ID resitele je ID uctu (usr_...). Person
(src/shared/people.ts) je jen pohled pro API, sklada ho personView:
| Pole | Odkud |
|---|---|
id |
ID uctu |
tenantId |
firma clenstvi |
name, email |
ucet |
role (popisek), capacity, externalIds |
clenstvi (Membership v src/shared/users.ts) |
enabled |
stav uctu |
roleIds |
role clenstvi |
Jeden pohled na jedno clenstvi: tentyz clovek ve dvou firmach je v seznamu
dvakrat, pokazde se stejnym id a jinym tenantId. Nic se tu neuklada,
vsechno se odvozuje z kopie uctu v pameti (src/data/users.ts).
Driv to byly dve veci. Uzivatel se prihlasoval, resitel mel u sebe tickety a spojka mezi nimi byl e-mail. To byla chyba: e-mail je prihlasovaci jmeno, ktere spravce muze zmenit, a tim se vazba tise rozpadla - clovek prestal videt "moje tickety" a nikdo nevedel proc. Prvni priznak byl, ze lide zalozeni z ARES nebyli v Lidech videt. Spojovat pres ID misto e-mailu by znamenalo dal drzet dva zaznamy o jednom cloveku, tak se slily do jednoho.
Technik bez uctu uz neexistuje. Kdo ma mit tickety, dostane ucet. Kdyz spravce nezada heslo, vygeneruje se nahodne a ucet se nemusi nikdy prihlasit; az bude chtit, heslo si nastavi pres pozvanku nebo mu ho zmeni spravce.
Filtr "moje tickety" (?assignee=me) tak nic nedohledava: personIdFor vrati
ID uctu, kdyz ma ve firme clenstvi, jinak null. Spravce platformy bez
clenstvi resitelem neni a filtr se mu v portalu nabidne jako nedostupny misto
toho, aby vracel prazdno bez vysvetleni.
Kazdy resitel ma capacity (vychozi 8), 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. Kapacita i popisek visi
na clenstvi, ne na uctu: v jedne firme je clovek dispecer s kapacitou 6,
v druhe ucetni s kapacitou 3.
Sprava je v zalozce Lide pod pravem people.manage. Spravce firmy tam
zaklada ucty (nebo prida clenstvi uctu, ktery uz s tim e-mailem existuje),
upravuje jmeno, e-mail, popisek, kapacitu, externi ID a role clenstvi
a odebira clenstvi. Ucet, ktery po odebrani nema zadne clenstvi a neni
spravce platformy, se vypne. Prepinac enabled v Lidech je za clenstvi:
vypnuty se v teto firme nenabizi k prirazeni, stare tickety mu zustanou
a v jinych firmach pracuje dal. Cely ucet vypina jen sprava uzivatelu.
Person.enabled je proto ucet.enabled && clenstvi.enabled !== false.
Fronta skupiny a prevzeti
Ticket nemusi mit resitele hned. Muze lezet u skupiny a cekat, az si ho
nekdo vezme - POST /tickets/:id/claim. Kdo je ve skupine, si ho vezme sam,
prace se nerozdava shora.
Proc obojí vedle sebe:
| Situace | Co se hodi |
|---|---|
| havarie, musi to nekdo hned resit | automat prideli nejvolnejsimu |
| bezny dotaz, lidi maji ruzne dny | necha se ve fronte skupiny |
Proto je prepinac Priradit rovnou nejvolnejsimu na kroku automatizace
(ticket/assign-group), ne na skupine: tataz skupina potrebuje obe chovani,
jen u jineho typu prace.
Vzit si ticket, ktery uz nekdo resi, neni prevzeti, ale prehozeni. Chce to pravo
ticket.assign.others a jde to jen pres prirazeni, ne pres prevzeti.
Pozvanky do firmy
src/data/invites.ts
Novy clovek se do firmy dostane odkazem s nahodnym kodem, ne tim, ze mu nekdo zalozi ucet a posle heslo. Duvody:
- heslo nikdy nikam neposilame. Kdo si ho nastavi sam za odkazem, ma ho jen on,
- pozvanka vyprsi (tyden) a da se zrusit, ucet ne,
- kdo uz ucet ma, se jen pripoji k dalsi firme misto zakladani druheho - ale musi zadat svoje heslo, jinak by kdokoliv s odkazem pripojil cizi adresu ke sve firme.
U pozvanky se rovnou rekne, jake role clovek dostane. Resitelem je kazdy
clen firmy, takze zadna dalsi volba neni potreba; stary priznak asPerson se
prijme a ignoruje, aby starsi klient dal fungoval.
Sprava je v zalozce Lide, ne v nastaveni: pozvat kolegu je bezna denni prace.
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, s vlastnim druhem kind: 'comment'
a polem author. Diky tomu je vsechno na jedne casove ose a nemusi se nikde
skladat dohromady dva ruzne seznamy; detail z logu vybere jen komentare do
sekce Komentare (TicketComments.tsx) a log je ukazuje s autorem. Starsi
komentare ulozene jako note "autor: text" zustavaji jen v logu.
Prilohy do logu zapisuji taky note: Příloha: nazev (velikost) pri nahrani,
Příloha odebrána: nazev pri smazani (noteAttachment
v src/data/tickets/store.ts). Radek v logu je jedine, co ticket o priloze
vi - soubor sam lezi jinde, viz nize.
Prilohy
src/data/attachments.ts, typ Attachment v src/shared/attachments.ts
Priloha je soubor, ktery prisel s poptavkou z webu nebo ho nekdo pripojil
v detailu ticketu. Neni to pole ticketu. Ticket o prilohach nic nenese,
lezi ve vlastni kolekci attachment obecneho uloziste a vazou se pres
ticketId a tenantId:
interface Attachment {
id: string; // att_xxxxxxxx
tenantId: string; // firma ticketu, hranice viditelnosti
ticketId: string;
name: string; // bez cesty a ridicich znaku, nejvys 200 znaku
mime: string; // neznamy = application/octet-stream
size: number; // bajty po dekodovani
uploadedBy: string | null; // ucet; null = prisla z verejneho formulare
createdAt: string;
}
Proc vlastni kolekce a ne pole na ticketu:
| Duvod | Co by se stalo s polem na ticketu |
|---|---|
| obsah je velky | kazde cteni seznamu ticketu by taha megabajty base64 |
| meni se nezavisle | nahrani souboru by prepisovalo cely ticket a soutezilo s komentari |
| seznam se vraci bez obsahu | obsah jde jen pres /attachments/:id/content, ne v detailu |
Obsah se uklada jako base64 uvnitr zaznamu. Zadna nova zavislost (multer,
S3 klient): prototyp ma ulozit jednotky MB a obecne uloziste to zvladne.
Cenou je, ze v rezimu file kazda zmena prepise cely attachment.json.
Az to zacne byt znat, presune se obsah do blob uloziste; rozhrani modulu
zustane, zmeni se jen odkud getAttachment cte content.
Viz 14-databaze.md.
Limity: 5 MB na soubor (MAX_ATTACHMENT_BYTES), 10 na ticket
(MAX_ATTACHMENTS_PER_TICKET). checkUploads je cista funkce nad celou
davkou: kdyz neprojde treti soubor, neulozi se ani prvni dva, jinak by klient
nevedel, co uz tam je. Kontaktni formular ji vola pred zalozenim ticketu,
aby po chybe nezustala poptavka bez souboru, ktere k ni patrily.
Nazev projde sanitizeName: bere se jen cast za poslednim lomitkem,
ridici znaky se vyhodi. Klient posila, co chce - ../../etc/passwd nebo
nazev s novym radkem, ktery by rozbil hlavicku Content-Disposition.
Kazde nahrani a smazani zapise radek do logu ticketu, posune updatedAt
a posle ticket.updated, aby se detail v portalu prekreslil stejne jako po
komentari. Pravo je ticket.comment za firmu ticketu: priloha je jen dalsi
zprava k ticketu. API je v 04-api.md, portal ma sekci
TicketAttachments v detailu ticketu.
Mazani ticketu dnes neexistuje, proto tu neni kaskada. Az pribude, patri
do attachments.ts removeAttachmentsOfTicket(ticketId).
Poptavka z webu
src/routes/contact.ts
Kontaktni formular na verejnem webu je kanal form jako kazdy jiny:
POST /api/contact zalozi ticket firme, ktera je provozovatelem portalu
(operatorTenant, viz 07-firmy-a-prava.md). Driv se
poptavka jen zalogovala a nikdo ji nevidel.
| Pole ticketu | Hodnota |
|---|---|
channel |
form |
subject |
Poptávka: <tema> (automatizace, voicebot, integrace, dashboard, podpora, jine) |
body |
JSON formulare: name, email, company, phone, topic, message, receivedAt |
tags |
Poptávka a tema |
customer |
id null, company a contact z formulare, reply = e-mail |
externalSource |
web-form, externalId null |
createdById |
null |
trace |
note "Poptávka z webového formuláře" s odesilatelem a tematem |
Telo je JSON, ne veta, zamerne: TicketBody ho ukaze jako tabulku klic
a hodnota (viz vyse "Obsah se na detailu cte") a zadne pole formulare se
neztrati v prose. Prilohy z formulare (nejvys 3) se ulozi pres
addAttachments s uploadedBy null.
Bez provozovatele se poptavka jen zaloguje (warn) a odpoved je porad 202: zvenku nema byt poznat, jak je portal nastaveny. Upozorneni e-mailem na novou poptavku zatim neni, resitel ji vidi az v seznamu ticketu.
Opakovana udalost
Odesilatele umi poslat stejnou zpravu i osmdesatkrat za minutu. Kdyz prijde presne totez co posledne (stejny typ, zdroj, popisek a stejna data), nezaklada se dalsi radek: pricte se k pocitadlu u te predchozi.
| Co se stane | Proc |
|---|---|
repeats +1, lastAt = ted |
osmdesat radku znamena, ze v historii nikdo nic nenajde |
| ticket se nemeni | jinak by duplikat rozblikal dashboard a spustil automatizaci na zmenu ticketu |
| do logu se nezapisuje nic | log ma ukazovat zmeny, ne to, ze se nezmenilo nic |
| udalost se nezahazuje | bez pocitadla by nikdo nezjistil, ze proti nam neco tluce ve smycce |
Porovnava se jen s posledni udalosti. "Objednavka pripravena" muze legitimne prijit znovu za hodinu, kdyz se mezitim stalo neco jineho - to je novy fakt.
Co je v logu videt
Log se cte jako prislo tohle a zpusobilo to tuhle zmenu. Zmeny, ktere udela
beh automatizace, se radi pod jeho radek udalosti (parentId), takze nejde
zamenit, co zpusobilo co:
Prijata udalost: stav completed (automatizace TEST) 13:13:27
stav in-progress -> completed 13:13:27
stitky +hovor 13:13:27
Prijata udalost: stav completed 22x beze zmeny 13:13:34
U udalosti stoji, kdo ji prinesl. Prvni otazka nad zmenenym ticketem je
"kdo mi do toho sahl" a odpoved webhook na ni neodpovida.
Data udalosti se ukladaji cela. Kdyz krok nema vlastni, ulozi se to, cim beh zacal - u webhooku prijate telo. Prazdna udalost je horsi nez zadna: tvari se, ze se neco stalo, a nerekne co.
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
Co se ticketu preda
Akce "Zalozit ticket" ma nastavitelna pole. Klikni na krok ve strome a vyplnis
je primo tam. Hodnota je sablona: {{nazev}} se nahradi parametrem
spoustece.
| Pole | Typicka hodnota u WhatsApp |
|---|---|
| Predmet | Zprava od {{profileName}} |
| Obsah | {{text}} |
| Firma | necha se prazdne, doplni CRM krok |
| Kontakt | {{profileName}} |
| Odpoved na | {{phone}} |
| Priorita | vyber ze seznamu |
| Resitel | vyber ze seznamu lidi |
Nabidka parametru je pod poli. Kliknuti vlozi {{nazev}} na pozici kurzoru,
takze se nemusi psat rucne a neudela se preklep.
Zalozeni a doplneni delaji totez
Kdyz uz ticket se stejnym externim ID existuje, zapise se do nej totez co pri zalozeni. Plati jedno pravidlo:
Neprazdna hodnota prepise, prazdna nemaze.
Diky tomu nemusi data prijit najednou. U hovoru prvni zprava jen ohlasi, ze zacal, a hodnoceni dorazi az posledni - ta uz na ticket dopadne, i kdyz ticket zalozila ta prvni.
| Pole | Chovani u druhe a dalsi zpravy |
|---|---|
| Predmet, Obsah | neprazdna hodnota prepise |
| Typ, Priorita, Kanal | neprazdna hodnota prepise |
| Zakaznik, Resitel, Skupina | neprazdna hodnota prepise, po polozkach |
| Stav, Vyrizeny | prepise se, pres updateTicketStatus |
| Stitky | pridaji se, existujici zustanou |
| Vlastni pole typu | klice se scitaji, prazdna hodnota nemaze |
Dve vyjimky, obe zamerne:
- Zaloha predmetu (externi ID, kdyz zadny predmet nezadate) se dosadi jen pri vzniku. Jinak by se predmet prepisoval externim ID i tam, kde ho nikdo nechtel.
- Stav nechodi tudy, ale pres
updateTicketStatus. To resi i priznak vyrizeni, cas vyreseni a pocet znovuotevreni - obejit ho by ta cisla rozbilo.
Vlastni pole typu se scitaji podle klicu: kdyz prvni zprava prinese data
a druha data2, ma ticket obe. Prazdny retezec nemaze, protoze sablona, ktera
na nic neukazuje, se dosadi prazdnem. Vymazat pole jde poslanim null - to uz
je zamer, ne vedlejsi ucinek nevyplnene sablony.
Vyber resitele se plni ze seznamu v people.ts, ne z rucne psaneho ID. Novy
clovek v tymu se v nabidce objevi sam.
Co je kde videt
Krok vidi parametry spoustece plus vystupy vsech kroku, ktere jsou pred nim. Diky tomu jde do stromu vlozit predvalidaci a vetvit se podle jeji odpovedi.
Dve pravidla:
- krok vidi to, co je pred nim ve stejne sekvenci, a to, co videl jeho rodic,
- vetev podminky nepridava nic do sekvence za podminkou. Vetev nemusela probehnout, spolehat se na jeji vystup by byla past.
Vystupy akci deklaruje katalog v outputFields. Ma je napriklad "Dohledat firmu"
(customerKnown, companyId, companyName), "Zaradit do kategorie"
(category, confidence) nebo "Zalozit ticket" (newTicketId).
Vypocet je v src/data/flowScope.ts, kopie pro UI v web/src/lib/flow.ts.
Jak udelat podminku
Tri ruzne pripady, kazdy se resi jinak.
Na prichozi zpravu. text je parametr spoustece, funguje rovnou:
Prijata zprava z WhatsApp
text obsahuje "faktura"
ANO ...
Na vysledek mezikroku. Tohle je ta predvalidace. Krok "Dohledat firmu" vrati
customerKnown a podminka se na nej zepta:
Prijata zprava z WhatsApp
Dohledat firmu Telefon {{phone}}
customerKnown je splneno
ANO Zalozit ticket Firma {{companyName}}, Obsah {{text}}
NE Zalozit ticket bez firmy
Zalozit obchodni pripad
{{companyName}} je uvnitr vetve dostupne, protoze krok "Dohledat firmu" je
pred podminkou. Presne tenhle strom je ve vzorovych automatizacich.
Na obsah uz zalozeneho ticketu. Ve stejnem strome nejde, ticket v tu chvili
teprve vznika a "Zalozit ticket" vraci jen newTicketId, ne cely ticket. Dela se
druhou automatizaci se spoustecem "Zalozen ticket":
Zalozen ticket
assigned neni splneno
ANO body obsahuje "faktur"
ANO Priradit resiteli ID {{ticketId}}, Martin Kriz
NE body obsahuje "voicebot"
ANO Priradit Eva Novakova
NE Priradit Karel Vomacka
Rozdeleni neni obchazeni omezeni, ale zamer. Prijem z kanalu je jedna automatizace na kanal, smerovani je jedna spolecna nad vsemi tickety.
Vzorove automatizace
V src/data/automations/seed.ts (ukazkove v seedDemo.ts) jsou nasazene presne v tomhle rozdeleni:
| Automatizace | Co ukazuje |
|---|---|
| WhatsApp: zprava do ticketu | predvalidace v CRM a vetveni podle vysledku |
| Facebook: zprava do ticketu | prijem bez predvalidace, nemame podle ceho hledat |
| E-mail: pozadavky do ticketu | dohledani podle {{from}}, plus odpoved zadavateli |
| Smerovani ticketu na resitele | jedna spolecna logika nad vsemi tickety |
Prijmove automatizace zamerne neprirazuji resitele. Nechavaji ticket ve
fronte a smerovani si ho prevezme. Podminka assigned neni splneno na zacatku
smerovani zaridi, ze rucni prirazeni se neprepise.
Sablony
Odkazuje se jmenem parametru ({{subject}}), ne jeho ID. Jmeno uzivatel
vidi a pise, {{f_42}} by nikdo neprecetl.
Cenou je, ze prejmenovani parametru sablonu rozbije. Proto se to hlida:
- builder podtrhne pole a napise, ktery parametr chybi,
- server to vraci mezi
issues, tedy jako nedodelek, ne jako chybu. Rozdelana prace se ulozi, jen automatizace nepujde zapnout.
Naopak chyba (400) je nastaveni pole, ktere akce v katalogu nema. To uz neni nedodelek, ale rozbity strom - ulozit ho by znamenalo drzet data, ktera nikdo neprecte.
Chybejici parametr se pri behu dosadi jako prazdny retezec a zaloguje. Nechat
v textu {{neco}} by znamenalo poslat to zakaznikovi.
Akce, ktere inputs zatim nemaji, to v builderu napisou primo na karte kroku.
Lepsi nez nechat cloveka hledat nastaveni, ktere neexistuje.
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/automations.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
Otevrene rozhodnuti pro runtime
Model nema zadne "prvni shoda vyhrava". Vsechny automatizace se stejnym spoustecem se spusti, vsechny. Podminka je uvnitr stromu, ne na spousteci, takze automatizaci nezastavi pred prvnim krokem.
Doporucene rozdeleni (jedna automatizace na kanal, jedna spolecna na smerovani) na to nenarazi, protoze kazdy prijem ma jiny spoustec a smerovani je jen jedno. Problem vznikne, jakmile nekdo udela vic automatizaci nad stejnym spoustecem:
- tri automatizace na "Prijat e-mail", kazda s jinym
obsahuje, udelaji z jednoho e-mailu tri tickety, - dve smerovaci automatizace provedou dve prirazeni a poradi neni dane.
Dnes se to neprojevi, protoze runtime neexistuje. Az se bude psat, je potreba to rozhodnout vedome, ne omylem. Varianty od nejlevnejsi:
- Nechat jak je a drzet se pravidla jedna automatizace na spoustec. Funguje dnes, nic se nemeni, ale nikdo to nevynucuje.
- Filtr na spousteci. Automatizace by umela rict "tenhle e-mail neni muj" jeste pred prvnim krokem. Umozni jednu automatizaci na pravidlo.
- Poradi a prvni shoda vyhrava. Nejmocnejsi, ale chovani zavisle na neviditelnem poradi se spatne ladi. Nedoporucuje se.
Co chybi
| Chybi | Poznamka |
|---|---|
| Bugs a wishes | vyvojarska agenda, samostatna evidence |
| Skutecny beh automatizaci | sablony se ukladaji, ale nikdo je nevyhodnocuje |
inputs u zbylych konektoru |
zatim ticket, kanaly, CRM a AI, ostatni maji jen napovedu |
| Napojeni logu na beh | automationId je odkaz, historie behu ale neexistuje |
| Odpoved zakaznikovi z detailu | akce send u kanalu se z portalu nevola |
| Upozorneni na poptavku z webu | ticket vznikne, ale nikomu neprijde e-mail |
| SLA a eskalace | zadne lhuty, capacity je jen orientacni |
| Databaze | data v pameti, restart je vrati na vychozi sadu |