# Nastavení, práva, typy ticketů, akce a widgety Co všechno se dá nastavit z portálu a proč je to postavené takhle. Model je z [09-navrh-rozsireni.md](09-navrh-rozsireni.md), tady je hotový stav. Seznam funkcí a komponent, které se u toho mají použít, je v [15-rejstrik-funkci.md](15-rejstrik-funkci.md). ## Jedna vrstva pro všechny záznamy Firmy, uživatelé, role, řešitelé, skupiny, typy ticketů, akce a widgety mají společné to, že se u nich dělá totéž: seznam za firmu, detail, zápis, mazání, kontrola práva, zápis do auditu. Napsat to osmkrát znamená osm skoro stejných souborů, ze kterých se jeden opraví a ostatní ne. Proto tři vrstvy, každá napsaná jednou: | Vrstva | Kde | Co dělá | | -------- | -------------------------------------- | --------------------------------------------------------------------------------------------- | | Úložiště | `src/data/store/` | `EntityStore` a dvě implementace. Volající nepozná, jestli běží Postgres nebo JSON soubor. | | API | `src/routes/crud.ts` | `crudRouter` vyrobí pětici endpointů včetně práva a auditu. | | Klient | `components/dashboard/EntityAdmin.tsx` | Tabulka, modál, validace, mazání. | Nová entita v nastavení pak znamená: `defineStore` v modulu entity, jeden řádek v `bootstrap.ts`, jeden `crudRouter` v `src/routes/settings/.ts` a mount v `settings/index.ts`, jeden popis v `Settings.tsx`. Nic víc. `crudRouter` navic s volbou `event` publikuje `.created`, `.updated` a `.deleted` s celym zaznamem v payloadu, takze klientsky sklad ciselniku (`lib/collections.tsx`) se opravi bez dotazu. Po zapisu se obnovi **jen cache te entity** (`bootstrapDataRefresh(route)`), ne vsechny. ## Práva jsou data Práv je 26 a jsou v katalogu (`src/data/permissions.ts`). Role je **záznam**, ne konstanta v kódu: firma si může udělat vlastní roli s vlastní kombinací práv. Systémové role (`admin`, `agent`, `viewer`) se měnit nedají, aby si nikdo neodebral právo, kterým je odebírá. Členství uživatele ve firmě nese `roleIds`, protože jeden člověk může být v Automii řešitel a u Nordisu správce jejich servicedesku. Neznámé právo se **odmítne** už při ukládání role. Kdyby se jen ignorovalo, překlep by znamenal roli, která tiše nic nesmí. ### Prava se kontroluji u kazde route, za firmu zaznamu Do zari 2026 se vetsina rout ptala jen "je clen firmy". Prava v katalogu byla, ale `viewer` mohl zalozit konektor nebo smazat automatizaci. Ted: | Co | Pravo | | ---------------------------------------- | -------------------------------------------------------- | | firmy | jen spravce platformy | | uzivatele | spravce platformy, nebo `user.manage` jen ve sve firme | | lide (resitele = clenove firmy) | `people.manage` jen ve sve firme, zaklada ucet s clenstvim | | pozvanky | `user.manage`, role jen z te firmy | | konektory (zalozeni, uprava, smazani, test) | `connector.manage` | | automatizace (zalozeni, uprava, smazani, novy token) | `automation.edit` | | stav incidentu | `incident.manage` za firmu incidentu; platformni jen spravce platformy | | vestavene akce na ticketu | pravo akce za firmu ticketu plus strop viditelnosti | | prepnuti na jiny ucet, audit | `impersonate`, `audit.view` | Spravce firmy s `user.manage` nenastavi `platformAdmin`, neprida clenstvi v jine firme, nesahne na spravce platformy a nesmaze cloveka, ktery je i v jine firme. Duvody a rozhodnuti "kdo koho zaklada" jsou v [07-firmy-a-prava.md](07-firmy-a-prava.md). Zalozka Lide (`people.manage`) uz nespravuje zvlastni zaznam resitele: **resitel je clenstvi uctu ve firme** a ID resitele je ID uctu, viz [06-tickety.md](06-tickety.md). Zalozeni cloveka v Lidech zalozi ucet (heslo nepovinne, bez nej nahodne) nebo prida clenstvi uctu, ktery uz s tim e-mailem existuje. Upravit jde jmeno, e-mail, popisek, kapacita, externi ID a role clenstvi; smazani odebere jen clenstvi. Sprava uctu v Nastaveni (`/users`, spravce platformy) k clenstvi bere i `seesAllTenant`, `role`, `capacity`, `externalIds` a `enabled` a pri uprave je zachova. Prepinac `enabled` v Lidech je za clenstvi: vypne cloveka jen v teto firme, cely ucet vypina jen sprava uzivatelu. ### Firma z registru ARES Zalozka Firmy ma vedle rucniho zalozeni cestu pres ARES (`components/dashboard/AresTenantDialog.tsx`), jen pro spravce platformy: IC nebo nazev, vyber firmy, vyber statutaru, kteri dostanou ucet s roli spravce a zastupnou adresou `IC-poradi@placeholder.cz`. Ucet je zaroven resitel (resitel je clenstvi uctu), takze jsou hned v Lidech; funkce z rejstriku je popisek clenstvi. Zastupne adresy se musi nahradit skutecnymi, jinak se ti lide neprihlasi. Firma pak nese `ico`, `dic`, `address` a `legalForm`, IC je unikatni. Endpointy jsou v [04-api.md](04-api.md). ## Navigace chodí ze serveru Co uživatel vidí za záložky, je průnik dvou věcí: 1. co má firma zaplacené a zapnuté (`tenantFeatures`), 2. na co má člověk právo. Počítá to server (`navFor`) a klient jen kreslí. Kdyby si klient záložky dovozoval sám, počítalo by se to na dvou místech a jednou by se to rozešlo - a ta chyba by znamenala odkaz do 403. ## Typy ticketů a tagy Typ ticketu nese **vlastní pole** (klíč, popisek, typ, povinnost, nápověda) a může mít vlastní workflow stavů. Tag je volné označení bez polí za ním. Rozdíl není kosmetický: - **Typ** znamená "za tímhle stojí data". Objednávka má číslo objednávky a částku, takže se na ni dá navázat akce, která ta data potřebuje. - **Tag** znamená "takhle si to označuju". Hodí se na filtry a widgety. Akce se proto váže na **typ nebo tag**, ale akci s daty má smysl vázat na typ. Pole `object` a `list` se v detailu ticketu ručně nevyplňují - plní je automatizace. Textarea s JSONem by tam byla past, ne pohodlí. ## Vydefinované akce Akce a automatizační stromy jsou **dvě různé věci** a nemají mezi sebou žádnou společnou mezivrstvu. Automatizace běží sama, akce je tlačítko, které zmáčkne člověk. Tělo akce je jedno z trojice: | Tělo | Kdy | Příklad | | ----------------- | ------------------------------- | ---------------------------------------------------- | | Operace konektoru | běžný případ | odeslat objednávku do iDokladu | | Vlastní strom | akce má víc kroků a rozhodování | dohledat kontakt, vystavit fakturu, odeslat e-mailem | | Skript | nic z toho nestačí | vlastní výpočet nebo cizí API, které v katalogu není | Server vrací k ticketu **jen akce, které v té situaci opravdu jdou spustit**: sedí typ nebo tag, projdou podmínky a volající na ně má právo. Klient nefiltruje nic. Spuštění akce vrací 200 i když akce selhala - selhání akce není chyba API. V odpovědi je celé chybové hlášení a celý průběh se zapíše do logu ticketu. Vestavěné akce (typ, tagy, skupina, přiřazení, stav, komentář) jsou zvlášť: mění ticket sám, ne cizí službu, a každá má vlastní právo. ## Vlastní widgety Widget je definice zdroje dat plus způsob zobrazení. Zdroj je ticket nebo automatizace, k tomu filtr a případné seskupení (podle stavu, kanálu, řešitele, typu, tagu). Data pro celý přehled chodí **jedním requestem**. Deset dlaždic nesmí znamenat deset dotazů. Viditelnost je stejná jako u služeb: všichni, konkrétní firmy, konkrétní lidé, nebo jen správce. ## Přepnutí na jiný účet Správce platformy se může podívat na portál očima konkrétního uživatele. Dvě věci, na kterých to stojí: 1. **Výchozí je jen pro čtení.** Podívat se, co klient vidí, je mnohem častější než za něj něco měnit, a nechtěný zápis pod cizím jménem je to nejhorší, co se tady může stát. Zápis se musí zapnout vědomě a middleware ho jinak odmítne u všeho kromě GET. 2. **Všechno je v auditu** včetně toho, kdo to doopravdy byl. Bez auditu nemá přepínání účtů co dělat v produktu. Svůj původní token si klient odkládá do `sessionStorage`. Server o něm nic neví, takže ho nemůže ani omylem prodloužit. ## Co se ukládá a co se drží v paměti | Data | Jak | Proč | | ----------------------------------------------------------------------- | ----------------- | ---------------------------------------------------------------------------------------------------- | | Firmy, uživatelé, role, řešitelé, skupiny, typy, akce, widgety, záložky | `withCache` | Čtou se při každém requestu, mění se zřídka. Kopie v paměti (`byId` je `Map`), obnova jen te entity po zápisu. | | Tickety včetně logu, automatizace, incidenty, rozložení dashboardu | `withMirror` | Mění se v paměti za provozu, po každé změně se celý záznam zapíše. Zápisy téhož ID jdou za sebou, ne naráz. | | Audit | přímo do úložiště | Jen se připisuje, nikdy nečte při každém requestu. Oreza se davkou po 50 zapisech nebo jednou za minutu. | | Konektory | vlastní úložiště | Nesou šifrovaná tajemství a potřebují částečný unikátní index. Viz [14-databaze.md](14-databaze.md). | Čítače ID se při startu dopočítají z uložených záznamů, takže nový ticket nikdy nepřepíše starý.