Files
csbot-prototype/documentation/06-tickety.md
T
JiriUhlir c25e826766 Poptavka z webu je ticket, prilohy, udaje provozovatele, kolacovy graf
Provozovatel portalu: firma s priznakem portalOperator (jen jedna, zapnuti
odebere ostatnim) a novymi poli contactEmail, contactPhone, website vedle
ico, dic, adresy a pravni formy. Verejny GET /api/public/brand vraci jeji
udaje a web je bere pres useBrand() na kontaktu, v paticce, O nas,
prihlaseni i v titulku; brand.ts je jen zaloha.

Poptavka z webu zaklada u provozovatele ticket kanalu form: predmet
"Poptavka: tema", telo JSON s poli formulare, tag Poptavka plus tema,
zakaznik z formulare, poznamka v logu. Bez provozovatele se jen zaloguje.

Prilohy ticketu: formular az 3 soubory po 5 MB, ticket az 10; nahrani,
seznam, stazeni a smazani (pravo ticket.comment, strop viditelnosti,
poznamky v logu, audit). Soubor jde v JSON jako Base64 a lezi v beznem
ulozisti, bez nove zavislosti; strop tela jen na techto cestach.

Vlastni widget s kreslenim Graf umi i pocet ticketu se seskupenim jako
kolac (PieChart.tsx, ciste SVG, osm barev z tokenu, zbytek jako ostatni).

OpenAPI rozdelene na mensi soubory (102 cest, 28 schemat overeno shodnych),
28 novych testu (135 celkem), dokumentace aktualizovana.
2026-09-09 19:35:00 +02:00

27 KiB
Raw Blame History

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 (kind: 'note'). Diky tomu je vsechno na jedne casove ose a nemusi se nikde skladat dohromady dva ruzne seznamy.

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 (normalizeTriggerFields v src/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:

  1. Nechat jak je a drzet se pravidla jedna automatizace na spoustec. Funguje dnes, nic se nemeni, ale nikdo to nevynucuje.
  2. Filtr na spousteci. Automatizace by umela rict "tenhle e-mail neni muj" jeste pred prvnim krokem. Umozni jednu automatizaci na pravidlo.
  3. 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