Skripty konektoru: vykonna cast s manifestem a kontrolou parametru
Konektory dostaly vykonnou cast. Jeden skript je jeden soubor, ktery nese manifest (vstupni a vystupni parametry) i kod. Diky manifestu s nim umi pracovat strom automatizace, aniz by o kodu cokoliv vedel. Soubory jsou zamerne obycejny JavaScript, ne TypeScript. TypeScript by se musel prelozit a to je presne to otaceni, ktere tady nema byt. Registr sleduje cas zmeny souboru, takze uprava v portalu, rucni uprava souboru i novy soubor ve slozce funguji stejne a bez restartu. Pridano: - scripts/ se skripty konektoru, nazev souboru je zaroven ID operace - kontrola vstupu i vystupu proti manifestu, jedna funkce pro obe strany. Chybejici povinny vystup je chyba skriptu, ne uzivatele - jinak by strom veril parametru, ktery nikdy nedosel - ctx predavany skriptu: http nad adresou napojeni, util, log, config, idempotencyKey, fail a retry. Skript nedostane pristupove udaje - rozliseni opakovatelne a koncove chyby. Runner nikdy nevyhodi vyjimku, vzdy vraci vysledek vcetne retryable - redakce tajnych hodnot pred zapisem do logu. Cizi API rado vraci prijaty token v chybove zprave a log ticketu vidi klient - napojeni z environment variables vcetne iDokladu - sest ukazkovych skriptu pro iDoklad proti skutecnemu API sluzby services.csbot.cz/apps/idoklad, kazdy na jiny vzor - stranka /dashboard/skripty: seznam, manifest, editor, zkusebni spusteni. Formular testu se sklada z manifestu, nepise se pro kazdy skript - endpointy /api/dashboard/scripts vcetne Swaggeru Zmeneno: - katalog konektoru uz neni jen staticky seznam. Akce ze skriptu se domeruji prekryvem v src/data/connectors.ts, takze se naraz objevi ve validaci stromu, ve vypoctu scope i v sablonach. Pri stejnem ID vyhrava skript - ConnectorOperation ma implementation a scriptId - ApiError na klientovi nese cele telo odpovedi a umi z nej vytahnout issues - Dockerfile kopiruje scripts/ do vysledneho image Ukladani nemuze rozbit fungujici skript: kod se nejdriv zapise do docasneho souboru, ten se nacte a overi, a az pak prepise puvodni. K tomu tri dokumenty navrhu dalsich kroku: 09 datove modely a prava, 10 runtime a rozpocet na 150 klientu, 11 popis skriptu konektoru. Overeno: npm run typecheck prochazi na serveru i webu. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
bbc2236c0d
commit
6f6b287d7e
@@ -0,0 +1,98 @@
|
||||
/**
|
||||
* iDoklad: zapsani uhrady k vydane fakture.
|
||||
*
|
||||
* Sluzba: https://services.csbot.cz/apps/idoklad
|
||||
* Endpointy: GET /issued-payments/default/{invoiceId}, POST /issued-payments
|
||||
*
|
||||
* Vzor akce, ktera **neco meni**. U te zalezi na idempotenci: kdyz runtime krok
|
||||
* zopakuje po timeoutu, nesmi vzniknout druha uhrada. Klic `ctx.idempotencyKey`
|
||||
* je pro tentyz krok stejny pres vsechny pokusy a runtime ho posila v hlavicce
|
||||
* `Idempotency-Key` automaticky, takze skript nemusi delat nic navic.
|
||||
*
|
||||
* Castka se necha prazdna pro plnou uhradu - vzor z iDokladu uz nese zbytek
|
||||
* k zaplaceni, takze se nemusi pocitat tady.
|
||||
*/
|
||||
|
||||
export const manifest = {
|
||||
id: 'idoklad.register-payment',
|
||||
name: 'Zapsat úhradu faktury',
|
||||
description:
|
||||
'Zapíše k vydané faktuře úhradu. Bez zadané částky se použije zbytek ' +
|
||||
'k zaplacení podle iDokladu.',
|
||||
|
||||
inputs: [
|
||||
{ id: 'invoiceId', label: 'ID faktury v iDokladu', type: 'number', required: true },
|
||||
{
|
||||
id: 'amount',
|
||||
label: 'Uhrazená částka',
|
||||
type: 'number',
|
||||
required: false,
|
||||
hint: 'Nevyplněno = celý zbytek k zaplacení.',
|
||||
},
|
||||
{
|
||||
id: 'dateOfPayment',
|
||||
label: 'Datum úhrady',
|
||||
type: 'date',
|
||||
required: false,
|
||||
hint: 'Nevyplněno = dnes.',
|
||||
},
|
||||
{
|
||||
id: 'sendConfirmation',
|
||||
label: 'Poslat potvrzení odběrateli',
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
],
|
||||
|
||||
outputs: [
|
||||
{ id: 'paymentId', label: 'ID úhrady', type: 'number', required: true },
|
||||
{ id: 'amount', label: 'Zapsaná částka', type: 'number', required: true },
|
||||
{ id: 'dateOfPayment', label: 'Datum úhrady', type: 'date', required: true },
|
||||
],
|
||||
};
|
||||
|
||||
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;
|
||||
|
||||
if (inputs.amount !== null && inputs.amount <= 0) {
|
||||
ctx.fail('Uhrazená částka musí být větší než nula.');
|
||||
}
|
||||
|
||||
// Vzor nese zbytek k zaplaceni i vychozi zpusob platby.
|
||||
const defaults = unwrap((await ctx.http.get(`/issued-payments/default/${inputs.invoiceId}`)).body);
|
||||
if (!defaults || typeof defaults !== 'object') {
|
||||
ctx.fail(`K faktuře ${inputs.invoiceId} nejde zapsat úhradu, iDoklad ji nezná.`);
|
||||
}
|
||||
|
||||
const suggested = num(pick(defaults, 'paymentAmount'));
|
||||
const amount = inputs.amount ?? suggested;
|
||||
if (amount === null) {
|
||||
ctx.fail('iDoklad nevrátil zbytek k zaplacení, zadejte částku ručně.');
|
||||
}
|
||||
|
||||
const paidAt = inputs.dateOfPayment ? new Date(inputs.dateOfPayment) : new Date();
|
||||
|
||||
const payload = {
|
||||
...defaults,
|
||||
invoiceId: inputs.invoiceId,
|
||||
paymentAmount: amount,
|
||||
dateOfPayment: isoDay(paidAt),
|
||||
sendPaymentConfirmation: inputs.sendConfirmation ?? false,
|
||||
};
|
||||
|
||||
const { body } = await ctx.http.post('/issued-payments', payload);
|
||||
const payment = unwrap(body);
|
||||
|
||||
ctx.log(`K faktuře ${inputs.invoiceId} zapsána úhrada ${amount}.`);
|
||||
|
||||
return {
|
||||
paymentId: need(num(pick(payment, 'id')), 'ID úhrady'),
|
||||
amount: need(num(pick(payment, 'paymentAmount')) ?? amount, 'zapsanou částku'),
|
||||
dateOfPayment: need(date(pick(payment, 'dateOfPayment')) ?? date(paidAt), 'datum úhrady'),
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user