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.
This commit is contained in:
JiriUhlir
2026-09-09 19:35:00 +02:00
parent 22dda2d139
commit c25e826766
72 changed files with 3978 additions and 1118 deletions
+91
View File
@@ -231,6 +231,96 @@ 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`:
```ts
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](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](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](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
@@ -509,5 +599,6 @@ to rozhodnout vedome, ne omylem. Varianty od nejlevnejsi:
| `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 |