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>
108 lines
3.3 KiB
JavaScript
108 lines
3.3 KiB
JavaScript
/**
|
|
* SAP Business One: dohledani obchodniho partnera.
|
|
*
|
|
* Sluzba: https://services.csbot.cz/apps/sap-bo
|
|
* Endpoint: GET /api/business-partners?$filter=...
|
|
*
|
|
* Vzor **predvalidace nad OData**. Service Layer filtruje jazykem OData, kde
|
|
* se retezce uvozuji apostrofem - a apostrof v nazvu firmy by dotaz rozbil,
|
|
* takze se zdvojuje. Nenalezeno neni chyba: podle toho se strom vetvi na
|
|
* zalozeni noveho partnera.
|
|
*/
|
|
|
|
export const manifest = {
|
|
id: 'sap-bo.find-business-partner',
|
|
name: 'Najít obchodního partnera',
|
|
description:
|
|
'Dohledá partnera v SAP Business One podle kódu, IČO nebo názvu. ' +
|
|
'Nic nezakládá, výsledek slouží k větvení stromu.',
|
|
|
|
inputs: [
|
|
{
|
|
id: 'cardCode',
|
|
label: 'Kód partnera',
|
|
type: 'string',
|
|
required: false,
|
|
hint: 'Například C00123. Nejpřesnější, hledá se podle něj první.',
|
|
},
|
|
{
|
|
id: 'taxId',
|
|
label: 'IČO',
|
|
type: 'string',
|
|
required: false,
|
|
hint: 'Porovnává se s polem FederalTaxID.',
|
|
},
|
|
{
|
|
id: 'name',
|
|
label: 'Název',
|
|
type: 'string',
|
|
required: false,
|
|
hint: 'Stačí část názvu, hledá se podřetězec.',
|
|
},
|
|
],
|
|
|
|
outputs: [
|
|
{ id: 'found', label: 'Partner nalezen', type: 'boolean', required: true },
|
|
{ id: 'cardCode', label: 'Kód partnera', type: 'string', required: false },
|
|
{ id: 'cardName', label: 'Název partnera', type: 'string', required: false },
|
|
{ id: 'taxId', label: 'IČO', type: 'string', required: false },
|
|
{ id: 'currency', label: 'Měna', type: 'string', required: false },
|
|
{ id: 'matchedBy', label: 'Podle čeho se našel', type: 'string', required: true },
|
|
],
|
|
|
|
timeoutMs: 30000,
|
|
};
|
|
|
|
const notFound = {
|
|
found: false,
|
|
cardCode: null,
|
|
cardName: null,
|
|
taxId: null,
|
|
currency: null,
|
|
matchedBy: 'nenalezeno',
|
|
};
|
|
|
|
export async function run(inputs, ctx) {
|
|
const { pick, text } = ctx.util;
|
|
|
|
if (!inputs.cardCode && !inputs.taxId && !inputs.name) {
|
|
ctx.fail('Vyplňte kód partnera, IČO nebo název, jinak není podle čeho hledat.');
|
|
}
|
|
|
|
// Poradi je zamer: kod je klic, ICO je jednoznacne, nazev se pise pokazde jinak.
|
|
const attempts = [];
|
|
if (inputs.cardCode) attempts.push([`CardCode eq ${ctx.util.quote(inputs.cardCode)}`, 'kód']);
|
|
if (inputs.taxId) attempts.push([`FederalTaxID eq ${ctx.util.quote(inputs.taxId)}`, 'IČO']);
|
|
if (inputs.name) attempts.push([`contains(CardName,${ctx.util.quote(inputs.name)})`, 'název']);
|
|
|
|
for (const [filter, label] of attempts) {
|
|
const { body } = await ctx.http.get('/api/business-partners', {
|
|
query: {
|
|
$filter: filter,
|
|
$select: 'CardCode,CardName,FederalTaxID,Currency,CardType',
|
|
$top: 5,
|
|
},
|
|
});
|
|
|
|
const raw = pick(body, 'value', 'data', 'items');
|
|
const items = Array.isArray(raw) ? raw : Array.isArray(body) ? body : [];
|
|
if (items.length === 0) {
|
|
ctx.log(`Podle ${label} se nic nenašlo.`);
|
|
continue;
|
|
}
|
|
if (items.length > 1) ctx.log(`Podle ${label} vyšlo ${items.length} partnerů, bere se první.`);
|
|
|
|
const partner = items[0];
|
|
return {
|
|
found: true,
|
|
cardCode: text(pick(partner, 'CardCode')),
|
|
cardName: text(pick(partner, 'CardName')),
|
|
taxId: text(pick(partner, 'FederalTaxID')),
|
|
currency: text(pick(partner, 'Currency')),
|
|
matchedBy: label,
|
|
};
|
|
}
|
|
|
|
return notFound;
|
|
}
|