Pauza, zapis do logu a podminky nad vystupy konecne funguji
Vyslo najevo pri stavbe ukazkovych automatizaci nad modelem objednavky. - log/write a delay/wait nemely vykonnou cast. Byly registrovane jen pod klici flow/log a flow/pause, ktere v katalogu nejsou. Kdo si v builderu vybral Zapsat zpravu nebo Pockat, dostal pri behu "operace nema vykonnou cast" - krok slo pridat, ale nikdy nefungoval. - Obe operace navic nemely nastavitelna pole, takze do nich neslo napsat, co se ma zapsat a jak dlouho cekat. - Podminka nad vystupem kroku se nevyhodnotila. Podminka si drzi ID parametru, aby ji prejmenovani nerozbilo, ale data chodi pod jmenem: parametr f_9x1 nese hodnotu z klice callSid, vystup kroku st_kontakt.idoklad.found lezi pod found. Beh dostava tabulku jmen podle ID, takze se ma o co oprit. Bez toho podminka tise vychazela jako prazdna a slo se vzdy vetvi NE. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
9b18531d3e
commit
99561c4682
+19
-1
@@ -1759,6 +1759,15 @@ export const services: Service[] = [
|
||||
name: 'Počkat',
|
||||
description: 'Pozastaví běh na zadanou dobu.',
|
||||
fields: ['Doba čekání'],
|
||||
inputs: [
|
||||
{
|
||||
id: 'seconds',
|
||||
label: 'Sekund',
|
||||
kind: 'text',
|
||||
required: true,
|
||||
hint: 'Nejvýš 60. Delší čekání patří do plánovače, ne do běhu.',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -1779,7 +1788,16 @@ export const services: Service[] = [
|
||||
id: 'write',
|
||||
name: 'Zapsat zprávu',
|
||||
description: 'Přidá záznam do historie běhu, užitečné při ladění.',
|
||||
fields: ['Úroveň', 'Zpráva'],
|
||||
fields: ['Zpráva'],
|
||||
inputs: [
|
||||
{
|
||||
id: 'message',
|
||||
label: 'Zpráva',
|
||||
kind: 'text',
|
||||
required: true,
|
||||
hint: 'Může obsahovat odkazy, například {{data.order.code}}.',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
@@ -270,6 +270,11 @@ async function runAction(
|
||||
tenantId: ticket.tenantId,
|
||||
ticketId: ticket.id,
|
||||
idempotencyKey: `ticket:${ticket.id}:${action.id}`,
|
||||
/*
|
||||
* Telo akce nema spoustec, parametry jsou udaje ticketu a odpovedi
|
||||
* z dialogu. Jmeno se rovna ID, takze podminka najde hodnotu i tady.
|
||||
*/
|
||||
fieldNames: Object.fromEntries(Object.keys(context).map((key) => [key, key])),
|
||||
});
|
||||
|
||||
if (!run.ok) {
|
||||
|
||||
@@ -513,20 +513,35 @@ const handlers: Record<string, Handler> = {
|
||||
* za tri dny) takhle delat nejde - to patri do fronty jako beh naplanovany
|
||||
* na pozdeji, viz `nextAttemptAt`. Strop je proto minuta.
|
||||
*/
|
||||
'flow/pause': async (inputs) => {
|
||||
const seconds = Math.min(Math.max(Number(inputs.seconds ?? 1) || 1, 0), 60);
|
||||
await new Promise((resolve) => setTimeout(resolve, seconds * 1_000));
|
||||
return { ok: true, summary: `${seconds} s`, detail: null, outputs: {} };
|
||||
},
|
||||
'flow/pause': pause,
|
||||
/*
|
||||
* Tytéž kroky pod klici z katalogu.
|
||||
*
|
||||
* V katalogu jsou jako sluzba `delay` a `log`, ale vykonna cast byla
|
||||
* registrovana jen pod `flow/...`. Kdo si v builderu vybral Pockat nebo
|
||||
* Zapsat zpravu, dostal pri behu "operace nema vykonnou cast" - krok slo
|
||||
* pridat, ale nikdy nefungoval.
|
||||
*/
|
||||
'delay/wait': pause,
|
||||
'log/write': write,
|
||||
'flow/log': write,
|
||||
};
|
||||
|
||||
/** Zapis do logu ticketu. Na overeni, ze strom dosel, kam mel. */
|
||||
'flow/log': (inputs) => ({
|
||||
async function pause(inputs: Record<string, string>): Promise<StepOutcome> {
|
||||
const seconds = Math.min(Math.max(Number(inputs.seconds ?? 1) || 1, 0), 60);
|
||||
await new Promise((resolve) => setTimeout(resolve, seconds * 1_000));
|
||||
return { ok: true, summary: `${seconds} s`, detail: null, outputs: {} };
|
||||
}
|
||||
|
||||
/** Zapis do logu ticketu. Na overeni, ze strom dosel, kam mel. */
|
||||
function write(inputs: Record<string, string>): StepOutcome {
|
||||
return {
|
||||
ok: true,
|
||||
summary: inputs.message?.trim() || 'zápis',
|
||||
detail: null,
|
||||
outputs: {},
|
||||
}),
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
function currentTicket(ticketId: string, tenantId: string): Ticket | undefined {
|
||||
return findTicket(ticketId, [tenantId]) ?? findByExternalId(tenantId, ticketId);
|
||||
|
||||
+51
-5
@@ -55,6 +55,15 @@ export interface RunOptions {
|
||||
* zapis do udalosti ticketu je potreba puvodni prijata zprava, ne smes.
|
||||
*/
|
||||
trigger?: Record<string, unknown>;
|
||||
/**
|
||||
* Jmena parametru podle jejich ID.
|
||||
*
|
||||
* Podminka si pamatuje **ID** parametru, aby ji prejmenovani nerozbilo. Data
|
||||
* ale prichazeji pod **jmenem**: parametr `f_9x1` nese hodnotu z klice
|
||||
* `callSid`, vystup kroku `st_kontakt.idoklad.found` lezi pod `found`. Bez
|
||||
* teto tabulky se podminka nema o co oprit a tise vychazi jako prazdna.
|
||||
*/
|
||||
fieldNames?: Record<string, string>;
|
||||
}
|
||||
|
||||
export interface StepResult {
|
||||
@@ -133,11 +142,11 @@ export async function runFlow(
|
||||
}
|
||||
|
||||
if (step.kind === 'condition') {
|
||||
const branch = evaluate(step, working) ? 'yes' : 'no';
|
||||
const branch = evaluate(step, working, options) ? 'yes' : 'no';
|
||||
results.push({
|
||||
stepId: step.id,
|
||||
kind: 'condition',
|
||||
label: describeCondition(step, working),
|
||||
label: describeCondition(step, working, options),
|
||||
ok: true,
|
||||
branch,
|
||||
summary: branch === 'yes' ? 'splněno' : 'nesplněno',
|
||||
@@ -459,12 +468,47 @@ function stringify(value: unknown): string {
|
||||
return JSON.stringify(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Hodnota, na kterou se podminka pta.
|
||||
*
|
||||
* Zkousi se v poradi od nejpresnejsiho: presny klic, pak jmeno parametru podle
|
||||
* jeho ID, pak `krok.jmeno`, nakonec cesta do struktury. Duvod je v tom, ze ID
|
||||
* parametru a klic v datech nejsou totez - podminka si drzi ID, aby ji
|
||||
* prejmenovani nerozbilo, ale data chodi pod jmenem.
|
||||
*/
|
||||
function conditionValue(
|
||||
fieldId: string,
|
||||
context: RunContext,
|
||||
options: RunOptions,
|
||||
): unknown {
|
||||
const candidates = [fieldId];
|
||||
|
||||
const name = options.fieldNames?.[fieldId];
|
||||
if (name) {
|
||||
candidates.push(name);
|
||||
// Vystup kroku lezi i pod `krok.jmeno`. Tahle podoba je presnejsi nez
|
||||
// hole jmeno, ktere pozdejsi krok se stejnym vystupem prepise.
|
||||
const step = fieldId.split('.')[0];
|
||||
if (step !== fieldId) candidates.push(`${step}.${name}`);
|
||||
}
|
||||
|
||||
candidates.push(nameOf(fieldId));
|
||||
|
||||
for (const key of candidates) {
|
||||
const value = context[key];
|
||||
if (value !== undefined) return value;
|
||||
}
|
||||
|
||||
return getPath(context, fieldId);
|
||||
}
|
||||
|
||||
/** Vyhodnoti podminku nad kontextem. */
|
||||
function evaluate(
|
||||
step: Extract<FlowStep, { kind: 'condition' }>,
|
||||
context: RunContext,
|
||||
options: RunOptions,
|
||||
): boolean {
|
||||
const raw = context[step.fieldId] ?? context[nameOf(step.fieldId)];
|
||||
const raw = conditionValue(step.fieldId, context, options);
|
||||
const value = raw === undefined || raw === null ? '' : stringify(raw);
|
||||
const expected = step.value ?? '';
|
||||
|
||||
@@ -519,10 +563,12 @@ function compare(operator: ConditionOperator, value: string, expected: string):
|
||||
function describeCondition(
|
||||
step: Extract<FlowStep, { kind: 'condition' }>,
|
||||
context: RunContext,
|
||||
options: RunOptions,
|
||||
): string {
|
||||
const raw = context[step.fieldId] ?? context[nameOf(step.fieldId)];
|
||||
const raw = conditionValue(step.fieldId, context, options);
|
||||
const value = raw === undefined || raw === null ? '(prázdné)' : stringify(raw);
|
||||
const name = nameOf(step.fieldId);
|
||||
// V logu ma stat jmeno, ktere clovek zna z builderu, ne vnitrni ID.
|
||||
const name = options.fieldNames?.[step.fieldId] ?? nameOf(step.fieldId);
|
||||
|
||||
if (isUnary(step.operator)) return `Podmínka: ${name} ${step.operator} (${value})`;
|
||||
return `Podmínka: ${name} ${step.operator} ${step.value ?? ''} (${value})`;
|
||||
|
||||
@@ -17,6 +17,8 @@ import { createIncident, findOpenIncident } from '../data/incidentStore.js';
|
||||
import { publish } from '../events/bus.js';
|
||||
import { withRun } from './context.js';
|
||||
import { runFlow, type RunResult } from './executor.js';
|
||||
import { collectScopes } from '../data/flowScope.js';
|
||||
import type { AutomationFlow } from '../data/automationStore.js';
|
||||
import {
|
||||
claimBatch,
|
||||
markDone,
|
||||
@@ -117,6 +119,9 @@ async function runOne(item: QueueItem): Promise<void> {
|
||||
runFlow(automation.flow.steps, item.payload, {
|
||||
tenantId: item.tenantId,
|
||||
ticketId: item.ticketId,
|
||||
// Podminka si drzi ID parametru, data chodi pod jmenem. Bez teto
|
||||
// tabulky by se nemela o co oprit, viz RunOptions.fieldNames.
|
||||
fieldNames: fieldNamesOf(automation.flow),
|
||||
// Klic je stabilni na beh, takze opakovany pokus nevystavi druhou fakturu.
|
||||
idempotencyKey: `run:${item.id}`,
|
||||
}),
|
||||
@@ -243,3 +248,10 @@ function sleep(ms: number): Promise<void> {
|
||||
export function workerEnabled(): boolean {
|
||||
return config.workerEnabled;
|
||||
}
|
||||
|
||||
/** Jmena parametru podle jejich ID, vcetne vystupu kroku. */
|
||||
function fieldNamesOf(flow: AutomationFlow): Record<string, string> {
|
||||
const names: Record<string, string> = {};
|
||||
for (const [id, field] of collectScopes(flow).all) names[id] = field.name;
|
||||
return names;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user