Krok Najit nebo zalozit kontakt a druhy vstup vlastniho skriptu
Chybelo k tomu, aby sla postavit cela cesta objednavka -> faktura. - Novy skript idoklad.upsert-contact: dohleda odberatele podle ICO nebo e-mailu, a kdyz neni, zalozi ho a vrati nove ID. Vystup `created` rekne, co se stalo, takze na to jde navazat podminkou. Proc jeden krok a ne dvojice ve vetvich podminky: vetev nepridava nic do sekvence za podminkou, protoze nemusela probehnout. ID odberatele by za podminkou nebylo k dispozici, i kdyz ho ve skutecnosti daji obe vetve. Zaklada se az kdyz hledani nic nenaslo - opacne poradi by pri kazde objednavce vyrobilo dalsiho odberatele se stejnym ICO. - Krok Vlastni skript ma druhy vstup "Co pridat ke vstupu". Skript casto potrebuje krome hlavniho objektu jednu hodnotu z predchoziho kroku, typicky ID odberatele. Prijde v input.extra, aby se hlavni data nemichala s tim, co jsme doplnili my. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
e4779caf82
commit
13c0c03ed4
@@ -0,0 +1,146 @@
|
||||
/**
|
||||
* 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',
|
||||
};
|
||||
}
|
||||
@@ -1762,6 +1762,15 @@ export const services: Service[] = [
|
||||
required: true,
|
||||
hint: 'Objekt, který skript dostane jako input. Třeba {{_body}}.',
|
||||
},
|
||||
{
|
||||
id: 'extra',
|
||||
label: 'Co přidat ke vstupu',
|
||||
kind: 'json',
|
||||
required: false,
|
||||
hint:
|
||||
'Hodnoty z předchozích kroků, například ' +
|
||||
'{"partnerId": "{{st_kontakt.contactId}}"}. Skript je najde v input.extra.',
|
||||
},
|
||||
],
|
||||
outputFields: [
|
||||
{ id: 'transform.result', name: 'result', type: 'object', required: true },
|
||||
|
||||
@@ -544,7 +544,16 @@ const handlers: Record<string, Handler> = {
|
||||
}
|
||||
|
||||
const source = inputs.source ? safeJson(inputs.source) : {};
|
||||
const result = runSandbox(script.code, source);
|
||||
|
||||
/*
|
||||
* Skript casto potrebuje krome hlavniho objektu i jednu hodnotu
|
||||
* z predchoziho kroku - typicky ID odberatele. Prijde v `input.extra`,
|
||||
* aby se hlavni data nemichala s tim, co jsme doplnili my.
|
||||
*/
|
||||
const extra = inputs.extra?.trim() ? safeJson(inputs.extra) : undefined;
|
||||
const payload = extra === undefined ? source : { ...source, extra };
|
||||
|
||||
const result = runSandbox(script.code, payload);
|
||||
noteRun(script.id, script.tenantId, result.ok);
|
||||
|
||||
if (!result.ok) {
|
||||
@@ -552,7 +561,7 @@ const handlers: Record<string, Handler> = {
|
||||
ok: false,
|
||||
summary: `${script.name}: ${result.error?.split('\n')[0] ?? 'chyba'}`,
|
||||
// Cely vstup i chyba. Bez vstupu se chyba v prevodu hleda naslepo.
|
||||
detail: JSON.stringify({ vstup: source, chyba: result.error }, null, 2),
|
||||
detail: JSON.stringify({ vstup: payload, chyba: result.error }, null, 2),
|
||||
outputs: {},
|
||||
};
|
||||
}
|
||||
@@ -560,7 +569,7 @@ const handlers: Record<string, Handler> = {
|
||||
return {
|
||||
ok: true,
|
||||
summary: `${script.name}: hotovo za ${result.durationMs} ms`,
|
||||
detail: JSON.stringify({ vstup: source, vystup: result.value }, null, 2),
|
||||
detail: JSON.stringify({ vstup: payload, vystup: result.value }, null, 2),
|
||||
outputs: { result: result.value },
|
||||
};
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user