From 6abac82d5e87557329929af131fb68720246c7f6 Mon Sep 17 00:00:00 2001 From: JiriUhlir <149317995+JiriUhlir@users.noreply.github.com> Date: Thu, 13 Aug 2026 17:03:03 +0200 Subject: [PATCH] Krok prirazeni resiteli a ukazkova data mimo zivy provoz Opraveno: `ticket/assign` nemel vykonnou cast, takze krok "Prirad resiteli" vzdycky selhal hlaskou "operace nema vykonnou cast". Doplnen jako vnitrni krok vedle prirazeni nejvolnejsimu ze skupiny a prirazeni podle externiho ID. Ukazkova automatizace "Smerovani ticketu na resitele" je vypnuta. Zapnuta prebirala tickety, ktere uz nekomu patrily podle skutecne automatizace zakaznika, a prepsat rucni nebo cizi rozhodnuti je to nejhorsi, co muze automatizace udelat. Do udaju spoustece zaroven pribylo `assigned` a `knownCustomer`, aby slo napsat podminku "uz je prirazeny, nesahej na to". `ticket/upsert` prijima `status`: kdyz hodnota patri mezi nase ctyri stavy, nastavi stav, jinak se ulozi jako faze. Cizi aplikace posila svoje stavy hovoru a nas zivotni cyklus je pevny, protoze se z nej pocitaji statistiky. Do shrnuti kroku se napise, co se stalo. Overeno pripadem z provozu: callSid do externiho ID, voicebotId jako stitek, status jako stav. Ctyri zpravy o trech hovorech daly tri tickety, filtr na stitek vratil jen hovory daneho voicebota a widget je spocital vcetne prokliku na vyfiltrovany seznam. Co-Authored-By: Claude Opus 5 (1M context) --- documentation/99-zmeny.md | 27 +++++++++++++++ src/data/automationStore.ts | 9 ++++- src/data/services.ts | 14 +++++++- src/runtime/builtinSteps.ts | 66 +++++++++++++++++++++++++++++++++---- src/runtime/triggers.ts | 7 ++++ 5 files changed, 115 insertions(+), 8 deletions(-) diff --git a/documentation/99-zmeny.md b/documentation/99-zmeny.md index 014eb41..1e469ba 100644 --- a/documentation/99-zmeny.md +++ b/documentation/99-zmeny.md @@ -2,6 +2,33 @@ Nejnovejsi nahore. +## 2026-08-13 - krok prirazeni a ukazkova data nesahaji na provoz + +### Opraveno + +- **`ticket/assign` nemel vykonnou cast**, takze krok "Prirad resiteli" vzdycky + selhal hlaskou "operace nema vykonnou cast". Doplnen jako vnitrni krok. +- **Ukazkova automatizace "Smerovani ticketu na resitele" je vypnuta.** + Zapnuta prebirala tickety, ktere uz nekomu patrily podle skutecne + automatizace zakaznika - ukazkova data nemaji sahat na zivy provoz. +- Do udaju spoustece pribylo `assigned` a `knownCustomer`, aby slo napsat + podminku "uz je prirazeny, nesahej na to". Bez toho nemela z ceho vychazet. + +### Zmeneno + +- `ticket/upsert` prijima `status`. Kdyz hodnota patri mezi nase ctyri stavy, + nastavi stav; jinak se ulozi jako **faze**, protoze to presne je - cizi + aplikace posila svoje stavy hovoru a nas zivotni cyklus je pevny. Do shrnuti + kroku se napise, co se stalo, aby to nebylo kouzlo. + +### Overeno + +Pripad z provozu: telo `{callSid, status, voicebotId}`, callSid do externiho +ID, voicebotId jako stitek, status jako stav. Ctyri zpravy o trech hovorech +daly **tri tickety**, filtr na stitek vratil jen hovory daneho voicebota +a widget je spocital (vb-77: 2, vb-88: 1) vcetne prokliku na vyfiltrovany +seznam. + ## 2026-08-13 - fronta, worker a spoustece Popis v [20-fronta-a-runtime.md](20-fronta-a-runtime.md). diff --git a/src/data/automationStore.ts b/src/data/automationStore.ts index 55fb99a..db4bca8 100644 --- a/src/data/automationStore.ts +++ b/src/data/automationStore.ts @@ -729,7 +729,14 @@ seed({ */ seed({ name: 'Směrování ticketů na řešitele', - enabled: true, + /* + * Ukazkova automatizace, ktera **meni data**, je vypnuta. + * + * Zapnuta by prebirala tickety, ktere uz nekomu patri podle skutecne + * automatizace zakaznika - ukazkova data nemaji sahat na zivy provoz. + * Kdo si ji chce vyzkouset, zapne si ji. + */ + enabled: false, runsToday: 103, successRate: 100, avgDurationMs: 310, diff --git a/src/data/services.ts b/src/data/services.ts index 9efaada..7c61a29 100644 --- a/src/data/services.ts +++ b/src/data/services.ts @@ -516,6 +516,15 @@ export const services: Service[] = [ { value: 'critical', label: 'Kritická' }, ], }, + { + id: 'status', + label: 'Stav', + kind: 'text', + required: false, + hint: + 'Nový, v řešení, čeká nebo vyřešeno. Jinou hodnotu (například stav hovoru ' + + 'od voicebota) uložíme jako fázi, protože to přesně je.', + }, { id: 'stage', label: 'Fáze', @@ -699,7 +708,10 @@ export const services: Service[] = [ { id: 'assign', name: 'Přiřadit řešiteli', - description: 'Předá ticket konkrétnímu člověku. Objeví se mu mezi jeho tickety.', + description: + 'Předá ticket konkrétnímu člověku. Objeví se mu mezi jeho tickety a dostane ' + + 'upozornění. Když nechcete vybírat ručně, použijte Předat nejvolnějšímu ze skupiny.', + implementation: 'script', inputs: [ { id: 'ticketId', diff --git a/src/runtime/builtinSteps.ts b/src/runtime/builtinSteps.ts index 61b662a..7934657 100644 --- a/src/runtime/builtinSteps.ts +++ b/src/runtime/builtinSteps.ts @@ -10,7 +10,7 @@ */ import { createIncident } from '../data/incidentStore.js'; -import { findGroup, findPersonByExternalId, listPeople } from '../data/people.js'; +import { findGroup, findPerson, findPersonByExternalId, listPeople } from '../data/people.js'; import { findTicketType } from '../data/ticketTypes.js'; import { assignTicket, @@ -92,19 +92,39 @@ const handlers: Record = { }); /* - * Faze se nastavi az po zalozeni: u noveho ticketu jeste neni typ, takze - * by se nemela proti cemu overit. Prazdna hodnota nic nemeni. + * Stav a faze se nastavuji az po zalozeni: u noveho ticketu jeste neni typ, + * takze by se faze nemela proti cemu overit. + * + * `status` je shovivavy zamerne. Cizi aplikace posila svoje stavy + * (`ringing`, `completed`) a nas zivotni cyklus ma pevne ctyri hodnoty. + * Kdyz hodnota mezi ne patri, nastavi se stav; kdyz ne, ulozi se jako + * **faze**, protoze to presne je - postup uvnitr typu. Do shrnuti kroku + * se napise, co se stalo, aby to nebylo kouzlo. */ - if (inputs.stage?.trim()) { + const wanted = inputs.status?.trim(); + const asStage = inputs.stage?.trim(); + let note = ''; + + if (wanted && statuses.includes(wanted as TicketStatus)) { + updateTicketStatus(result.ticket.id, wanted as TicketStatus, [context.tenantId]); + note = `, stav ${wanted}`; + } + + const stage = asStage || (wanted && !statuses.includes(wanted as TicketStatus) ? wanted : ''); + if (stage) { const type = result.ticket.typeId ? findTicketType(result.ticket.typeId) : undefined; - setTicketStage(result.ticket.id, inputs.stage.trim(), type?.statuses ?? [], [ + const changed = setTicketStage(result.ticket.id, stage, type?.statuses ?? [], [ context.tenantId, ]); + note += changed + ? `, fáze ${stage}` + : `, fáze ${stage} NEULOŽENA (není ve workflow typu)`; } return { ok: true, - summary: result.created ? `založen ${result.ticket.id}` : `doplněn ${result.ticket.id}`, + summary: + (result.created ? `založen ${result.ticket.id}` : `doplněn ${result.ticket.id}`) + note, detail: null, ticketId: result.ticket.id, outputs: { @@ -166,6 +186,40 @@ const handlers: Record = { }; }, + /** + * Preda ticket konkretnimu cloveku. + * + * Nejjednodussi zpusob prirazeni: vim, komu to patri, a reknu to. Kdyz je + * lidi vic a nechci vybirat rucne, je na to `assign-least-busy`. + */ + 'ticket/assign': (inputs, context) => { + const ticketId = inputs.ticketId?.trim() || context.ticketId; + if (!ticketId) return missing('ticket'); + + const assigneeId = inputs.assigneeId?.trim(); + if (!assigneeId) return missing('řešitele'); + + const person = findPerson(assigneeId); + if (!person || person.tenantId !== context.tenantId) { + return { + ok: false, + summary: `řešitel ${assigneeId} neexistuje`, + detail: 'Zkontrolujte ID řešitele v kroku. Řešitelé se spravují v Nastavení.', + outputs: {}, + }; + } + + const updated = assignTicket(ticketId, person.id, [context.tenantId]); + if (!updated) return { ok: false, summary: 'přiřazení se nepodařilo', outputs: {} }; + + return { + ok: true, + summary: person.name, + detail: null, + outputs: { assigneeId: person.id, assigneeName: person.name }, + }; + }, + /** * Preda ticket cloveku podle ID z cizi aplikace. * diff --git a/src/runtime/triggers.ts b/src/runtime/triggers.ts index 0ee8e8b..652a5d3 100644 --- a/src/runtime/triggers.ts +++ b/src/runtime/triggers.ts @@ -172,7 +172,14 @@ export function ticketPayload(ticket: Ticket): Record { stage: ticket.stage, tags: ticket.tags, assigneeId: ticket.assignee?.id ?? null, + /* + * Aby slo napsat "uz je prirazeny, nesahej na to". Bez toho podminka + * nema z ceho vychazet a automatizace prebere i ticket, ktery uz nekomu + * patri - a prepsat rucni rozhodnuti je to nejhorsi, co muze udelat. + */ + assigned: ticket.assignee !== null, assigneeGroupId: ticket.assigneeGroupId, + knownCustomer: ticket.customer.id !== null, company: ticket.customer.company, contact: ticket.customer.contact, reply: ticket.customer.reply,