Files
csbot-prototype/scripts/idoklad.find-issued-invoice.js
T
JiriUhlirandClaude Opus 5 6f6b287d7e Skripty konektoru: vykonna cast s manifestem a kontrolou parametru
Konektory dostaly vykonnou cast. Jeden skript je jeden soubor, ktery nese
manifest (vstupni a vystupni parametry) i kod. Diky manifestu s nim umi
pracovat strom automatizace, aniz by o kodu cokoliv vedel.

Soubory jsou zamerne obycejny JavaScript, ne TypeScript. TypeScript by se
musel prelozit a to je presne to otaceni, ktere tady nema byt. Registr
sleduje cas zmeny souboru, takze uprava v portalu, rucni uprava souboru
i novy soubor ve slozce funguji stejne a bez restartu.

Pridano:
- scripts/ se skripty konektoru, nazev souboru je zaroven ID operace
- kontrola vstupu i vystupu proti manifestu, jedna funkce pro obe strany.
  Chybejici povinny vystup je chyba skriptu, ne uzivatele - jinak by strom
  veril parametru, ktery nikdy nedosel
- ctx predavany skriptu: http nad adresou napojeni, util, log, config,
  idempotencyKey, fail a retry. Skript nedostane pristupove udaje
- rozliseni opakovatelne a koncove chyby. Runner nikdy nevyhodi vyjimku,
  vzdy vraci vysledek vcetne retryable
- redakce tajnych hodnot pred zapisem do logu. Cizi API rado vraci prijaty
  token v chybove zprave a log ticketu vidi klient
- napojeni z environment variables vcetne iDokladu
- sest ukazkovych skriptu pro iDoklad proti skutecnemu API sluzby
  services.csbot.cz/apps/idoklad, kazdy na jiny vzor
- stranka /dashboard/skripty: seznam, manifest, editor, zkusebni spusteni.
  Formular testu se sklada z manifestu, nepise se pro kazdy skript
- endpointy /api/dashboard/scripts vcetne Swaggeru

Zmeneno:
- katalog konektoru uz neni jen staticky seznam. Akce ze skriptu se domeruji
  prekryvem v src/data/connectors.ts, takze se naraz objevi ve validaci
  stromu, ve vypoctu scope i v sablonach. Pri stejnem ID vyhrava skript
- ConnectorOperation ma implementation a scriptId
- ApiError na klientovi nese cele telo odpovedi a umi z nej vytahnout issues
- Dockerfile kopiruje scripts/ do vysledneho image

Ukladani nemuze rozbit fungujici skript: kod se nejdriv zapise do docasneho
souboru, ten se nacte a overi, a az pak prepise puvodni.

K tomu tri dokumenty navrhu dalsich kroku: 09 datove modely a prava,
10 runtime a rozpocet na 150 klientu, 11 popis skriptu konektoru.

Overeno: npm run typecheck prochazi na serveru i webu.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-12 13:37:58 +02:00

91 lines
2.8 KiB
JavaScript

/**
* iDoklad: dohledani vydane faktury podle cisla dokladu.
*
* Sluzba: https://services.csbot.cz/apps/idoklad
* Endpoint: GET /issued-invoices?filter=(DocumentNumber~eq~2024001)
*
* Tohle je vzor **predvalidace**: skript nic nemeni, jen odpovi, jestli doklad
* existuje. Vystup `found` je pak to, na co se ve strome vetvi podminka.
* Stejny princip jako akce "Dohledat firmu" u CRM, viz documentation/06-tickety.md.
*
* Proto taky nenalezena faktura NENI chyba. Kdyby skript spadl, nesla by
* postavit vetev "doklad neznam, zaloz ho".
*/
export const manifest = {
id: 'idoklad.find-issued-invoice',
name: 'Najít vydanou fakturu',
description:
'Zjistí, jestli v iDokladu existuje vydaná faktura s daným číslem dokladu. ' +
'Nic nezakládá ani nemění. Podle výsledku se strom větví.',
inputs: [
{
id: 'documentNumber',
label: 'Číslo dokladu',
type: 'string',
required: true,
hint: 'Číslo, jak je na faktuře, například 2024001.',
},
],
outputs: [
{ id: 'found', label: 'Faktura nalezena', type: 'boolean', required: true },
{ id: 'invoiceId', label: 'ID faktury', type: 'number', required: false },
{ id: 'totalWithVat', label: 'Celkem s DPH', type: 'number', required: false },
{ id: 'dateOfMaturity', label: 'Datum splatnosti', type: 'date', required: false },
{ id: 'isPaid', label: 'Je uhrazená', type: 'boolean', required: false },
{
id: 'ambiguous',
label: 'Odpovídá víc faktur',
type: 'boolean',
required: true,
},
],
};
export async function run(inputs, ctx) {
const { unwrap, pick, num, bool, date } = ctx.util;
const { body } = await ctx.http.get('/issued-invoices', {
query: {
// Tvar filtru je dany iDokladem: (Pole~operator~hodnota)
filter: `(DocumentNumber~eq~${inputs.documentNumber})`,
filtertype: 'and',
// Dva staci: jeden na vysledek, druhy na zjisteni, ze neni jednoznacny.
pageSize: 2,
},
});
const items = unwrap(body);
const list = Array.isArray(items) ? items : [];
if (list.length === 0) {
ctx.log(`Faktura ${inputs.documentNumber} v iDokladu není.`);
return {
found: false,
invoiceId: null,
totalWithVat: null,
dateOfMaturity: null,
isPaid: null,
ambiguous: false,
};
}
if (list.length > 1) {
// Neni to chyba, ale nekdo to ma vedet - cislo dokladu ma byt jednoznacne.
ctx.log(`Číslu dokladu ${inputs.documentNumber} odpovídá víc faktur, beru první.`);
}
const invoice = list[0];
return {
found: true,
invoiceId: num(pick(invoice, 'id')),
totalWithVat: num(pick(invoice, 'totalWithVat', 'totalWithVatHc', 'total')),
dateOfMaturity: date(pick(invoice, 'dateOfMaturity')),
isPaid: bool(pick(invoice, 'isPaid')),
ambiguous: list.length > 1,
};
}