Revize projektu: prava, vykon, runtime, portal a ARES
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>
This commit is contained in:
co-authored by
Claude Fable 5.1
parent
0c405ea55a
commit
104ae36783
+4
-2
@@ -60,8 +60,10 @@ export const manifest = {
|
||||
* @param {{
|
||||
* http: { get: Function, post: Function, patch: Function, put: Function, del: Function,
|
||||
* postForm: Function },
|
||||
* util: { unwrap: Function, pick: Function, first: Function, text: Function,
|
||||
* num: Function, bool: Function, date: Function, round: Function, need: Function },
|
||||
* util: { unwrap: Function, pick: Function, first: Function, list: Function,
|
||||
* text: Function, num: Function, bool: Function, date: Function, day: Function,
|
||||
* round: Function, need: Function, get: Function, addresses: Function,
|
||||
* quote: Function, applyRules: Function, fillJson: Function },
|
||||
* log: Function, config: Record<string, string>, idempotencyKey: string,
|
||||
* fail: Function, retry: Function,
|
||||
* }} ctx
|
||||
|
||||
@@ -60,26 +60,12 @@ function digits(value) {
|
||||
return String(value ?? '').replace(/[^0-9]/g, '');
|
||||
}
|
||||
|
||||
/**
|
||||
* Seznam z odpovedi banky.
|
||||
*
|
||||
* COBS obaluje kolekci pod vlastni nazev (`accounts`, `transactions`), jiny
|
||||
* standard pouziva `content` nebo `items`. Hadat jeden tvar by znamenalo, ze
|
||||
* pri zmene obalky krok tise vrati nula zaznamu misto chyby.
|
||||
*/
|
||||
function collection(ctx, body, ...names) {
|
||||
if (Array.isArray(body)) return body;
|
||||
const found = ctx.util.pick(body, ...names, 'data', 'content', 'items');
|
||||
if (Array.isArray(found)) return found;
|
||||
const unwrapped = ctx.util.unwrap(body);
|
||||
return Array.isArray(unwrapped) ? unwrapped : null;
|
||||
}
|
||||
|
||||
export async function run(inputs, ctx) {
|
||||
const { pick, text, need } = ctx.util;
|
||||
|
||||
const { body: accountsBody } = await ctx.http.get('/accounts', { query: { size: 50 } });
|
||||
const accounts = collection(ctx, accountsBody, 'accounts');
|
||||
// COBS obaluje kolekci pod vlastni nazev, `list` zkusi i obvykle obalky.
|
||||
const accounts = ctx.util.list(accountsBody, 'accounts');
|
||||
if (accounts === null) ctx.fail('Odpověď banky nemá seznam účtů.', accountsBody);
|
||||
if (accounts.length === 0) {
|
||||
ctx.fail('Souhlas nedosáhne na žádný účet. Zkontrolujte platnost souhlasu v ČSOB.');
|
||||
@@ -117,7 +103,7 @@ export async function run(inputs, ctx) {
|
||||
},
|
||||
});
|
||||
|
||||
const transactions = collection(ctx, body, 'transactions');
|
||||
const transactions = ctx.util.list(body, 'transactions');
|
||||
if (transactions === null) ctx.fail('Odpověď banky nemá seznam pohybů.', body);
|
||||
|
||||
return {
|
||||
|
||||
@@ -56,14 +56,6 @@ export const manifest = {
|
||||
timeoutMs: 30000,
|
||||
};
|
||||
|
||||
/** Adresy z jednoho radku. Clovek pise carky, Gmail chce jeden retezec. */
|
||||
function addresses(value) {
|
||||
return String(value ?? '')
|
||||
.split(/[,;]/)
|
||||
.map((item) => item.trim())
|
||||
.filter((item) => item !== '');
|
||||
}
|
||||
|
||||
/** Base64 z textu. Pres bajty, aby diakritika prezila. */
|
||||
function base64(value) {
|
||||
const bytes = new TextEncoder().encode(value);
|
||||
@@ -86,8 +78,9 @@ function header(value) {
|
||||
}
|
||||
|
||||
export async function run(inputs, ctx) {
|
||||
const { pick, text, need } = ctx.util;
|
||||
const { pick, text, need, addresses } = ctx.util;
|
||||
|
||||
// Clovek pise carky, Gmail chce jeden retezec.
|
||||
const to = addresses(inputs.to);
|
||||
if (to.length === 0) ctx.fail('Nezůstal žádný příjemce, zkontrolujte pole Příjemci.');
|
||||
const cc = addresses(inputs.cc);
|
||||
|
||||
@@ -88,11 +88,6 @@ export const manifest = {
|
||||
],
|
||||
};
|
||||
|
||||
/** Datum ve tvaru, ktery iDoklad ceka. */
|
||||
function isoDay(value) {
|
||||
return new Date(value).toISOString().slice(0, 10);
|
||||
}
|
||||
|
||||
function addDays(value, days) {
|
||||
const date = new Date(value);
|
||||
date.setUTCDate(date.getUTCDate() + days);
|
||||
@@ -100,7 +95,8 @@ function addDays(value, days) {
|
||||
}
|
||||
|
||||
export async function run(inputs, ctx) {
|
||||
const { unwrap, pick, text, num, date, need } = ctx.util;
|
||||
// `day` dava datum ve tvaru, ktery iDoklad ceka (2026-08-20).
|
||||
const { unwrap, pick, text, num, date, need, day } = ctx.util;
|
||||
|
||||
if (inputs.unitPrice < 0) ctx.fail('Cena za jednotku nemůže být záporná.');
|
||||
const amount = inputs.amount ?? 1;
|
||||
@@ -120,9 +116,9 @@ export async function run(inputs, ctx) {
|
||||
...defaults,
|
||||
partnerId: inputs.partnerId,
|
||||
description: inputs.description,
|
||||
dateOfIssue: isoDay(issuedAt),
|
||||
dateOfTaxing: isoDay(issuedAt),
|
||||
dateOfMaturity: isoDay(maturityAt),
|
||||
dateOfIssue: day(issuedAt),
|
||||
dateOfTaxing: day(issuedAt),
|
||||
dateOfMaturity: day(maturityAt),
|
||||
items: [
|
||||
{
|
||||
name: inputs.itemName,
|
||||
|
||||
@@ -52,12 +52,8 @@ export const manifest = {
|
||||
],
|
||||
};
|
||||
|
||||
function isoDay(value) {
|
||||
return new Date(value).toISOString().slice(0, 10);
|
||||
}
|
||||
|
||||
export async function run(inputs, ctx) {
|
||||
const { unwrap, pick, num, date, need } = ctx.util;
|
||||
const { unwrap, pick, num, date, need, day } = ctx.util;
|
||||
|
||||
if (inputs.amount !== null && inputs.amount <= 0) {
|
||||
ctx.fail('Uhrazená částka musí být větší než nula.');
|
||||
@@ -81,7 +77,7 @@ export async function run(inputs, ctx) {
|
||||
...defaults,
|
||||
invoiceId: inputs.invoiceId,
|
||||
paymentAmount: amount,
|
||||
dateOfPayment: isoDay(paidAt),
|
||||
dateOfPayment: day(paidAt),
|
||||
sendPaymentConfirmation: inputs.sendConfirmation ?? false,
|
||||
};
|
||||
|
||||
|
||||
@@ -66,16 +66,10 @@ export const manifest = {
|
||||
timeoutMs: 30000,
|
||||
};
|
||||
|
||||
/** Seznam adres z jednoho radku. Graph chce pole objektu, clovek pise carky. */
|
||||
function addresses(value) {
|
||||
return String(value ?? '')
|
||||
.split(/[,;]/)
|
||||
.map((item) => item.trim())
|
||||
.filter((item) => item !== '')
|
||||
.map((address) => ({ address }));
|
||||
}
|
||||
|
||||
export async function run(inputs, ctx) {
|
||||
// Graph chce pole objektu, clovek pise carky.
|
||||
const addresses = (value) => ctx.util.addresses(value).map((address) => ({ address }));
|
||||
|
||||
const to = addresses(inputs.to);
|
||||
if (to.length === 0) ctx.fail('Nezůstal žádný příjemce, zkontrolujte pole Příjemci.');
|
||||
|
||||
|
||||
@@ -53,11 +53,6 @@ export const manifest = {
|
||||
timeoutMs: 30000,
|
||||
};
|
||||
|
||||
/** Retezec do OData filtru. Apostrof se zdvojuje, jinak dotaz spadne. */
|
||||
function quote(value) {
|
||||
return `'${String(value).replace(/'/g, "''")}'`;
|
||||
}
|
||||
|
||||
const notFound = {
|
||||
found: false,
|
||||
cardCode: null,
|
||||
@@ -76,9 +71,9 @@ export async function run(inputs, ctx) {
|
||||
|
||||
// Poradi je zamer: kod je klic, ICO je jednoznacne, nazev se pise pokazde jinak.
|
||||
const attempts = [];
|
||||
if (inputs.cardCode) attempts.push([`CardCode eq ${quote(inputs.cardCode)}`, 'kód']);
|
||||
if (inputs.taxId) attempts.push([`FederalTaxID eq ${quote(inputs.taxId)}`, 'IČO']);
|
||||
if (inputs.name) attempts.push([`contains(CardName,${quote(inputs.name)})`, 'název']);
|
||||
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', {
|
||||
|
||||
@@ -51,13 +51,10 @@ export const manifest = {
|
||||
timeoutMs: 30000,
|
||||
};
|
||||
|
||||
function quote(value) {
|
||||
return `'${String(value).replace(/'/g, "''")}'`;
|
||||
}
|
||||
|
||||
export async function run(inputs, ctx) {
|
||||
const { pick, num, text, round } = ctx.util;
|
||||
const { pick, num, text, round, quote } = ctx.util;
|
||||
|
||||
// Apostrof v OData filtru se zdvojuje, to dela `quote`.
|
||||
const conditions = [];
|
||||
if (inputs.cardCode) conditions.push(`CardCode eq ${quote(inputs.cardCode)}`);
|
||||
if (inputs.dateFrom) conditions.push(`DocDate ge ${quote(inputs.dateFrom.slice(0, 10))}`);
|
||||
|
||||
@@ -66,11 +66,7 @@ function statRows(ctx, body) {
|
||||
});
|
||||
}
|
||||
|
||||
const stats = pick(body, 'stats');
|
||||
if (Array.isArray(stats)) return stats;
|
||||
|
||||
const data = pick(body, 'data');
|
||||
return Array.isArray(data) ? data : [];
|
||||
return ctx.util.list(body, 'stats') ?? [];
|
||||
}
|
||||
|
||||
export async function run(inputs, ctx) {
|
||||
|
||||
Reference in New Issue
Block a user