Transformace dat, oprava ukladani udaju konektoru

Transformace dat ve dvou rezimech plus oprava chyby, kvuli ktere se neukladaly
pristupove udaje konektoru. Popis v documentation/13-transformace-dat.md.

Kroky si predavaji i cele struktury:
- FieldType ma object a list. Do sablony se nedosazuji, predavaji se jako celek
  dalsimu kroku - proto je u nich v builderu vyber a ne textove pole. Z podminek
  nad nimi ma smysl jen "prisla / neprisla"
- strop na velikost struktury (SCRIPT_MAX_VALUE_BYTES, vychozi 256 kB). Radek
  s vystupem kroku je nejrychleji rostouci tabulka v systemu

Dva rezimy transformace, oba nad enginem v src/scripts/mapping.ts:
- transform.map-fields: pole na pole s prevody, klikatelne
- transform.to-json: sablona cileveho objektu s ${cesta}

Marker ${...} je zamerne jiny nez {{...}}. Sablony kroku se dosazuji driv, nez
krok bezi, takze {{total}} by strom stihl vyhodnotit, nenasel by parametr toho
jmena a dosadil by prazdno. Cely retezec navic zachova typ, takze
"unitPrice": "${total}" vyrobi cislo - jinak by cizi sluzba dostala castku jako
text a odmitla ji.

Prevod map pro seznamy je to, bez ceho by priklad nesel dokoncit. Bez nej jde
prevest hlavicku dokladu, ale ne polozky objednavky, a doklad by byl na nulu.

Dal pridano:
- idoklad.create-invoice-from-object: druha polovina prikladu, bere hotove telo
  dokladu z transformace a doplni povinna pole ze vzoru iDokladu
- spoustec e-shopu predava celou objednavku jako objekt a polozky jako seznam
- klikaci editor pravidel vcetne rezimu JSON pro vnorena pravidla u map
- kontrola JSONu a tvaru pravidel uz pri ulozeni stromu. Preklep je nedodelek,
  ne chyba ukladani - rozdelana prace se nezahazuje

Opraveno: konektor neukladal pristupove udaje. Server byl v poradku, overeno
volanim POST i PATCH. Chyba byla v prohlizeci: u pole type="password" prohlizec
ignoruje autocomplete="off" a dosazuje ulozene prihlaseni. Uzivatel pak videl
jednu hodnotu, React drzel jinou, a ulozilo se to, co drzel React, tedy nic.
Resi to autocomplete="new-password", jmena poli, ktera nepripominaji heslo,
a prepinac zobrazeni, aby slo overit, co je opravdu zapsane.

Zakladani a uprava konektoru se presunuly do dialogu, na strance jsou jen male
karty. Formulare rozlozene po strance byly u vic konektoru neprehledne.

