Files
csbot-prototype/scripts/idoklad.create-invoice-from-object.js
T
JiriUhlirandClaude Opus 5 ad56c7f513 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>
2026-08-12 14:32:39 +02:00

89 lines
3.3 KiB
JavaScript

/**
* iDoklad: vystaveni faktury z hotoveho objektu.
*
* Sluzba: https://services.csbot.cz/apps/idoklad
* Endpointy: GET /issued-invoices/default, POST /issued-invoices
*
* Druha polovina prikladu z transformace: prijde objednavka z e-shopu,
* `transform.map-fields` z ni udela telo dokladu a tenhle krok ho odesle.
*
* Rozdil proti `idoklad.create-issued-invoice`: ta akce ma pole na kliknuti
* a umi jednu polozku. Tahle bere cely objekt, takze zvladne vic polozek
* a cokoliv dalsiho, co iDoklad prijima. Cenou je, ze tvar tela musi znat ten,
* kdo stavi transformaci.
*
* Vzor z `/default` se poradi i tady: nese povinna pole, ktera nikdo vyplnovat
* nechce (`documentSerialNumber`, `isEet`, `isIncomeTax`). Objekt z transformace
* ho jen prepise tam, kde neco rika.
*/
export const manifest = {
id: 'idoklad.create-invoice-from-object',
name: 'Vystavit fakturu z objektu',
description:
'Odešle do iDokladu připravené tělo dokladu. Chybějící povinná pole se ' +
'doplní z předvyplněného vzoru iDokladu.',
timeoutMs: 25000,
inputs: [
{
id: 'payload',
label: 'Tělo dokladu',
type: 'object',
required: true,
hint: 'Objekt z transformace dat. Klíče podle iDokladu, například partnerId, items.',
},
{
id: 'requireItems',
label: 'Vyžadovat aspoň jednu položku',
type: 'boolean',
required: false,
default: true,
hint: 'Doklad bez položek iDoklad přijme, ale bude na nulu.',
},
],
outputs: [
{ id: 'invoiceId', label: 'ID faktury', type: 'number', required: true },
{ id: 'documentNumber', label: 'Číslo dokladu', type: 'string', required: true },
{ id: 'totalWithVat', label: 'Celkem s DPH', type: 'number', required: false },
{ id: 'itemCount', label: 'Počet položek', type: 'number', required: true },
{ id: 'invoice', label: 'Celý doklad', type: 'object', required: true },
],
};
export async function run(inputs, ctx) {
const { unwrap, pick, text, num, need } = ctx.util;
const payload = inputs.payload;
const items = Array.isArray(payload.items) ? payload.items : [];
if ((inputs.requireItems ?? true) && items.length === 0) {
ctx.fail('Tělo dokladu neobsahuje žádné položky. Zkontrolujte pravidla transformace.');
}
if (num(pick(payload, 'partnerId')) === null) {
ctx.fail('Tělo dokladu neobsahuje partnerId, iDoklad by doklad nepřijal.');
}
// Vzor nese povinna pole, ktera z objednavky nikdy neprijdou.
const defaults = unwrap((await ctx.http.get('/issued-invoices/default')).body);
if (!defaults || typeof defaults !== 'object') {
ctx.retry('iDoklad nevrátil předvyplněný vzor faktury.');
}
const body = { ...defaults, ...payload };
const response = await ctx.http.post('/issued-invoices', body);
const invoice = unwrap(response.body);
ctx.log(`Doklad vystaven z objektu, položek: ${items.length}.`);
return {
invoiceId: need(num(pick(invoice, 'id')), 'ID vystavené faktury'),
documentNumber: need(text(pick(invoice, 'documentNumber', 'number')), 'číslo dokladu'),
totalWithVat: num(pick(invoice, 'totalWithVat', 'totalWithVatHc', 'total')),
itemCount: items.length,
// Cely doklad dal, aby na nej sel navazat dalsi krok bez druheho volani.
invoice: invoice && typeof invoice === 'object' ? invoice : {},
};
}