iDoklad ceka v tele `IssuedInvoiceItems`, ale prevod je casto pojmenuje `items` podle toho, jak se jmenuji ve zdrojove objednavce. Kontrola trvala na `items`, telo se ale posilalo tak, jak prislo - prevod s PascalCase jmeny by tedy neprosel kontrolou, a prevod s malymi pismeny by prosel a doklad by v iDokladu vznikl bez radku. Ted se polozky hledaji pod obema jmeny a do tela se doplni pod tim, ktere ceka iDoklad. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
101 lines
3.9 KiB
JavaScript
101 lines
3.9 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;
|
|
|
|
/*
|
|
* Polozky se hledaji pod obema jmeny. iDoklad je v tele ceka jako
|
|
* `IssuedInvoiceItems`, ale prevod je casto pojmenuje `items` podle toho,
|
|
* jak se jmenuji ve zdrojove objednavce. Trvat na jednom z nich by znamenalo,
|
|
* ze prevod projde kontrolou a doklad prijde do iDokladu bez radku.
|
|
*/
|
|
const rawItems = pick(payload, 'IssuedInvoiceItems', 'items');
|
|
const items = Array.isArray(rawItems) ? rawItems : [];
|
|
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.');
|
|
}
|
|
|
|
/*
|
|
* Vzor nese povinna pole, prevod prepise ta, kterym rozumime. Polozky se
|
|
* doplni pod jmenem, ktere ceka iDoklad, at uz prisly pojmenovane jakkoliv.
|
|
*/
|
|
const body = { ...defaults, ...payload, IssuedInvoiceItems: items };
|
|
delete body.items;
|
|
|
|
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 : {},
|
|
};
|
|
}
|