/** * iDoklad: najde odberatele, a kdyz neni, zalozi ho. * * Sluzba: https://services.csbot.cz/apps/idoklad * Endpointy: GET /contacts?filter=..., POST /contacts * * Proc jeden krok a ne dvojice "najdi" plus "zaloz" ve vetvich podminky: * vetev podminky **nepridava nic do sekvence za podminkou**, protoze nemusela * probehnout. ID odberatele by tedy za podminkou nebylo k dispozici, i kdyz * ho ve skutecnosti daji obe vetve. Sloucit to do jednoho kroku je poctivejsi * nez to obchazet - a navic je to presne ta operace, kterou clovek chce: * "chci mit jistotu, ze odberatel v iDokladu je". * * Vystup `created` rekne, co se stalo, takze se na to da navazat podminkou * (poslat uvitaci mail jen novemu zakaznikovi). */ export const manifest = { id: 'idoklad.upsert-contact', name: 'Najít nebo založit kontakt', description: 'Dohledá odběratele podle IČO nebo e-mailu. Když neexistuje, založí ho ' + 'a vrátí nové ID. Používá se před vystavením faktury.', timeoutMs: 25000, inputs: [ { id: 'identificationNumber', label: 'IČO', type: 'string', required: false, pattern: '^[0-9]{6,12}$', hint: 'Jen číslice. Přesnější než e-mail, hledá se podle něj první.', }, { id: 'email', label: 'E-mail', type: 'string', required: false, hint: 'Použije se, když IČO není k dispozici.', }, { id: 'companyName', label: 'Název odběratele', type: 'string', required: true, hint: 'U firmy název, u fyzické osoby jméno. Bez něj kontakt založit nejde.', }, { id: 'vatNumber', label: 'DIČ', type: 'string', required: false }, { id: 'street', label: 'Ulice', type: 'string', required: false }, { id: 'city', label: 'Město', type: 'string', required: false }, { id: 'postalCode', label: 'PSČ', type: 'string', required: false }, { id: 'countryCode', label: 'Země', type: 'string', required: false, default: 'CZ', hint: 'Dvoupísmenný kód. Nevyplněno = CZ.', }, { id: 'phone', label: 'Telefon', type: 'string', required: false }, ], outputs: [ { id: 'contactId', label: 'ID kontaktu', type: 'number', required: true }, { id: 'created', label: 'Kontakt vznikl teď', type: 'boolean', required: true }, { id: 'companyName', label: 'Název odběratele', type: 'string', required: false }, { id: 'matchedBy', label: 'Podle čeho se našel', type: 'string', required: true }, ], }; export async function run(inputs, ctx) { const { unwrap, pick, text, num, need } = ctx.util; const ico = text(inputs.identificationNumber); const email = text(inputs.email); if (!ico && !email) { ctx.fail('Vyplňte IČO nebo e-mail, jinak není podle čeho hledat ani zakládat.'); } /** Jedno hledani podle jednoho pole. Vraci kontakt, nebo null. */ async function search(field, value) { const { body } = await ctx.http.get('/contacts', { query: { filter: `(${field}~eq~${value})`, filtertype: 'and', pageSize: 1 }, }); const items = unwrap(body); return Array.isArray(items) && items.length > 0 ? items[0] : null; } // Poradi je zamer: ICO je jednoznacne, e-mail muze mit vic firem stejny. const attempts = []; if (ico) attempts.push(['IdentificationNumber', ico, 'IČO']); if (email) attempts.push(['Email', email, 'e-mail']); for (const [field, value, label] of attempts) { const contact = await search(field, value); if (!contact) { ctx.log(`Podle ${label} ${value} se nic nenašlo.`); continue; } ctx.log(`Kontakt nalezen podle ${label}, zakládat se nebude.`); return { contactId: need(num(pick(contact, 'id')), 'ID nalezeného kontaktu'), created: false, companyName: text(pick(contact, 'companyName', 'name')), matchedBy: label, }; } /* * Zaklada se az tady, kdyz uz je jiste, ze kontakt neexistuje. Poradi je * dulezite: zalozit napred a hledat potom by pri kazde objednavce vyrobilo * dalsiho odberatele se stejnym ICO. */ const companyName = text(inputs.companyName); if (!companyName) { ctx.fail('Kontakt v iDokladu neexistuje a bez názvu odběratele ho nejde založit.'); } const body = { CompanyName: companyName, ...(ico ? { IdentificationNumber: ico } : {}), ...(text(inputs.vatNumber) ? { VatIdentificationNumber: text(inputs.vatNumber) } : {}), ...(email ? { Email: email } : {}), ...(text(inputs.phone) ? { Mobile: text(inputs.phone) } : {}), ...(text(inputs.street) ? { Street: text(inputs.street) } : {}), ...(text(inputs.city) ? { City: text(inputs.city) } : {}), ...(text(inputs.postalCode) ? { PostalCode: text(inputs.postalCode) } : {}), CountryId: null, ...(text(inputs.countryCode) ? { CountryCode: text(inputs.countryCode) } : {}), }; const response = await ctx.http.post('/contacts', body); const created = unwrap(response.body); ctx.log(`Kontakt ${companyName} založen.`); return { contactId: need(num(pick(created, 'id')), 'ID založeného kontaktu'), created: true, companyName: text(pick(created, 'companyName', 'name')) || companyName, matchedBy: 'založeno', }; }