Uloziste pro vsechna data, oprava .gitignore, dodelany navrh rozsireni
.gitignore mel vzorec `data/`, ktery se shodl i se `src/data/`. Sestnact zdrojovych souboru tim tise chybelo v gitu vcetne cele slozky `src/data/store/`. Opraveno na `/data/`, stejne v .dockerignore. Tickety vcetne logu, automatizace, incidenty a rozlozeni dashboardu se po kazde zmene ukladaji. Pomocnik `withMirror` je opak `withCache`: data se meni v pameti a zapisuji cela, misto aby se po zapisu znovu nacitala. Citace ID se pri startu dopocitaji z ulozenych zaznamu, takze novy ticket neprepise stary. Detail ticketu umi typ, tagy, vlastni pole typu a prehozeni na skupinu. Nastaveni ma prepnuti spravce na jiny ucet, vychozi jen pro cteni. Skupiny resitelu chodi spolu s lidmi jednim requestem. Dokumentace: rejstrik znovupouzitelnych funkci (15), navrh monetizace a ceny za krok (16), popis nastaveni a prav (17). Doplneny endpointy do openapi.ts, petice CRUD rout se generuje jednou funkci. Overeno v rezimu souboru: zmeny prezily tvrde ukonceni procesu a po restartu byly zpatky vcetne logu ticketu. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
e7cf499a0b
commit
afbe948da3
@@ -33,17 +33,36 @@ React aplikaci ze slozky `dist/public`.
|
||||
| Skripty konektoru | hotovo | manifest, kontrola parametru, hot reload, iDoklad |
|
||||
| Konektory za firmu | hotovo | pristupove udaje v konektoru, overeni napojeni |
|
||||
| Transformace dat | hotovo | pravidla i sablona JSON, kroky si predavaji struktury |
|
||||
| Sprava clenstvi z portalu | chybi | memberships jdou zmenit jen v kodu |
|
||||
| Sprava clenstvi z portalu | hotovo | uzivatele, firmy a role v Nastaveni |
|
||||
| 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 |
|
||||
| Typy ticketu a vlastni pole | hotovo | typ rozhoduje, ktere akce se na ticketu ukazou |
|
||||
| Vydefinovane akce na ticketu | hotovo | vazba na typ nebo tag, telo je operace, strom, skript |
|
||||
| Vlastni widgety | hotovo | zdroj dat z ticketu nebo automatizaci, seskupeni |
|
||||
| Audit a prepnuti na jiny ucet | hotovo | prepnuti je vychozi jen pro cteni, vse v auditu |
|
||||
| Bugs a wishes | chybi | vyvojarska agenda, samostatna evidence vedle ticketu |
|
||||
| Beh automatizaci | chybi | ulozeny strom se nevykonava, neni runtime |
|
||||
| Uloziste konektoru | hotovo | Postgres, nebo JSON soubor. Udaje vzdy sifrovane |
|
||||
| Uloziste pro zbytek | chybi | automatizace, rozlozeni a tickety jsou v pameti |
|
||||
| Uloziste pro zbytek | hotovo | tickety, automatizace, incidenty, rozlozeni, entity |
|
||||
| Monetizace a cena za krok | navrh | popis v 16-monetizace.md, neni naprogramovane |
|
||||
| Odesilani e-mailu z formulare | chybi | poptavka se zatim jen loguje |
|
||||
|
||||
## Znama omezeni
|
||||
|
||||
Data jsou v pameti procesu. Restart containeru vrati tickety, incidenty
|
||||
i automatizace do vychoziho stavu. Nove vytvorene zaznamy se ztrati.
|
||||
Data prezijou restart, ale ne redeploy, kdyz neni databaze. Rezim se pozna
|
||||
v portalu i v `/health/ready` a rozhoduje o nem jedno misto, viz
|
||||
[14-databaze.md](14-databaze.md):
|
||||
|
||||
| Rezim | Kdy | Nasledek |
|
||||
| --- | --- | --- |
|
||||
| `postgres` | je `DATABASE_URL` a migrace prosly | data se neztraci |
|
||||
| `file` | neni databaze, je `DATA_DIR` | prezije restart, ne redeploy |
|
||||
| `memory` | neni ani `DATA_DIR` | ztrati se pri restartu |
|
||||
|
||||
Beh automatizaci zatim neexistuje: ulozeny strom se nevykonava, provoz se dela
|
||||
simulaci. Popis, jak to ma vypadat, je v
|
||||
[10-runtime-a-kapacita.md](10-runtime-a-kapacita.md).
|
||||
|
||||
Obsah verejneho webu je ukazkovy. Nazev firmy, reference, tym i cisla jsou
|
||||
vymyslene a pred ostrym pouzitim se musi nahradit. Firemni udaje jsou na jednom
|
||||
@@ -72,7 +91,30 @@ Prvni cast navrhu uz je hotova: vykonna cast konektoru, tedy skripty
|
||||
s manifestem a kontrolou parametru, viz [11-skripty-konektoru.md](11-skripty-konektoru.md).
|
||||
Runner je pripraveny, chybi nad nim fronta.
|
||||
|
||||
Zbytek navrhu je ve dvou souborech, oba jsou navrh k rozhodnuti, ne popis stavu:
|
||||
[09-navrh-rozsireni.md](09-navrh-rozsireni.md) pro datove modely a prava,
|
||||
[10-runtime-a-kapacita.md](10-runtime-a-kapacita.md) pro frontu, beh kroku
|
||||
a rozpocet na 150 klientu.
|
||||
Datove modely a prava z [09-navrh-rozsireni.md](09-navrh-rozsireni.md) jsou
|
||||
hotove, popis stavu je v [17-nastaveni-a-prava.md](17-nastaveni-a-prava.md).
|
||||
Navrh k rozhodnuti zustava [10-runtime-a-kapacita.md](10-runtime-a-kapacita.md)
|
||||
pro frontu, beh kroku a rozpocet na 150 klientu, a
|
||||
[16-monetizace.md](16-monetizace.md) pro cenu za krok.
|
||||
|
||||
## Dokumentace
|
||||
|
||||
| Soubor | O cem |
|
||||
| --- | --- |
|
||||
| [02-appfactory-proxy.md](02-appfactory-proxy.md) | beh za reverse proxy, ROOT_PATH, health |
|
||||
| [03-architektura-a-mapa-kodu.md](03-architektura-a-mapa-kodu.md) | kde co je |
|
||||
| [04-api.md](04-api.md) | endpointy a to, co ze Swaggeru neni videt |
|
||||
| [05-dashboard-a-builder.md](05-dashboard-a-builder.md) | editor automatizaci |
|
||||
| [06-tickety.md](06-tickety.md) | model ticketu a log prubehu |
|
||||
| [07-firmy-a-prava.md](07-firmy-a-prava.md) | firmy, pohledy, kdo co vidi |
|
||||
| [08-dashboard-widgety.md](08-dashboard-widgety.md) | nastavitelny prehled |
|
||||
| [09-navrh-rozsireni.md](09-navrh-rozsireni.md) | puvodni navrh rozsireni |
|
||||
| [10-runtime-a-kapacita.md](10-runtime-a-kapacita.md) | **navrh**: fronta, beh kroku, kapacita |
|
||||
| [11-skripty-konektoru.md](11-skripty-konektoru.md) | vykonna cast sluzeb |
|
||||
| [12-sluzby-a-konektory.md](12-sluzby-a-konektory.md) | sluzba, konektor, viditelnost |
|
||||
| [13-transformace-dat.md](13-transformace-dat.md) | pole na pole a JSON na JSON |
|
||||
| [14-databaze.md](14-databaze.md) | tri rezimy uloziste, migrace, sifrovani |
|
||||
| [15-rejstrik-funkci.md](15-rejstrik-funkci.md) | k cemu je jaka funkce a komponenta |
|
||||
| [16-monetizace.md](16-monetizace.md) | **navrh**: cena za krok a balicky |
|
||||
| [17-nastaveni-a-prava.md](17-nastaveni-a-prava.md) | prava, typy, akce, widgety, prepnuti uctu |
|
||||
| [99-zmeny.md](99-zmeny.md) | zaznam zmen, nejnovejsi nahore |
|
||||
|
||||
@@ -36,6 +36,11 @@ Vyzaduji `Authorization: Bearer <token>`:
|
||||
| POST | `/api/dashboard/tickets/:id/assign` |
|
||||
| POST | `/api/dashboard/tickets/:id/status` |
|
||||
| POST | `/api/dashboard/tickets/:id/comment` |
|
||||
| POST | `/api/dashboard/tickets/:id/type` |
|
||||
| POST | `/api/dashboard/tickets/:id/tags` |
|
||||
| POST | `/api/dashboard/tickets/:id/group` |
|
||||
| GET | `/api/dashboard/tickets/:id/actions` |
|
||||
| POST | `/api/dashboard/tickets/:id/actions/:actionId` |
|
||||
| GET | `/api/dashboard/incidents` |
|
||||
| GET | `/api/dashboard/storage` |
|
||||
| GET | `/api/dashboard/services` |
|
||||
@@ -58,8 +63,26 @@ Vyzaduji `Authorization: Bearer <token>`:
|
||||
| PUT | `/api/dashboard/automations/:id` |
|
||||
| DELETE | `/api/dashboard/automations/:id` |
|
||||
| POST | `/api/dashboard/automations/:id/webhook/regenerate` |
|
||||
| POST | `/api/dashboard/widget-data` |
|
||||
| GET | `/api/dashboard/widget-data/options` |
|
||||
| GET | `/api/dashboard/settings/catalog` |
|
||||
| GET | `/api/dashboard/settings/features` |
|
||||
| GET | `/api/dashboard/settings/people-overview` |
|
||||
| GET | `/api/dashboard/settings/users-overview` |
|
||||
| PATCH | `/api/dashboard/settings/users/:id/password` |
|
||||
| POST | `/api/admin/impersonate` |
|
||||
| POST | `/api/admin/impersonate/stop` |
|
||||
| GET | `/api/admin/impersonate/candidates` |
|
||||
| GET | `/api/admin/audit` |
|
||||
| POST | `/api/simulate` |
|
||||
|
||||
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`,
|
||||
`widgets`, `features`.
|
||||
|
||||
## Format chyb
|
||||
|
||||
Jednotny pro cele API:
|
||||
@@ -165,6 +188,40 @@ 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.
|
||||
|
||||
## Akce na ticketu
|
||||
|
||||
Popis modelu je v [09-navrh-rozsireni.md](09-navrh-rozsireni.md).
|
||||
|
||||
`GET /api/dashboard/tickets/:id/actions` vraci **jen akce, ktere v teto situaci
|
||||
opravdu jdou spustit**: sedi typ nebo tag, projdou podminky a volajici na ne ma
|
||||
pravo. Klient si nefiltruje nic - jinak by se to pocitalo na dvou mistech
|
||||
a jednou se to rozejde.
|
||||
|
||||
`POST /api/dashboard/tickets/:id/actions/:actionId` vraci **200 i kdyz akce
|
||||
selhala**. Selhani akce neni chyba API. V odpovedi je `ok`, `summary`, `detail`
|
||||
s celym chybovym hlasenim a `durationMs`. Cely prubeh se zapise do logu ticketu.
|
||||
|
||||
Vestavene akce (`type`, `tags`, `group`, `assign`, `status`, `comment`) jsou
|
||||
zvlast: meni ticket sam, ne cizi sluzbu, a kazda ma vlastni pravo.
|
||||
|
||||
## Prava a navigace
|
||||
|
||||
`GET /api/dashboard/access` vraci `permissions` (efektivni prava po slouceni
|
||||
roli), `nav` (zalozky, ktere ma volajici videt) a `platformAdmin`. Klient podle
|
||||
toho kresli, ale **nic si nedovozuje** - kdo co smi, rozhoduje server u kazdeho
|
||||
requestu znovu.
|
||||
|
||||
`GET /api/dashboard/settings/catalog` vraci katalog prav a modulu, aby formular
|
||||
role nemel seznam prav napsany v kodu klienta.
|
||||
|
||||
## Prepnuti na jiny ucet
|
||||
|
||||
`POST /api/admin/impersonate` vraci novy token s narokem `act` (kdo se za koho
|
||||
vydava) a `writes`. Bez `writes` middleware **odmitne cokoliv jineho nez GET**
|
||||
s 403. Kazde prepnuti i ukonceni je v auditu vcetne toho, kdo to byl doopravdy.
|
||||
|
||||
Svuj puvodni token si klient odklada do `sessionStorage`, server o nem nic nevi.
|
||||
|
||||
## Simulace
|
||||
|
||||
`POST /api/simulate` vyvola provozni udalost pro nahled ziveho dashboardu.
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
# Rejstřík znovupoužitelných funkcí a komponent
|
||||
|
||||
K čemu je co, aby se za rok nepsalo znovu něco, co už existuje. Když píšeš
|
||||
druhou funkci, která dělá skoro totéž jako něco odsud, je to skoro vždycky
|
||||
chyba - buď se má použít ta původní, nebo se má rozšířit.
|
||||
|
||||
Podrobný popis je vždycky v komentáři u samotné funkce. Tady je jen jedna věta
|
||||
a kdy to použít.
|
||||
|
||||
## Ukládání dat (server)
|
||||
|
||||
Rozhoduje se na **jednom místě**, viz [14-databaze.md](14-databaze.md).
|
||||
Volající nikdy nezjišťuje, jestli běží Postgres, soubor, nebo pamět.
|
||||
|
||||
| Co | Kde | K čemu |
|
||||
| --- | --- | --- |
|
||||
| `defineStore<T>(kind)` | `src/data/store/index.ts` | Založí úložiště pro nový druh záznamu. Jeden řádek na entitu. |
|
||||
| `initStores({databaseReady})` | `src/data/store/index.ts` | Vybere režim. Volá se jednou při startu, nikde jinde. |
|
||||
| `flushStores()` | `src/data/store/index.ts` | Dopíše rozepsané zápisy. Jen při ukončení procesu. |
|
||||
| `withCache(store)` | `src/data/store/cached.ts` | Kopie v paměti pro **konfigurační** entity, které se čtou při každém requestu (uživatelé, role, firmy). Čte se synchronně, obnovuje se po zápisu. |
|
||||
| `withMirror(store)` | `src/data/store/mirror.ts` | Opačný směr než `withCache`: data se mění v paměti a po každé změně se celý záznam zapíše. Pro **provozní** data (tickety, automatizace, incidenty, rozložení). |
|
||||
| `isVisible(entity, options)` | `src/data/store/types.ts` | Vidí volající tenhle záznam? Prázdný seznam firem znamená "nic", ne "vše". |
|
||||
| `nowIso()` | `src/data/store/types.ts` | Časová značka. Ať se nepíše `new Date().toISOString()` na třiceti místech. |
|
||||
| `memorySnapshot` / `fileSnapshot` | `src/data/snapshot.ts` | Nižší vrstva pod `createLocalStore`: atomický zápis JSONu s debounce. Přímo se nepoužívá. |
|
||||
| `db()`, `query`, `queryOne`, `transaction` | `src/db/pool.ts` | Postgres. `dbFor(tenantId)` je připravený šev pro rozdělení na víc databází. |
|
||||
| `seal`, `open`, `sealAll`, `openAll` | `src/db/secretBox.ts` | Šifrování přístupových údajů konektorů (AES-256-GCM). Nic tajného se neukládá jinak. |
|
||||
| `runMigrations()` | `src/db/migrate.ts` | Migrace pod zámkem, jeden soubor = jedna transakce. |
|
||||
|
||||
## Entity a práva (server)
|
||||
|
||||
| Co | Kde | K čemu |
|
||||
| --- | --- | --- |
|
||||
| `crudRouter(options)` | `src/routes/crud.ts` | Celý CRUD nad jednou entitou: seznam, detail, vytvoření, úprava, mazání, právo, audit. Nová entita v nastavení = jeden `crudRouter`, ne pět handlerů. |
|
||||
| `readScope(req)` | `src/routes/crud.ts` | Ze které firmy smí request číst. Povinný argument všech `list` volání. |
|
||||
| `accessFor(user, tenantId?)` | `src/data/access.ts` | Co uživatel smí: práva, záložky, výchozí firma. Klient si nic nedovozuje sám. |
|
||||
| `permissionsOf(user, tenantId)` | `src/data/permissions.ts` | Efektivní práva z rolí. Pětisekundová cache, `invalidatePermissions()` po zápisu. |
|
||||
| `hasPermission(...)` | `src/data/permissions.ts` | Jedna kontrola. Používá ji `crudRouter` i ruční handlery. |
|
||||
| `navFor(...)` | `src/data/tenantFeatures.ts` | Průnik toho, co firma má, a toho, na co má člověk právo. Navigace chodí ze serveru. |
|
||||
| `recordAudit(input)` | `src/data/audit.ts` | Zápis do auditu. Nevrací chybu a nečeká se - rozbitý audit nesmí rozbít aplikaci. |
|
||||
| `refreshCaches()` | `src/data/bootstrap.ts` | Obnoví všechny kopie v paměti. Volá se po zápisu, který je může změnit. |
|
||||
| `bootstrapData({databaseReady})` | `src/data/bootstrap.ts` | Seznam všech entit a provozních dat. **Nová entita se přidává tady**, ne rozesetě po modulech. |
|
||||
|
||||
## Skripty a konektory (server)
|
||||
|
||||
Viz [11-skripty-konektoru.md](11-skripty-konektoru.md).
|
||||
|
||||
| Co | Kde | K čemu |
|
||||
| --- | --- | --- |
|
||||
| `runScript(id, inputs, ctx)` | `src/scripts/runner.ts` | Spustí skript. **Nikdy nevyhodí výjimku**, chybu vrací jako výsledek s celým hlášením. |
|
||||
| `validateValues(...)` | `src/scripts/values.ts` | Jedna kontrola pro vstupy i výstupy skriptu podle manifestu. |
|
||||
| `scriptUtil` | `src/scripts/util.ts` | Nádobíčko pro skripty: `pick`, `first`, `num`, `date`, `need`, `get`, `applyRules`, `fillJson`. Skript nemá sahat na nic jiného. |
|
||||
| `createRedactor(...)` | `src/scripts/util.ts` | Vyškrtá tajemství z textu **před** logováním. Používá se u všeho, co jde do logu. |
|
||||
| `applyRules`, `fillJson` | `src/scripts/mapping.ts` | Transformace dat: pole na pole s převody, nebo objekt na objekt. Viz [13-transformace-dat.md](13-transformace-dat.md). |
|
||||
| `getPath(obj, path)` | `src/scripts/mapping.ts` | Čtení `zakaznik.adresa.mesto` z neznámého objektu. |
|
||||
| `resolveTarget(...)` | `src/scripts/connections.ts` | Z konektoru poskládá adresu a hlavičky. Přístupové údaje nikam jinam nevedou. |
|
||||
| `createHttp(...)` | `src/scripts/http.ts` | HTTP se timeoutem, limitem odpovědi a rozlišením "zkusit znovu" a "marné". |
|
||||
| `scriptIdFor(serviceId, operationId)` | `src/scripts/lookup.ts` | Který skript obsluhuje operaci z katalogu. |
|
||||
|
||||
## Klient
|
||||
|
||||
| Co | Kde | K čemu |
|
||||
| --- | --- | --- |
|
||||
| `EntityAdmin` | `components/dashboard/EntityAdmin.tsx` | Celá správa jedné entity: tabulka, modál, validace, mazání. Nová záložka nastavení = popis sloupců a polí, ne nová stránka. |
|
||||
| `parseJsonField` | `components/dashboard/EntityAdmin.tsx` | Textové pole s JSONem na hodnotu, s hlášením, kde je chyba. |
|
||||
| `TicketActions` | `components/dashboard/TicketActions.tsx` | CTA akcí na ticketu plus typ, tagy a vlastní pole. Seznam akcí chodí ze serveru už vyfiltrovaný. |
|
||||
| `ErrorDetail` | `components/dashboard/ErrorDetail.tsx` | Rozbalovací celé chybové hlášení s kopírováním. Chyba se nikdy nezkracuje. |
|
||||
| `MappingEditor` | `components/dashboard/flow/MappingEditor.tsx` | Editor transformací v obou režimech (pole na pole, JSON). |
|
||||
| `DataState` | `components/dashboard/DataState.tsx` | Načítání, chyba, prázdno. Ať to každá stránka nekreslí po svém. |
|
||||
| `apiFetch<T>` | `lib/api.ts` | Jediná cesta na API: base path, token, `ApiError` s celým hlášením ze serveru. |
|
||||
| `useApiQuery<T>` | `lib/useApiQuery.ts` | Načtení dat do stránky včetně `reload`. |
|
||||
| `cn(...)` | `lib/cn.ts` | Skládání tříd. Podmíněné třídy nikdy ručně přes šablonu. |
|
||||
| `format*` | `lib/format.ts` | Čísla, procenta, datum, relativní čas, trvání. Formátování se nepíše v komponentě. |
|
||||
| `serviceIcon(key)` | `lib/serviceIcons.ts` | Klíč ikony ze serveru na komponentu. Server neposílá komponenty. |
|
||||
| `usePageMeta` | `lib/usePageMeta.ts` | Titulek stránky. |
|
||||
| `Badge`, `Button`, `Modal`, `Card`, ... | `components/ui/` | Základní prvky. Nový vzhled tlačítka patří sem, ne do stránky. |
|
||||
|
||||
## Pravidla, která z toho plynou
|
||||
|
||||
1. **Nová entita v nastavení**: `defineStore` v modulu entity, řádek v
|
||||
`bootstrap.ts`, `crudRouter` v `settings.ts`, popis v `Settings.tsx`.
|
||||
Nic jiného se psát nemusí.
|
||||
2. **Data, která se mění za provozu**, jdou přes `withMirror`. Data, která se
|
||||
čtou při každém requestu a mění zřídka, přes `withCache`. Obojí nikdy.
|
||||
3. **Chybu se nesmí zkracovat.** Server vrací celé hlášení, klient ho umí
|
||||
zobrazit (`ErrorDetail`).
|
||||
4. **Klient nepočítá práva.** Co smí, říká `accessFor`.
|
||||
@@ -0,0 +1,95 @@
|
||||
# Návrh monetizace a cena za krok
|
||||
|
||||
Stav: **návrh**, není naprogramované. Zbytek dokumentace popisuje hotové věci,
|
||||
tenhle soubor ne - viz [99-zmeny.md](99-zmeny.md).
|
||||
|
||||
Zadání bylo: chci si napsat vlastní automatizaci a hned vidět, kolik by mě
|
||||
měsíčně stála, když si k jednotlivým krokům zadám ceny (například iDoklad,
|
||||
odeslání objednávky, 0,10 Kč).
|
||||
|
||||
## Co se dá účtovat
|
||||
|
||||
Čtyři věci, každá měří něco jiného:
|
||||
|
||||
| Základ | Co to znamená | Proč / proč ne |
|
||||
| --- | --- | --- |
|
||||
| Za uživatele | Kolik lidí má přístup do portálu | Předvídatelné, ale nesouvisí s tím, co aplikace dělá. U automatizací platí zákazník za lidi, kteří tam nemusí chodit. |
|
||||
| Za automatizaci | Kolik má zapnutých stromů | Trestá to rozdělení jednoho velkého stromu na tři přehledné. Špatná motivace. |
|
||||
| **Za krok** | Kolik kroků se skutečně vykonalo | Odpovídá naší práci: každý krok je jedno volání služby, jeden zápis, jeden běh skriptu. Zákazník vidí, za co platí. |
|
||||
| Za objem dat | Kolik toho proteče | Nesouvisí s náklady, u nás jsou to kilobajty. |
|
||||
|
||||
Doporučení: **paušál plus kroky**. Paušál kryje portál, tickety, úložiště
|
||||
a podporu, kroky kryjí provoz automatizací. Bez paušálu je zákazník, který má
|
||||
automatizace vypnuté, zdarma, a přesto mu běží ticketovací systém.
|
||||
|
||||
## Ceník kroků
|
||||
|
||||
Cena patří **k operaci v katalogu**, ne ke službě. Zápis faktury do iDokladu
|
||||
a jeho dotaz na kontakt stojí jinak, protože nás jinak stojí.
|
||||
|
||||
Tři pásma:
|
||||
|
||||
| Pásmo | Příklady | Návrh ceny |
|
||||
| --- | --- | --- |
|
||||
| Obecné | pauza, zápis do logu, podmínka, transformace dat | 0 Kč. Účtovat podmínku je jako účtovat mezeru v textu. |
|
||||
| Naše práce | webhook, plánovač, založení ticketu, přiřazení řešitele, HTTP požadavek | 0,02 Kč |
|
||||
| Cizí služba | iDoklad, Shoptet, SAP, CRM, hlasová brána, AI | 0,05 až 0,50 Kč podle toho, co za to platíme sami |
|
||||
|
||||
Číslo je jedno pole u operace, takže se dá měnit bez zásahu do kódu. Ceník
|
||||
je verzovaný: změna ceny nepřepíše historii, jinak by se zpětně změnila
|
||||
i vyúčtovaná částka.
|
||||
|
||||
## Odhad ceny v editoru
|
||||
|
||||
Co k tomu je potřeba:
|
||||
|
||||
1. **Cena u operace.** Do katalogu (`src/data/services.ts`) přidat
|
||||
`priceCzk` k `ServiceOperation`. Chybějící cena znamená 0, ne chybu -
|
||||
nová operace nesmí rozbít odhad.
|
||||
2. **Očekávaný počet běhů.** Jedno číslo u automatizace, které zadá uživatel
|
||||
("čekám 400 objednávek denně"). Bez něj nejde nic spočítat a hádat to za
|
||||
uživatele by dalo číslo, kterému nebude věřit.
|
||||
3. **Součet přes strom.** Podmínka má dvě větve a projde se vždycky jen jedna.
|
||||
Pro odhad se bere **dražší** větev, aby výsledek byl horní hranice, ne
|
||||
příjemné číslo, které se pak překročí. Vedle se ukáže i levnější varianta.
|
||||
4. **Zobrazení.** V editoru u každého kroku jeho cena, nahoře součet za běh
|
||||
a za měsíc. Jedna funkce, obě čísla, žádný druhý výpočet na klientovi.
|
||||
|
||||
Odhad je záměrně jen odhad: skutečná fakturace se počítá ze **skutečně
|
||||
vykonaných kroků** zapsaných při běhu, ne z toho, co editor předpověděl.
|
||||
|
||||
## Skutečné vyúčtování
|
||||
|
||||
Každý vykonaný krok už teď zapisuje řádek do logu ticketu nebo běhu
|
||||
automatizace. Na fakturaci z toho chybí:
|
||||
|
||||
- u řádku evidovat firmu, operaci a cenu **platnou v tu chvíli**,
|
||||
- denní součet za firmu (aby se faktura nepočítala přes miliony řádků),
|
||||
- měsíční uzávěrka, která součty zamkne.
|
||||
|
||||
Model je stejný jako u auditu: připisovací tabulka, nic se nepřepisuje.
|
||||
Uzavřený měsíc se nedá změnit, jen opravit dobropisem.
|
||||
|
||||
## Balíčky
|
||||
|
||||
Cena za krok samotná zákazníka děsí, protože nezná svoje čísla. Proto balíčky
|
||||
s předplacenými kroky a stejnou cenou nad limit:
|
||||
|
||||
| Balíček | Paušál | Kroků v ceně | Nad limit |
|
||||
| --- | --- | --- | --- |
|
||||
| Start | 490 Kč | 5 000 | 0,05 Kč |
|
||||
| Provoz | 1 900 Kč | 40 000 | 0,04 Kč |
|
||||
| Firma | 6 900 Kč | 200 000 | 0,03 Kč |
|
||||
|
||||
Nad limit se **nevypíná**. Zastavit zákazníkovi fakturaci objednávek kvůli
|
||||
překročení limitu je horší než mu to dofakturovat. Limit hlásí varování
|
||||
v portálu a e-mailem.
|
||||
|
||||
## Co je potřeba rozhodnout
|
||||
|
||||
1. Účtují se kroky, které skončily chybou? Návrh: **ne u naší chyby, ano
|
||||
u chyby cizí služby**, protože volání jsme opravdu zaplatili. Musí to být
|
||||
v ceníku vidět, jinak to vypadá jako počítání chyb ve svůj prospěch.
|
||||
2. Účtuje se opakování po chybě? Návrh: první opakování zdarma, další ano.
|
||||
3. Účtují se kroky v testovacím režimu? Návrh: ne, ale s denním limitem, aby
|
||||
se testem ceník neobcházel.
|
||||
@@ -0,0 +1,128 @@
|
||||
# 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<T>` 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 `settings.ts`, jeden popis v
|
||||
`Settings.tsx`. Nic víc.
|
||||
|
||||
## 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í.
|
||||
|
||||
## 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, obnova 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. |
|
||||
| Audit | přímo do úložiště | Jen se připisuje, nikdy nečte při každém requestu. |
|
||||
| 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ý.
|
||||
@@ -2,6 +2,60 @@
|
||||
|
||||
Nejnovejsi nahore.
|
||||
|
||||
## 2026-08-13 - firmy, prava, typy ticketu, akce, widgety a uloziste pro vsechno
|
||||
|
||||
Dodelany cely [navrh rozsireni](09-navrh-rozsireni.md) a vsechna data se
|
||||
ukladaji. Rejstrik novych funkci a komponent je v
|
||||
[15-rejstrik-funkci.md](15-rejstrik-funkci.md).
|
||||
|
||||
### Pridano - obecne vrstvy
|
||||
|
||||
- `src/data/store/`: jedno rozhrani `EntityStore<T>` a dve implementace, soubor
|
||||
nebo pamet (`local.ts`) a Postgres nad tabulkou `records` (`postgres.ts`).
|
||||
Volajici nepozna, ktera bezi. Nova entita znamena jeden `defineStore` a jeden
|
||||
radek v `bootstrap.ts`.
|
||||
- `store/cached.ts` (`withCache`) pro konfiguracni entity ctene pri kazdem
|
||||
requestu a `store/mirror.ts` (`withMirror`) pro provozni data, ktera se meni
|
||||
v pameti a po zmene se cela zapisuji. Dva pomocniky, ne osm skoro stejnych
|
||||
souboru.
|
||||
- `put` v rozhrani uloziste: zapis celeho zaznamu bez skladani patche.
|
||||
- `src/routes/crud.ts`: fabrika CRUD rout. Seznam, detail, zapis, mazani, pravo
|
||||
a audit na jednom miste.
|
||||
- `components/dashboard/EntityAdmin.tsx`: obecna sprava zaznamu na klientovi.
|
||||
Nova zalozka nastaveni je popis sloupcu a poli, ne nova stranka.
|
||||
|
||||
### Pridano - funkce
|
||||
|
||||
- Firmy, uzivatele, clenstvi, osoby a skupiny resitelu se spravuji z portalu.
|
||||
- Role a prava jsou **data**, ne pevny seznam v kodu: katalog 26 prav, vlastni
|
||||
role za firmu, systemove role nejde menit. Navigace chodi ze serveru jako
|
||||
prunik toho, co firma ma, a toho, na co ma clovek pravo.
|
||||
- Typy ticketu s vlastnimi poli, tagy a vlastni workflow stavu.
|
||||
- Vydefinovane akce na ticketu: vazba na **typ nebo tag**, telo je jedna
|
||||
operace, vlastni strom, nebo skript. Server posila jen akce, ktere v dane
|
||||
situaci projdou podminkami a pravy - klient si nepocita, co ukazat.
|
||||
- Vlastni widgety dashboardu vcetne dat na jeden request a seskupeni.
|
||||
- Audit a prepnuti spravce na jiny ucet. Prepnuti je **vychozi jen pro cteni**,
|
||||
zapis se musi zapnout vedome a je videt v auditu.
|
||||
- Detail ticketu: CTA akci, typ, tagy, vlastni pole a prehozeni na skupinu.
|
||||
|
||||
### Pridano - uloziste pro provozni data
|
||||
|
||||
Tickety vcetne logu, automatizace, incidenty a rozlozeni dashboardu se po kazde
|
||||
zmene zapisuji. Citace ID se pri startu dopocitaji z ulozenych zaznamu, takze
|
||||
novy ticket nikdy neprepise stary.
|
||||
|
||||
### Overeno
|
||||
|
||||
V rezimu `file`: zmena typu, tagu, vlastnich poli, stavu, komentare, nova
|
||||
automatizace, novy incident a upravene rozlozeni dashboardu prezily **tvrde
|
||||
ukonceni procesu** a po restartu byly zpatky vcetne logu ticketu. Novy ticket
|
||||
dostal dalsi cislo, ne cislo existujiciho. Agent bez prav dostane 403 na spravu
|
||||
roli a uzsi navigaci. `passwordHash` se nikdy nevraci.
|
||||
|
||||
Databaze se v tomhle kole neoverovala, nebylo na cem - kod pro ni je stejny
|
||||
a psany soucasne, ale nebezel.
|
||||
|
||||
## 2026-08-12 - soubor jako uloziste bez databaze
|
||||
|
||||
Mockup se k databazi nedostane, takze pribyl treti rezim: JSON soubor.
|
||||
|
||||
Reference in New Issue
Block a user