/** * 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 : {}, }; }