Overeno: npm run typecheck prochazi na serveru i webu, node --check na skriptech.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
JiriUhlir
2026-08-12 14:32:39 +02:00
co-authored by Claude Opus 5
parent 8ad91a6c28
commit ad56c7f513
20 changed files with 2063 additions and 297 deletions
+1
View File
@@ -32,6 +32,7 @@ React aplikaci ze slozky `dist/public`.
| Nastavitelny dashboard | hotovo | widgety, sirky a poradi, ulozene za uzivatele a firmu |
| 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 |
| Bugs a wishes | chybi | vyvojarska agenda, samostatna evidence vedle ticketu |
| Beh automatizaci | chybi | ulozeny strom se nevykonava, neni runtime |
+203
View File
@@ -0,0 +1,203 @@
# 13 - Transformace dat
Naprogramovano. Souvisi se skripty ([11](11-skripty-konektoru.md))
a se sluzbami ([12](12-sluzby-a-konektory.md)).
## K cemu to je
Prijde objednavka z e-shopu a do iDokladu je potreba uplne jiny objekt. Jmena
poli nesedi, castky jsou textem, polozky maji jinou strukturu. Mezi tim musi byt
krok, ktery rekne, co se kam prepise a jak se to po ceste prevede.
```
Nova objednavka (e-shop) -> objekt "order"
Transformace dat -> objekt "result" ve tvaru dokladu
Vystavit fakturu z objektu -> iDoklad
```
## Kroky si predavaji i cele struktury
Dosud si kroky predavaly jen jednotlive hodnoty. Pribyly dva typy parametru:
| Typ | Co to je | Do sablony | Podminka |
| -------- | --------------------------- | ---------- | ----------------- |
| `object` | cely objekt | ne | je / neni prazdny |
| `list` | seznam | ne | je / neni prazdny |
**Do sablony se nedosazuji.** `{{order}}` v textu by znamenalo vlozit do vety
kus JSONu, coz nikdo nechce. Struktura se predava **jako celek** dalsimu kroku,
proto je u ni v builderu vyber a ne textove pole.
Nad strukturou ma z podminek smysl jen "prisla / neprisla". Porovnavat dva
objekty by znamenalo urcit, co je "stejny", a to zavisi na pripadu.
**Strop na velikost.** Struktura nad `SCRIPT_MAX_VALUE_BYTES` (vychozi 256 kB)
neprojde kontrolou. Radek s vystupem kroku je nejrychleji rostouci tabulka
v systemu (viz [10-runtime-a-kapacita.md](10-runtime-a-kapacita.md)), takze
hranice patri do kontroly parametru, ne az do uklidu databaze.
## Dva rezimy
Obe moznosti stoji na tom samem enginu v `src/scripts/mapping.ts`.
Volba je o tom, cehoz je vic:
| Rezim | Kdy | Skript |
| ------------------------- | -------------------------------------- | ----------------------- |
| **Pravidla** (pole na pole) | hlavni prace je v prevodech hodnot | `transform.map-fields` |
| **Sablona JSON** | hlavni prace je ve tvaru struktury | `transform.to-json` |
### Rezim 1: pravidla
Jedno pravidlo je "vezmi tuhle cestu, projed prevody, uloz sem".
```json
[
{ "to": "partnerId", "from": "customer.idokladId", "required": true },
{ "to": "description", "from": "number", "transforms": [{ "op": "string" }] },
{ "to": "dateOfIssue", "from": "createdAt", "transforms": [{ "op": "date", "format": "day" }] },
{ "to": "variableSymbol", "from": "number", "omitIfEmpty": true },
{
"to": "items",
"from": "items",
"transforms": [
{
"op": "map",
"rules": [
{ "to": "name", "from": "title", "required": true },
{ "to": "amount", "from": "quantity", "transforms": [{ "op": "number" }] },
{ "to": "unitPrice", "from": "priceWithoutVat", "transforms": [{ "op": "number" }, { "op": "round", "decimals": 2 }] },
{ "to": "unit", "value": "ks" },
{ "to": "priceType", "value": 0 },
{ "to": "vatRateType", "value": 0 },
{ "to": "discountPercentage", "value": 0 },
{ "to": "isTaxMovement", "value": false }
]
}
]
}
]
```
Prevod `map` je to, bez ceho by priklad nesel dokoncit. Bez nej by slo prevest
hlavicku dokladu, ale ne seznam polozek, a doklad by byl na nulu.
| Klic | K cemu |
| ------------- | --------------------------------------------------------- |
| `to` | kam se ulozi. Tecka znamena zanoreni: `partner.id` |
| `from` | cesta ve zdroji. `items.0.name` i `items[0].name` |
| `value` | pevna hodnota, kdyz `from` chybi |
| `transforms` | prevody v uvedenem poradi |
| `fallback` | pouzije se, kdyz je vysledek prazdny |
| `omitIfEmpty` | prazdny vysledek se do vystupu vubec nezapise |
| `required` | prazdny vysledek je chyba |
`required` neni formalita. Doklad bez `partnerId` iDoklad odmitne, a je lepsi
to poznat na kroku transformace s nazvem pole, nez z odpovedi 400 od iDokladu.
### Rezim 2: sablona JSON
Napise se cely cilovy objekt a na mistech hodnot se odkaze pres `${cesta}`:
```json
{
"partnerId": "${customer.idokladId}",
"description": "Objednávka ${number}",
"dateOfIssue": "${createdAt}",
"items": [{ "name": "Zboží dle objednávky", "unitPrice": "${total}", "amount": 1 }]
}
```
Dve veci, ktere v tom rezimu rozhoduji:
**Marker je `${...}`, ne `{{...}}`.** Sablony kroku se dosazuji driv, nez se
krok spusti. `{{total}}` by tedy stihl vyhodnotit strom, zadny parametr toho
jmena by nenasel a dosadil by prazdno. Rozdilny marker to nemuze splest.
**Cely retezec zachova typ.** `"unitPrice": "${total}"` vyrobi **cislo**, ne
text. Bez toho by cizi sluzba dostala castku jako retezec a vetsina jich to
odmitne. Uvnitř delsiho textu (`"Objednávka ${number}"`) se naopak vklada
jako text, protoze jinak to nedava smysl.
## Prevody
| Prevod | Co dela |
| ----------------------------- | ---------------------------------------- |
| `trim`, `lower`, `upper` | uprava textu |
| `string`, `number`, `boolean` | zmena typu |
| `date` (`iso` / `day`) | datum, `day` je jen `YYYY-MM-DD` |
| `round` (`decimals`) | zaokrouhleni |
| `multiply`, `add` (`by`) | pocty, napriklad prevod na cenu s DPH |
| `default` (`value`) | vyplneni prazdne hodnoty |
| `replace` (`find`, `with`) | nahrazeni textu |
| `slice` (`start`, `end`) | cast textu nebo seznamu |
| `split`, `join` (`separator`) | text na seznam a zpatky |
| `sum` (`path`) | soucet pres seznam |
| `count` | pocet polozek |
| `map` (`rules`) | kazdou polozku seznamu podle vlastnich pravidel |
Sada je zamerne **uzavrena**. Volny vyraz by z transformace udelal dalsi jazyk
k ladeni a hlavne by to byl kod bez sandboxu na miste, kde ho nikdo neceka.
Kdo potrebuje vic, napise skript.
## Kontrola
| Kdy | Co se overi | Vysledek |
| ----------------- | ---------------------------------------------------- | --------- |
| Pri psani | JSON se parsuje, chyba se ukaze hned pod polem | jen v UI |
| Pri ulozeni stromu| JSON a tvar pravidel (`to`, `from`/`value`, `op`) | nedodelek |
| Pri behu | typy, `required`, prazdne hodnoty, hloubka zanoreni | chyba behu|
Rozbite pravidlo je **nedodelek**, ne chyba ukladani. Rozdelana prace se
nezahazuje, jen automatizace nepujde zapnout. Stejny rezim jako u ostatnich
nedodelku, viz [05-dashboard-a-builder.md](05-dashboard-a-builder.md).
Neznamy prevod je pri behu chyba, ne preskoceni. Tise ho ignorovat by znamenalo
odeslat neprevedena data a divit se az u odberatele.
## Kde to je
```
src/scripts/mapping.ts engine: cesty, prevody, pravidla, sablona
scripts/transform.map-fields.js rezim 1
scripts/transform.to-json.js rezim 2
scripts/idoklad.create-invoice-from-object.js druha polovina prikladu
web/src/components/dashboard/flow/MappingEditor.tsx klikaci editor pravidel
web/src/components/dashboard/flow/StepInputs.tsx pole typu mapping, json a object
```
Engine je na serveru a skripty ho dostanou na `ctx.util` jako `get`, `applyRules`
a `fillJson`. Nemuze byt v souboru skriptu, protoze skripty nic neimportuji -
a nema tam byt, protoze by ho jinak mel kazdy skript ve vlastni verzi.
## Cely priklad
```
Spoustec: Nova objednavka (e-shop)
vystupy: orderNumber, orderTotal, customerEmail, order (objekt), items (seznam)
Krok 1: Premapovat pole
Zdrojova data: order
Pravidla: viz vyse
vystup: result (objekt), fieldCount
Krok 2: Vystavit fakturu z objektu (iDoklad)
Telo dokladu: result
vystupy: invoiceId, documentNumber, totalWithVat, itemCount, invoice
Krok 3: Odeslat fakturu e-mailem (iDoklad)
ID faktury: {{invoiceId}}
E-mail: {{customerEmail}}
```
Krok 2 doplni z `GET /issued-invoices/default` povinna pole, ktera z objednavky
nikdy neprijdou (`documentSerialNumber`, `isEet`, `isIncomeTax`). Objekt
z transformace ho prepise jen tam, kde neco rika.
## Co chybi
| Chybi | Poznamka |
| --------------------------- | ------------------------------------------------------- |
| Nahled transformace | pravidla jde zkusit jen pres test skriptu |
| Vnorena pravidla klikanim | prevod `map` se upravuje v rezimu JSON |
| Napoveda cest ze skutecnych dat | nabizeji se jmena parametru, ne cesty uvnitr objektu |
| Vykonavani ze stromu | runner je hotovy, runtime automatizaci ne |
+38
View File
@@ -2,6 +2,44 @@
Nejnovejsi nahore.
## 2026-08-12 - transformace dat a oprava konektoru
Transformace dat popsana v [13-transformace-dat.md](13-transformace-dat.md).
### Pridano
- Kroky si predavaji i cele struktury. `FieldType` ma `object` a `list`.
Do sablony se nedosazuji, predavaji se jako celek dalsimu kroku - proto je
u nich v builderu vyber a ne textove pole. Z podminek nad nimi ma smysl jen
"prisla / neprisla".
- Strop na velikost struktury (`SCRIPT_MAX_VALUE_BYTES`, vychozi 256 kB).
Radek s vystupem kroku je nejrychleji rostouci tabulka v systemu.
- `src/scripts/mapping.ts`: cesty, prevody, pravidla a sablona JSON.
Skripty ho dostanou na `ctx.util` jako `get`, `applyRules` a `fillJson`.
- Dva rezimy transformace: `transform.map-fields` (pole na pole s prevody)
a `transform.to-json` (sablona cileveho objektu s `${cesta}`).
V sablone je marker `${...}` zamerne jiny nez `{{...}}`: sablony kroku se
dosazuji driv, nez krok bezi, a stihly by ji prepsat.
- Prevod `map` pro seznamy. Bez nej by slo prevest hlavicku dokladu, ale ne
polozky objednavky, a doklad by byl na nulu.
- `idoklad.create-invoice-from-object`: druha polovina prikladu, bere hotove
telo dokladu z transformace.
- Spoustec e-shopu predava celou objednavku jako objekt a polozky jako seznam.
- Klikaci editor pravidel v builderu vcetne rezimu JSON pro vnorena pravidla.
- Kontrola JSONu a tvaru pravidel uz pri ulozeni stromu. Preklep je nedodelek,
ne chyba ukladani - rozdelana prace se nezahazuje.
### Opraveno
- **Konektor neukladal pristupove udaje.** Server byl v poradku, chyba byla
v prohlizeci: u pole `type="password"` prohlizec ignoruje `autocomplete="off"`
a dosazuje ulozene prihlaseni. Uzivatel pak videl jednu hodnotu, React drzel
jinou, a ulozilo se to, co drzel React, tedy nic. Resi to `autocomplete`
`new-password`, jmena poli, ktera nepripominaji heslo, a prepinac zobrazeni,
aby slo overit, co je opravdu zapsane.
- Zakladani a uprava konektoru se presunuly do dialogu. Na strance jsou jen
male karty - formulare rozlozene po strance byly u vic konektoru neprehledne.
## 2026-08-12 - sluzby a konektory
Rozdeleni na sluzbu a konektor. Popis