Resitel je clenstvi uctu, firma z ARES, prepinani jazyku schovane

Resitel uz neni vlastni zaznam spojeny s uctem pres e-mail: je to
clenstvi uctu ve firme a jeho ID je ID uctu. Popisek, kapacita, externi
ID a zapnuti visi na clenstvi, takze clovek ve dvou firmach je v kazde
jinak a spravce firmy ho vypne jen u sebe. Odebrani z firmy odebere jen
clenstvi. Stara data se pri startu jednou prevedou (migratePeople.ts),
vcetne odkazu v ticketech, skupinach, automatizacich, akcich a widgetech.
Sprava lidi v zalozce Lide zaklada ucty, pozvanka uz nema volbu resitele.

Zalozeni firmy z registru ARES: hledani podle IC nebo nazvu, dotazeni
IC, DIC, sidla a pravni formy, vyber soucasnych statutarnich zastupcu
a prokury, ucty spravce firmy s nahradnim e-mailem IC-poradi@placeholder.cz.
Vychozi rozlozeni dashboardu bez resitele neobsahuje list.myTickets.

Prepinani jazyku je docasne schovane (MULTILANG_ENABLED), web je cesky.
Dokumentace aktualizovana.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
JiriUhlir
2026-09-09 13:09:35 +02:00
co-authored by Claude Fable 5.1
parent 6eed909a0d
commit bc6508e1f3
41 changed files with 1415 additions and 328 deletions
+9 -1
View File
@@ -86,6 +86,14 @@ s tim, co uzivatel smi. Dvoji vypocet se jednou rozejde.
firmu jako povinny argument. Clovek muze byt spravce v jedne firme a bezny
uzivatel v druhe.
**Resitel je clenstvi uctu, ne vlastni zaznam.** Kazdy clen firmy muze mit
tickety u sebe a ID resitele je ID uctu. `Person` je jen pohled, ktery sklada
`personView` z uctu a jednoho jeho clenstvi (popisek, kapacita, externi ID
visi na clenstvi). Driv byl resitel zvlastni zaznam spojeny s uctem pres
e-mail; e-mail je ale prihlasovaci jmeno, ktere spravce meni, a vazba se
tise rozpadla. Technik bez uctu neexistuje - ucet dostane nahodne heslo
a nemusi se nikdy prihlasit. Podrobnosti v [06-tickety.md](06-tickety.md).
**Pravo se kontroluje u kazde route, za firmu zaznamu.** Clenstvi ve firme
neni pravo. Konektor chce `connector.manage`, automatizace `automation.edit`,
uzivatele `user.manage` a jen ve sve firme, firmy jen spravce platformy.
@@ -184,7 +192,7 @@ prvni misto, kam se divat.
| Misto | Co hrozi |
| ---------------------------------- | ----------------------------------------------------------------------------------------------- |
| ID operaci a poli v katalogu | odkazuji se na ne ulozene stromy, prejmenovani je rozbije |
| poradi v `bootstrapData` | tickety az po resitelich, nastroje MCP az po firmach |
| poradi v `bootstrapData` | tickety az po uctech, migrace resitelu (`migratePeople`) az po ticketech a automatizacich, nastroje MCP az po firmach |
| overeni konektoru bez `verifyPath` | proxy vraci 200 s prazdnym telem i pro neexistujici aplikaci, takze test projde a nic to nerika |
| migrace | nikdy se neupravuji zpetne, oprava je vzdy novy soubor |
| BOM v JSONu | rozbije Node i Vite, zapisovat UTF-8 bez BOM |
+3 -3
View File
@@ -19,12 +19,12 @@ React aplikaci ze slozky `dist/public`.
| Katalog sluzeb | hotovo | 34 sluzeb, 7 kategorii vcetne Obecne |
| Builder automatizaci | hotovo | strom akci, vetveni podminkou |
| Webhook s registrovanou adresou | hotovo | token generuje server, verejny endpoint validuje data |
| Tickety na konkretni lidi | hotovo | resitel, filtr moje, prehled vytizeni tymu |
| Tickety na konkretni lidi | hotovo | resitel je clen firmy, filtr moje, prehled vytizeni |
| Prijem udalosti do ticketu | hotovo | webhook na firmu, externi ID unikatni za firmu |
| Udalosti na ticketu | hotovo | dalsi zprava se navesi na tentyz ticket |
| Statistiky resitelu | hotovo | odbaveno, mediany casu, vracene, fronta |
| Pohledy tabulka a dlazdice | hotovo | tickety i lide |
| Stranka Lide a detail osoby | hotovo | vykon a co ma u sebe |
| Stranka Lide a detail osoby | hotovo | sprava clenu firmy, vykon a co ma u sebe |
| Log ticketu ve strome | hotovo | vcetne toho, co ktera sluzba vratila |
| Kanaly do ticketu | hotovo | WhatsApp, e-mail, hlas a formular jako spoustece |
| Parametry od sluzby | hotovo | katalog je deklaruje, server je dosazuje pri ulozeni |
@@ -47,7 +47,7 @@ React aplikaci ze slozky `dist/public`.
| Sprava clenstvi z portalu | hotovo | firmy a role v Nastaveni, lide a pozvanky v Lidech |
| Role a prava jako data | hotovo | 26 prav v katalogu, vlastni role za firmu |
| Zalozky a limity za firmu | hotovo | navigace chodi ze serveru, ne z kodu klienta |
| Osoby a skupiny resitelu | hotovo | ticket lze prehodit na skupinu, ne jen na cloveka |
| Osoby a skupiny resitelu | hotovo | resitel je clenstvi uctu, ticket jde i na skupinu |
| Prevzeti ticketu ze skupiny | hotovo | kdo ma cas, si praci vezme sam |
| Pozvanky do firmy | hotovo | odkaz s kodem, heslo si nastavi pozvany |
| Typy ticketu a vlastni pole | hotovo | typ rozhoduje, ktere akce se na ticketu ukazou |
+7 -4
View File
@@ -53,7 +53,8 @@ image jen `dist`, takze staci jedna slozka.
| `src/data/store/` | tri rezimy uloziste, `withCache`, `withMirror`, `initStores` |
| `src/data/snapshot.ts` | atomicky zapis JSONu pro rezim `file` |
| `src/data/ticketStore.ts` | tickety, jejich resitele, log prubehu, prehled vytizeni |
| `src/data/people.ts` | resitele ticketu - oddeleni od uzivatelu portalu |
| `src/data/people.ts` | resitele jako pohled na clenstvi uctu (`personView`), skupiny |
| `src/data/migratePeople.ts` | jednorazovy prevod starych zaznamu resitelu `ppl_` na ucty |
| `src/data/tenants.ts` | firmy, ktere portal pouzivaji, vcetne udaju z ARES |
| `src/data/access.ts` | kdo co vidi - jedno misto pro cely portal |
| `src/data/widgets.ts` | katalog widgetu prehledu |
@@ -138,9 +139,11 @@ ale nezkompiluje se. Podrobnosti v [07-firmy-a-prava.md](07-firmy-a-prava.md).
s tim, co uzivatel smi. Kdyby si to klient pocital sam, pocitalo by se to na dvou
mistech a jednou se to rozejde.
**Resitel neni uzivatel.** Uzivatel se prihlasuje do portalu, resitel ma u sebe
tickety. Technik muze mit tickety a ucet nikdy nemit. Spojka je e-mail,
podrobnosti v [06-tickety.md](06-tickety.md).
**Resitel je clenstvi uctu.** ID resitele je ID uctu, `Person` je jen pohled
na ucet a jedno jeho clenstvi ve firme (jmeno a e-mail z uctu, popisek,
kapacita a externi ID z clenstvi). Driv byl resitel vlastni zaznam spojeny
s uctem pres e-mail a zmena e-mailu vazbu tise rozbila. Stara uloziste
prevadi `migratePeople` pri startu. Podrobnosti v [06-tickety.md](06-tickety.md).
**Filtrovani ticketu dela server.** Klient posila query parametry a dostane hotovy
seznam. Kdyby filtroval sam, ukazoval by jina cisla nez prehled vytizeni.
+43 -5
View File
@@ -97,9 +97,14 @@ Sprava zaznamu ma u kazde entity stejnou petici (seznam, detail, vytvoreni,
uprava, mazani) na `/api/dashboard/settings/<entita>`, protoze ji dela jedna
fabrika (`src/routes/crud.ts`):
`tenants`, `users`, `roles`, `people`, `groups`, `ticket-types`, `actions`,
`tenants`, `users`, `roles`, `groups`, `ticket-types`, `actions`,
`widgets`, `features`.
`people` ma stejne cesty a stejne pravo (`people.manage`), ale vlastni
handlery v `settings.ts`: zaznam, ktery se meni, je ucet bez firmy
a odpoved je pohled za jednu firmu, coz fabrika neumi. Popis je nize
v sekci Lide.
## Format chyb
Jednotny pro cele API:
@@ -239,13 +244,43 @@ v uz nactenem seznamu.
toho, co ktera volana sluzba vratila.
`POST /api/dashboard/tickets/:id/assign` s telem `{"assigneeId": null}` vrati
ticket do fronty. Neznamy resitel vraci 404, ne tiche odpojeni.
ticket do fronty. `assigneeId` je ID uctu; kdo ve firme ticketu neni clenem,
vraci 404, ne tiche odpojeni.
`POST /api/dashboard/tickets/:id/claim` je **prevzeti prace**, ne prehozeni:
volajici si bere ticket sam a telo je prazdne. Smi to u ticketu bez resitele
a u ticketu ve skupine, ve ktere je. Kdyz uz ticket nekdo resi, vraci 409, resp.
403 u cizi skupiny - vzit nekomu rozdelanou praci je jine rozhodnuti a chce to
pravo `ticket.assign.others`. Kdo neni vedeny jako resitel, dostane 400.
pravo `ticket.assign.others`. Kdo ve firme nema clenstvi (neni resitel),
dostane 400.
## Lide (resitele)
Resitel je clenstvi uctu ve firme, ne vlastni zaznam; ID resitele je ID uctu.
Duvody v [06-tickety.md](06-tickety.md). `Person` je pohled: `id` a `name`,
`email`, `enabled` z uctu, `tenantId`, `role` (popisek), `capacity`,
`externalIds` a `roleIds` z clenstvi. Tentyz clovek ve dvou firmach prijde
dvakrat se stejnym `id`.
`GET /api/dashboard/people` vraci cleny zvolene firmy s povolenym uctem
a jejich skupiny (`items`, `groups`, `meId`). Sprava je na
`/api/dashboard/settings/people` pod pravem `people.manage`:
| Volani | Telo | Co se stane |
| ----------------- | --------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- |
| `GET people` | | vcetne vypnutych uctu |
| `POST people` | `{ name, email, password?, roleIds?, role?, capacity?, externalIds?, enabled? }` | zalozi ucet (heslo nahodne, kdyz chybi; role `role_agent`, kdyz chybi) s clenstvim ve firme, nebo prida clenstvi uctu, ktery s tim e-mailem uz je |
| `PATCH people/:id` | tataz pole, vsechna nepovinna | `name`, `email` (unikatni) a `enabled` meni ucet, ostatni clenstvi v teto firme |
| `DELETE people/:id` | | odebere jen clenstvi; ucet bez clenstvi, ktery neni spravce platformy, se vypne |
Spravce firmy na spravce platformy nesaha, stejne jako u `/users`. Udalosti
jsou `person.created`, `person.updated`, `person.deleted` s payloadem
`{ id, person }`, u smazani `{ id }`. `enabled` je za clenstvi: vypne cloveka
jen v teto firme, ucet a ostatni clenstvi zustavaji.
`/users` (spravce platformy) bere u clenstvi vedle `roleIds` i
`seesAllTenant`, `role`, `capacity` a `externalIds` a pri uprave je zachova,
takze zmena role v Nastaveni nesmaze kapacitu nastavenou v Lidech.
## Pozvanky do firmy
@@ -269,7 +304,9 @@ pripojil cizi adresu ke sve firme a videl by jeji data.
Sprava pozvanek (`/api/dashboard/invites`) chce pravo `user.manage`. Seznam
vraci u kazde pozvanky **celou adresu** vcetne prefixu proxy, aby slo rovnou
kopirovat - relativni cesta se do zpravy vlepit neda.
kopirovat - relativni cesta se do zpravy vlepit neda. Pozvanka nese jen
`email` a `roleIds`; stary priznak `asPerson` se prijme a ignoruje, protoze
resitelem je kazdy clen firmy.
## Akce na ticketu
@@ -300,7 +337,7 @@ v [07-firmy-a-prava.md](07-firmy-a-prava.md).
| ---------------------------------------- | ---------------------------------------------------------------------------------------------------- |
| `GET ares/companies?query=` | same cislice (1 az 8) hledaji IC presne, jinak nazev, nejvys 10. U firmy, ktera uz v portalu je, `existingTenantId` |
| `GET ares/companies/{ico}/persons` | soucasni statutari a prokura z verejneho rejstriku, u kazdeho navrzeny e-mail `IC-poradi@placeholder.cz` |
| `POST ares/tenants` | zalozi firmu a ucty vybranych osob, vraci firmu a seznam uctu |
| `POST ares/tenants` | zalozi firmu a ucty vybranych osob (`role_admin`, popisek clenstvi z funkci v rejstriku, kapacita 8), vraci firmu a seznam uctu. Ucet je zaroven resitel |
Chyba registru je `ares_error` s kodem podle toho, co ARES vratil - neni to
chyba naseho API a nema se opakovat automaticky. Adresa registru je
@@ -356,6 +393,7 @@ Pravo se vzdy pta **za firmu zaznamu**, ne za prepnutou firmu. Cizi firma je
| ----------------------------------------------- | --------------------------------------------------------------------- |
| firmy CRUD, ARES | spravce platformy |
| uzivatele CRUD | spravce platformy, nebo `user.manage` jen v ramci sve firmy |
| lide (`/settings/people`) | `people.manage` jen v ramci sve firmy |
| pozvanky | `user.manage`, role jen z te firmy |
| konektory create, update, delete, test | `connector.manage` |
| automatizace create, update, delete, regenerate | `automation.edit` |
+48 -12
View File
@@ -99,17 +99,53 @@ neprirazeny.
`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.
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`:
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.
| 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 |
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.
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
@@ -145,9 +181,9 @@ zalozi ucet a posle heslo. Duvody:
musi zadat svoje heslo, jinak by kdokoliv s odkazem pripojil cizi adresu ke
sve firme.
U pozvanky se rovnou rekne, jake role clovek dostane a jestli z nej ma byt
i **resitel**. Uzivatel a resitel nejsou totez, viz vyse - proto se to pta
misto hadani.
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.
+12 -3
View File
@@ -38,7 +38,7 @@ globalni, nesla by tahle situace vubec zapsat.
| -------- | ------------------------ | -------------------------- |
| `all` | napric vsemi firmami | jen `platformAdmin` |
| `tenant` | cela jedna firma | kdokoliv, kdo do ni patri |
| `mine` | jen tickety prihlaseneho | kdo ma navazaneho resitele |
| `mine` | jen tickety prihlaseneho | kdo je clenem firmy |
Posilaji se jako query: `?scope=tenant&tenantId=tnt_automia`.
@@ -132,11 +132,16 @@ rozlezlo po routach, driv nebo pozdeji vznikne endpoint, ktery filtr zapomene.
"tenants": [{ "id": "tnt_automia", "name": "Automia" }],
"defaultTenantId": "tnt_automia",
"canAssignOthers": true,
"personId": "ppl_uhlir",
"personId": "usr_1",
"roleNames": ["Správce"]
}
```
`personId` je ID uctu: resitel je clenstvi uctu ve firme, ne vlastni zaznam
(viz [06-tickety.md](06-tickety.md)). `null` znamena, ze prihlaseny ve
zvolene firme clenstvi nema - typicky spravce platformy - a pohled `mine`
pak v seznamu neni.
Pristup se pocita **jednou na request** (`attachAccess`
v `src/middleware/tenant.ts`, vysledek v `req.access`) a routy si z nej berou
firmu pres `tenantOrDeny`. Kazda route, ktera si to pocitala sama, to delala
@@ -217,6 +222,7 @@ a drzi se vsude, kde se neco zaklada:
| firma | jen spravce platformy (`platformOnly` u CRUD firem) | `src/routes/settings.ts` |
| firma z registru ARES | jen spravce platformy | `src/routes/ares.ts` |
| uzivatel | spravce platformy, nebo `user.manage` jen ve sve firme | `src/routes/settings.ts` |
| resitel (clen firmy, Lide) | `people.manage` jen ve sve firme, zaklada ucet s clenstvim | `src/routes/settings.ts` |
| pozvanka | `user.manage`, role jen z te firmy | `src/routes/invites.ts` |
| konektor | `connector.manage` | `src/routes/connectors.ts`, `dashboard.ts` |
| automatizace | `automation.edit` za firmu automatizace | `src/routes/dashboard.ts` |
@@ -237,7 +243,10 @@ Zalozit firmu rucne znamena opsat nazev, IC, DIC a adresu a pak zvlast
zakladat ucty lidem, kteri ji povedou. Proto `Nastaveni`, firma z ARES:
spravce platformy zada IC nebo nazev, vybere firmu a z verejneho rejstriku
dostane **soucasne cleny statutarniho organu a prokuru**. Vybrani dostanou
ucet s roli `role_admin` v nove firme a nahodnym heslem.
ucet s roli `role_admin` v nove firme a nahodnym heslem. Resitel je clenstvi
uctu, ne vlastni zaznam (viz [06-tickety.md](06-tickety.md)), takze jsou
v Lidech hned a jde jim prihodit ticket; funkce z rejstriku je popisek
clenstvi (`role`), kapacita je vychozich 8.
Rejstrik e-maily nezna. Kazda osoba proto dostane zastupnou adresu
`IC-poradi@placeholder.cz`, pokud spravce nezada skutecnou. **Zastupna adresa
+3 -2
View File
@@ -24,8 +24,9 @@ Klic je `${userId}:${tenantId}`, uloziste je `src/data/dashboardLayouts.ts`
dostane vychozi rozlozeni a `custom: false`. Ulozene rozlozeni si drzi
`createdAt` i po uprave.
Vychozi rozlozeni dostava stejne `hasPerson` jako katalog: kdo ve firme neni
veden jako resitel, nema v katalogu `list.myTickets`, a tak ho nesmi mit ani
Vychozi rozlozeni dostava stejne `hasPerson` jako katalog: `hasPerson`
znamena "je clenem firmy" (resitel je clenstvi uctu). Kdo clenstvi nema,
typicky spravce platformy, nema v katalogu `list.myTickets`, a tak ho nesmi mit ani
ve vychozi sade (misto nej jsou nezarazene tickety pres celou sirku). Jinak
by novy ucet videl jako prvni vec hlasku o widgetu, ktery "uz v katalogu neni".
+20
View File
@@ -158,6 +158,26 @@ Dve veci, na kterych to stoji:
souboru znamena dve rozdilna schemata se stejnym cislem. Oprava je vzdy novy
soubor.
### Stara kolekce `person`
Kolekce `person` je **jen pozustatek**: resitel byval vlastni zaznam
(`ppl_...`) spojeny s uctem pres e-mail, dnes je resitel clenstvi uctu
(viz [06-tickety.md](06-tickety.md)) a nic se do ni nezapisuje. Prevod
nedela SQL migrace, ale `src/data/migratePeople.ts` pri startu
z `bootstrapData`, az po nacteni ticketu a automatizaci, a jen kdyz v kolekci
neco je - druhy start uz nic nedela. Plati pro vsechny tri rezimy uloziste.
| Krok |
| ------------------------------------------------------------------------------------------------- |
| ke kazdemu zaznamu se najde ucet podle e-mailu, nebo se zalozi s nahodnym heslem a clenstvim `role_agent` |
| popisek, kapacita a externi ID se prenesou na clenstvi |
| stare ID se prepise na ID uctu v ticketech (`assigneeId`, `resolvedById`), stromech automatizaci, skupinach, telech akci a zdrojich vlastnich widgetu |
| prevedene zaznamy se smazou, do logu jde `[migrace] resitele -> ucty: ...` |
Chyba jednoho zaznamu jen zaloguje, zaznam zustane a migrace se k nemu vrati
pri dalsim startu. Historicke udalosti v logu ticketu si stara ID nechavaji,
neprepisuji se.
## Sifrovani pristupovych udaju
`src/db/secretBox.ts`, AES-256-GCM.
+7 -1
View File
@@ -56,7 +56,13 @@ Volající nikdy nezjišťuje, jestli běží Postgres, soubor, nebo pamět.
| `onTicketEvent(kind, ticket)` | `src/runtime/triggers.ts` | Změna ticketu zařadí navázané automatizace, včetně ochrany proti smyčce. |
| `withRun(marker, work)` | `src/runtime/context.ts` | Označí, který běh práci způsobil. Bez toho automatizace spouští sama sebe. |
| `findBuiltinStep(...)` | `src/runtime/builtinSteps.ts` | Kroky, které sahají do našeho úložiště, ne ven přes HTTP. |
| `findPersonByExternalId(...)` | `src/data/people.ts` | Řešitel podle ID z cizí aplikace, například voicebotId. |
| `personView(user, membership)` | `src/data/people.ts` | Jediné místo, kde z účtu a jednoho členství vzniká pohled `Person`. Řešitel je členství, ID řešitele je ID účtu. |
| `listPeople(tenantIds)`, `listAllPeople(tenantIds)` | `src/data/people.ts` | Řešitelé vybraných firem, jeden záznam na členství; druhá i s vypnutými účty (správa týmu). |
| `findPerson(id, tenantId)` | `src/data/people.ts` | Řešitel v dané firmě, firma je povinná. Kdo v ní není členem, je `undefined`, i když účet existuje. |
| `personName(id)` | `src/data/people.ts` | Jméno účtu bez ohledu na firmu, pro popisky u záznamů, které už prošly filtrem na firmu. |
| `personIdFor(user, tenantId)` | `src/data/people.ts` | ID řešitele, kterým je uživatel ve firmě: ID účtu při členství, jinak `null`. Neptat se `user.id` přímo. |
| `findPersonByExternalId(value, tenantIds)` | `src/data/people.ts` | Řešitel podle ID z cizí aplikace, například voicebotId. Externí ID visí na členství. |
| `migratePeople()` | `src/data/migratePeople.ts` | Jednorázový převod starých záznamů řešitelů (`ppl_`) na účty při startu. Přepisuje odkazy přes `remapPersonIds` v `ticketStore.ts` a `automationStore.ts`. |
| `notify(input)` | `src/data/notifications.ts` | Upozorní člověka. Nečeká se a nevyhazuje chyby, stejně jako audit. |
| `runFlow(steps, context, options)` | `src/runtime/executor.ts` | Vykoná strom kroků. Nikdy nevyhodí výjimku, chyba je výsledek. Používá to akce na ticketu i webhook, aby se strom choval všude stejně. |
| `widgetCatalog(tenantIds, userId)` | `src/data/widgets.ts` | Jediná definice toho, co jde položit na dashboard. Používá ji nabídka i kontrola ukládaného rozložení. |
+15 -1
View File
@@ -51,6 +51,7 @@ byla, ale `viewer` mohl zalozit konektor nebo smazat automatizaci. Ted:
| ---------------------------------------- | -------------------------------------------------------- |
| 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` |
@@ -62,12 +63,25 @@ 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`. Zastupne
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).
+6
View File
@@ -3,6 +3,12 @@
Mechanismus je hotovy a overeny. Verejny web je prelozeny cely, portal za
prihlasenim jen ve spolecnych castech.
**Prepinani je docasne schovane** (rozhodnuti 2026-09-09, do rozhodnuti
o znacce): `MULTILANG_ENABLED = false` v `web/src/i18n/index.ts`. Prepinac
se nekresli, vzdy se pouzije cestina a ulozena volba `en` se ignoruje, aby
nikdo nezustal v anglictine bez moznosti prepnout zpet. Slovniky zustavaji,
zapnuti je zmena jedne konstanty.
## Jak to funguje
| Co | Kde |
+86
View File
@@ -2,6 +2,92 @@
Nejnovejsi nahore.
## 2026-09-09 - Prepinani jazyku docasne schovane
Do rozhodnuti o nove znacce je web jen cesky. `MULTILANG_ENABLED = false`
v `web/src/i18n/index.ts` schova prepinac v hlavicce i v mobilnim menu a
`detect()` vraci vzdy `cs`; ulozena volba `en` se ignoruje, jinak by nekdo
zustal v anglictine bez cesty zpet. Slovniky a `LanguageSwitch` zustavaji,
zapnuti je jedna konstanta. Navrh znacky je zatim jen artefakt "Navrh znacky",
do projektu se neaplikoval.
## 2026-09-09 - Resitel je clenstvi uctu, ne vlastni zaznam
Prvni priznak: osoby zalozene pres ARES dostaly ucet, ale v Lidech nebyl
nikdo, ani ten, za koho se spravce prepnul. Puvodni oprava chtela k uctu
dozakladat zaznam resitele. Pri ni se ukazalo, ze chyba je hloubeji: resitel
byl vlastni zaznam (`ppl_...`) spojeny s uctem **pres e-mail**, a e-mail je
prihlasovaci jmeno, ktere spravce muze zmenit. Zmena adresy vazbu tise
rozbila - clovek prestal videt "moje tickety" a nikdo nevedel proc. Kazde
misto, ktere zaklada ucet (pozvanka, ARES, Nastaveni), navic muselo pamatovat
na druhy zaznam a jedno vzdycky zapomnelo.
Rozhodnuti: spojovat pres ID misto e-mailu by znamenalo dal drzet dva zaznamy
o jednom cloveku. Oba se proto slily: **resitel je clenstvi uctu ve firme**,
kazdy clen muze mit tickety u sebe, ID resitele je ID uctu.
### Model
`Membership` (`src/shared/users.ts`) ma navic `role` (popisek), `capacity`
(vychozi 8) a `externalIds`. `Person` (`src/shared/people.ts`) je jen pohled
na jedno clenstvi: `id` = ID uctu, `tenantId` = firma clenstvi, jmeno
a e-mail z uctu, `enabled` = stav uctu, `roleIds` = role clenstvi. Tentyz
clovek ve dvou firmach je dvakrat se stejnym `id`.
`src/data/people.ts` uz nic neuklada: zmizely `personStore`, `seedPeople`,
`refreshPeople` a `findPersonByEmail`. Pohled sklada jedine misto,
`personView`; k tomu `listPeople(tenantIds)`, `listAllPeople`,
`findPerson(id, tenantId)` (neclen je `undefined`), `personName(id)`,
`findPersonByExternalId(value, tenantIds)` a `personIdFor(user, tenantId)`.
Skupiny (`personGroup`) zustavaji, clenove odkazuji na ID uctu.
### Migrace starych dat
`src/data/migratePeople.ts` bezi pri startu z `bootstrapData`, az po nacteni
ticketu a automatizaci, a jen kdyz ma stara kolekce `person` zaznamy. Ke
kazdemu najde ucet podle e-mailu, nebo ho zalozi (nahodne heslo, `enabled`
podle resitele, clenstvi `role_agent`), prenese popisek, kapacitu a externi
ID na clenstvi a prepise `ppl_x -> usr_y` v ticketech (`assigneeId`,
`resolvedById`), stromech automatizaci (nahrada v JSON), skupinach, telech
akci a zdrojich vlastnich widgetu. Prevedene zaznamy smaze a zaloguje
`[migrace] resitele -> ucty: ...`. Chyba jen loguje, zaznam se zkusi znovu
pri dalsim startu. Historicke udalosti v logu ticketu si stara ID nechavaji.
### API a web
`/api/dashboard/settings/people` ma stejne cesty a pravo `people.manage`,
ale pod nim jsou ucty: `POST` zalozi ucet (heslo nahodne, kdyz chybi, role
`role_agent`) s clenstvim, nebo prida clenstvi uctu s tim e-mailem; `PATCH`
meni jmeno, e-mail (unikatni) a `enabled` na uctu a popisek, kapacitu,
externi ID a role na clenstvi; `DELETE` odebere jen clenstvi a ucet bez
clenstvi bez `platformAdmin` vypne. Udalosti `person.*` nesou `{ id, person }`.
`/users` bere u clenstvi i `seesAllTenant`, `role`, `capacity`, `externalIds`
a pri uprave je zachova. Pozvanka prisla o `asPerson` (prijme se a ignoruje).
ARES zaklada ucet `role_admin` s popiskem clenstvi z funkci v rejstriku.
V portalu spravce v Lidech upravuje cleny (pole vyse, role jako vyber vice
hodnot), `InvitePanel` ztratil prepinac "zalozit i jako resitele" a texty
o "resiteli bez uctu" a "spojce pres e-mail" jsou pryc. `hasPerson` pro
katalog widgetu a vychozi rozlozeni znamena "je clenem firmy".
### Ukazkova data
Ukazkovi resitele jsou ukazkove ucty (heslo `demo1234`): `usr_1`
admin@automia.cz (Vedouci tymu, 5), `usr_2` karel.vomacka@automia.cz
(Automia Servicedesk 8, Nordis Spravce servicedesku 8), `usr_3`
martin.kriz@automia.cz (Integrace a API, 6), `usr_novakova`, `usr_bartos`,
`usr_horakova`, `usr_kadlec`. Ukazkove tickety, automatizace a skupina
`grp_servicedesk` odkazuji na ne.
### Ucet ve vic firmach
Ucet muze byt ve vic firmach, proto se v Lidech odebira jen clenstvi
(`DELETE /settings/people/:id`), ne ucet. Ze stejneho duvodu je i `enabled`
na clenstvi: spravce firmy vypne cloveka u sebe, ne v jine firme, kde
pracuje dal. Cely ucet vypina jen sprava uzivatelu. `Person.enabled` je
`ucet.enabled && clenstvi.enabled !== false`; vypnute clenstvi se nenabizi
k prirazeni ani nehleda podle externiho ID.
## 2026-09-09 - Novy ucet videl na dashboardu chybejici widget
Kdo se prihlasil do nove firmy (nebo jako nove zalozeny ucet), videl jako