Files
csbot-prototype/scripts/idoklad.create-issued-invoice.js
T
JiriUhlirandClaude Fable 5.1 104ae36783 Revize projektu: prava, vykon, runtime, portal a ARES
Prava a bezpecnost: spravce firmy uz nemuze nastavit priznak spravce
platformy ani clenstvi v cizi firme; pozvanky, konektory a automatizace
kontroluji sve pravo; cizi firma v query je 404; zivy stream posila
udalosti jen firmam, kterych se tykaji; akce nad ticketem maji kontrolu
prava za firmu ticketu a strop viditelnosti; tokeny se nelogujou; limit
pokusu na prihlaseni, kontakt a pozvanky; bezpecnostni hlavicky;
zachyceni chyb v async handlerech; timing-safe porovnani tokenu.

Vykon: audit neskenuje celou kolekci pri kazdem zapisu a konecne maze
firemni zaznamy; ticket se uklada jednou misto trikrat; zapisy do
Postgresu jsou serializovane podle ID; prava se pocitaji jednou na
request; widgety nacitaji tickety jednou; strankovani seznamu; worker
je pool misto kol; na webu udalost ze streamu neodmontuje stranku,
dotazy maji spolecny debounce a cache, ciselniky drzi typovany sklad.

Runtime: opakuji se jen chyby oznacene retryable; smycka nenarazi na
strop 50 kroku (novy strop 1000 akci); podminka nad datem funguje;
vystup MCP nastroje neprepisuje spoustec; sandbox skriptu firmy nejde
opustit; MCP session id se drzi mezi volanimi; incident z kroku patri
firme; jedno rozhodnuti o rezimu uloziste; snapshot neprepise soubor
po chybe cteni.

Refaktory: sdilene typy API v src/shared (web nic nekopiruje, osm
rozjetych tvaru sjednoceno); spolecny modul net/guard pro volani ven;
formularova vrstva ui/form; rozdeleni Connectors a FlowCanvas; jeden
helper pro firmu z query, validaci a CRUD udalosti; pomucky ctx.util
pro skripty konektoru; i18n verejneho webu vcetne anglictiny.

Nova funkce: zalozeni firmy z registru ARES v Nastaveni (IC nebo nazev,
dotazeni IC, DIC, sidla a pravni formy, vyber soucasnych statutarnich
zastupcu a prokury, ucty spravce firmy s nahradnim e-mailem
IC-poradi@placeholder.cz).

Dokumentace: zaznam v 99-zmeny.md a aktualizace 15 dalsich dokumentu.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-09 10:26:07 +02:00

152 lines
5.1 KiB
JavaScript

/**
* iDoklad: vystaveni vydane faktury s jednou polozkou.
*
* Sluzba: https://services.csbot.cz/apps/idoklad
* Endpointy: GET /issued-invoices/default, POST /issued-invoices
*
* Vzor **dvou volani za sebou**. iDoklad u faktury vyzaduje pole, ktera nikdo
* rucne vyplnovat nechce (`documentSerialNumber`, `isEet`, `isIncomeTax`),
* takze se nejdriv vezme predvyplneny vzor z `/issued-invoices/default`
* a prepisou se v nem jen ty veci, ktere prisly ze stromu.
*
* Kdyby se telo skladalo od nuly, rozbila by ho kazda zmena povinnych poli
* na strane iDokladu. Takhle se prepisuje jen to, cemu rozumime.
*/
export const manifest = {
id: 'idoklad.create-issued-invoice',
name: 'Vystavit vydanou fakturu',
description:
'Vystaví v iDokladu vydanou fakturu s jednou položkou. Chybějící údaje ' +
'se doplní z předvyplněného vzoru iDokladu.',
timeoutMs: 25000,
inputs: [
{
id: 'partnerId',
label: 'ID odběratele v iDokladu',
type: 'number',
required: true,
hint: 'Umí ho dohledat akce Najít kontakt.',
},
{
id: 'description',
label: 'Popis dokladu',
type: 'string',
required: true,
hint: 'Text v hlavičce faktury, například Objednávka {{orderNumber}}.',
},
{ id: 'itemName', label: 'Název položky', type: 'string', required: true },
{
id: 'unitPrice',
label: 'Cena za jednotku',
type: 'number',
required: true,
hint: 'V měně dokladu. Desetinná čárka i tečka projdou.',
},
{ id: 'amount', label: 'Počet jednotek', type: 'number', required: false, default: 1 },
{ id: 'unit', label: 'Jednotka', type: 'string', required: false, default: 'ks' },
{
id: 'dateOfIssue',
label: 'Datum vystavení',
type: 'date',
required: false,
hint: 'Nevyplněno = dnes.',
},
{
id: 'maturityDays',
label: 'Splatnost ve dnech',
type: 'number',
required: false,
default: 14,
},
{ id: 'variableSymbol', label: 'Variabilní symbol', type: 'string', required: false },
{ id: 'note', label: 'Poznámka', type: 'string', required: false, multiline: true },
{
id: 'vatRateType',
label: 'Kód sazby DPH',
type: 'number',
required: false,
default: 0,
hint: 'Číselný kód VatRateType z iDokladu. Když nevíte, nechte 0.',
},
{
id: 'priceType',
label: 'Kód typu ceny',
type: 'number',
required: false,
default: 0,
hint: 'Číselný kód PriceType z iDokladu (cena s DPH nebo bez). Když nevíte, nechte 0.',
},
],
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: 'dateOfMaturity', label: 'Datum splatnosti', type: 'date', required: true },
],
};
function addDays(value, days) {
const date = new Date(value);
date.setUTCDate(date.getUTCDate() + days);
return date;
}
export async function run(inputs, ctx) {
// `day` dava datum ve tvaru, ktery iDoklad ceka (2026-08-20).
const { unwrap, pick, text, num, date, need, day } = ctx.util;
if (inputs.unitPrice < 0) ctx.fail('Cena za jednotku nemůže být záporná.');
const amount = inputs.amount ?? 1;
if (amount <= 0) ctx.fail('Počet jednotek musí být větší než nula.');
// 1. Predvyplneny vzor. Nese povinna pole, ktera nechceme vyplnovat rucne.
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 issuedAt = inputs.dateOfIssue ? new Date(inputs.dateOfIssue) : new Date();
const maturityAt = addDays(issuedAt, inputs.maturityDays ?? 14);
// 2. Prepisou se jen ta pole, kterym rozumime. Zbytek zustava ze vzoru.
const payload = {
...defaults,
partnerId: inputs.partnerId,
description: inputs.description,
dateOfIssue: day(issuedAt),
dateOfTaxing: day(issuedAt),
dateOfMaturity: day(maturityAt),
items: [
{
name: inputs.itemName,
amount,
unit: inputs.unit ?? 'ks',
unitPrice: inputs.unitPrice,
discountPercentage: 0,
isTaxMovement: false,
priceType: inputs.priceType ?? 0,
vatRateType: inputs.vatRateType ?? 0,
},
],
};
if (inputs.variableSymbol) payload.variableSymbol = inputs.variableSymbol;
if (inputs.note) payload.note = inputs.note;
const { body } = await ctx.http.post('/issued-invoices', payload);
const invoice = unwrap(body);
ctx.log(`Faktura vystavena pro odběratele ${inputs.partnerId}.`);
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')),
dateOfMaturity:
date(pick(invoice, 'dateOfMaturity')) ?? need(date(maturityAt), 'datum splatnosti'),
};
}