Vystup z vetve plati i za podminkou, samostatny krok Zalozit kontakt

Bezny postup nesel poskladat z kroku: najdi podle ICO, kdyz neni zkus mail,
kdyz porad neni zaloz - ID dava jednou jedna vetev a jednou druha, ale
rozsah vystupy z vetvi za podminku nepoustel. Slucovat kvuli tomu hledani
a zakladani do jednoho kroku bylo obejiti nasi chyby, ne reseni.

- Vystup z vetve je za podminkou k dispozici, jen jako nepovinny. Ze muze
  chybet, se neztratilo: builder to u pole ukaze a pri behu se dosadi
  prazdno.
- Vystup se stejnym jmenem uz z nabidky nemaze ten starsi. Po druhem
  hledani kontaktu zmizelo ID z prvniho, tedy to, co je v tu chvili
  potreba. Odkaz se jmenem kroku je jednoznacny.
- Duplicitni jmena u vystupu kroku uz nejsou nedodelek. Konflikt zustava
  mezi parametry spoustece, kde zadny prefix neni.
- Novy krok Zalozit kontakt. Nic nedohledava, hledani je vlastni krok.
  Najit nebo zalozit zustava pro toho, komu staci jistota jednim krokem.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
JiriUhlir
2026-08-20 09:01:57 +02:00
co-authored by Claude Opus 5
parent c62902bfb6
commit 6297bbf480
6 changed files with 249 additions and 41 deletions
+6 -2
View File
@@ -395,8 +395,12 @@ export function collectFlowIssues(flow: AutomationFlow): string[] {
}
}
walk(step.yes, new Set(roots));
walk(step.no, new Set(roots));
/*
* Koreny z vetvi se **nesou dal**. Vystup z vetve je za podminkou
* k dispozici, jen nepovinne - viz data/flowScope.ts.
*/
walk(step.yes, roots);
walk(step.no, roots);
}
};
walk(flow.steps, sampleRoots);
+62 -24
View File
@@ -5,10 +5,19 @@
* vrati `customerKnown`, "zalozit ticket" vrati `newTicketId`. Podminka i sablona
* se pak muzou odkazat na cokoliv, co uz **pred nimi** vzniklo.
*
* Dve pravidla, na kterych to stoji:
* - krok vidi jen to, co je pred nim ve stejne sekvenci, a to, co videl jeho rodic,
* - vetev podminky NEPRIDAVA nic do sekvence za podminkou. Vetev totiz nemusela
* probehnout, takze spolehat se na jeji vystup by byla past.
* Pravidla:
* - krok vidi to, co je pred nim ve stejne sekvenci, a to, co videl jeho rodic,
* - vystupy z vetvi podminky jsou za podminkou **k dispozici, ale nepovinne**.
*
* Druhe pravidlo bylo drive prisnejsi: vetev nepridavala nic. Znelo to opatrne,
* ale znamenalo to, ze zcela bezny postup nesel poskladat z kroku - "najdi
* zakaznika, a kdyz neni, zaloz ho, pak pouzij jeho ID" konci tim, ze ID dava
* jednou jedna vetev a jednou druha. Prisnost se tim neziskala, jen se obchazela
* tim, ze se dva kroky slouci do jednoho.
*
* Ze hodnota **muze chybet**, se neztratilo: pole je `required: false`, takze
* builder u nej ukaze, ze vznika jen ve vetvi. Kdo se na nej odkaze, dostane pri
* behu prazdno - stejne jako u ceho jineho, co neprislo.
*
* POZOR: stejna logika je na klientovi v web/src/lib/flow.ts.
* Server je autorita, kopie na klientovi existuje jen kvuli UI.
@@ -46,15 +55,46 @@ export function collectScopes(flow: AutomationFlow): FlowScopes {
const triggerFields = flow.trigger?.fields ?? [];
for (const field of triggerFields) all.set(field.id, field);
const walk = (steps: FlowStep[], inherited: TriggerField[]) => {
/**
* Projde sekvenci a vrati pole, ktera v ni **vznikla**.
*
* Vraci se jen pribytek, ne cely rozsah. Volajici z nej sklada, co je videt
* za podminkou, a zdedena pole uz tam samozrejme jsou.
*/
const walk = (steps: FlowStep[], inherited: TriggerField[]): TriggerField[] => {
let available = inherited;
const produced: TriggerField[] = [];
/**
* Prida vystupy do rozsahu.
*
* **Nic se neodebira.** Driv novejsi vystup starsi se stejnym jmenem
* z nabidky vyhodil, takze po druhem hledani kontaktu zmizelo ID z toho
* prvniho - presne to, co clovek v tu chvili potrebuje. Odkaz s jmenem
* kroku (`{{st_ico.contactId}}`) je jednoznacny, takze duvod k mazani neni.
*
* Hole jmeno (`{{contactId}}`) porad znamena ten posledni vystup; tak to
* dela runtime a tady se to neresi.
*/
const add = (outputs: TriggerField[]) => {
for (const output of outputs) all.set(output.id, output);
available = [...available, ...outputs];
produced.push(...outputs);
};
for (const step of steps) {
byStep.set(step.id, available);
if (step.kind === 'condition') {
walk(step.yes, available);
walk(step.no, available);
const fromYes = walk(step.yes, available);
const fromNo = walk(step.no, available);
/*
* Za podminkou plati obojí, ale jako nepovinne - probehla jen jedna
* vetev. Kdyz obe vetve daji pole stejneho jmena, je to ten bezny
* pripad "jednou najdeno, jednou zalozeno" a duplicita to neni.
*/
add([...fromYes, ...fromNo].map((field) => ({ ...field, required: false })));
continue;
}
@@ -74,29 +114,17 @@ export function collectScopes(flow: AutomationFlow): FlowScopes {
* Po smycce je k dispozici seznam vysledku za kazdou polozku. Prave to
* se posila dal jako radky objednavky nebo faktury.
*/
const results: TriggerField[] = [
add([
{ id: `${step.id}.results`, name: `${step.id}.results`, type: 'list', required: true },
{ id: `${step.id}.count`, name: `${step.id}.count`, type: 'number', required: true },
];
for (const output of results) all.set(output.id, output);
available = [...available, ...results];
]);
continue;
}
const outputs = outputsOf(step);
if (outputs.length === 0) continue;
for (const output of outputs) all.set(output.id, output);
/*
* Novejsi vystup **prekryje** starsi se stejnym jmenem. Presne to dela
* i runtime pri dosazovani do sablon, takze `{{assigneeId}}` znamena
* vzdycky ten posledni. Kdyby se tady jen pricitalo, hlasil by builder
* konflikt tam, kde zadny neni - napriklad kdyz krok vraci totez, co uz
* dal spoustec.
*/
const names = new Set(outputs.map((output) => output.name));
available = [...available.filter((field) => !names.has(field.name)), ...outputs];
add(outputsOf(step));
}
return produced;
};
walk(flow.steps, triggerFields);
@@ -116,7 +144,17 @@ export function scopeFor(scopes: FlowScopes, stepId: string): TriggerField[] {
export function duplicateNames(fields: TriggerField[]): string[] {
const seen = new Set<string>();
const duplicates = new Set<string>();
for (const field of fields) {
/*
* Vystupy kroku se sem nepocitaji. Dva kroky, ktere vraci `contactId`, jsou
* bezna vec (hledani a zalozeni) a odkaz `{{st_zaloz.contactId}}` je
* jednoznacny. Hlasit to jako konflikt by znamenalo psat varovani k tomu,
* co je spravne. Duplicita mezi **parametry spoustece** konflikt je - tam
* zadny prefix neni a v sablone by neslo poznat, ktery se mysli.
*/
if (field.id.includes('.')) continue;
if (seen.has(field.name)) duplicates.add(field.name);
seen.add(field.name);
}