Obsah ticketu, nastaveni kroku a vystupy kroku
Ticket dostal telo (body) a odkaz na zdrojovou zpravu. Predmet je shrnuti,
telo je cely text pozadavku.
Akce maji nastavitelna pole (inputs) se sablonami {{parametr}}. Zatim ticket,
kanaly, CRM a AI, ostatni maji jen napovedu.
Krok vidi parametry spoustece plus vystupy kroku pred nim, takze jde vlozit
predvalidaci a vetvit se podle jejiho vysledku. Vetev podminky nepridava nic
do sekvence za podminkou.
Nove konektory Facebook Messenger a Instagram, nova akce RAYNET Dohledat firmu.
Ctyri vzorove automatizace v rozdeleni jedna na kanal pro prijem
a jedna spolecna pro smerovani na resitele.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
52b190bfbc
commit
dd021b5f69
+30
-4
@@ -17,6 +17,7 @@ import {
|
||||
findOperation,
|
||||
providedFieldsFor,
|
||||
} from '../data/connectors.js';
|
||||
import { collectScopes } from '../data/flowScope.js';
|
||||
import { listIncidents } from '../data/incidentStore.js';
|
||||
import { getSummary } from '../data/mock.js';
|
||||
import { findPersonByEmail, listPeople } from '../data/people.js';
|
||||
@@ -56,7 +57,15 @@ dashboardRouter.get('/people', (req, res) => {
|
||||
});
|
||||
|
||||
const ticketStatuses: TicketStatus[] = ['new', 'open', 'waiting', 'resolved'];
|
||||
const ticketChannels: TicketChannel[] = ['whatsapp', 'email', 'voice', 'form', 'portal'];
|
||||
const ticketChannels: TicketChannel[] = [
|
||||
'whatsapp',
|
||||
'facebook',
|
||||
'instagram',
|
||||
'email',
|
||||
'voice',
|
||||
'form',
|
||||
'portal',
|
||||
];
|
||||
|
||||
/**
|
||||
* Filtr ze query parametru. Nesmyslnou hodnotu zahodime a zalogujeme -
|
||||
@@ -198,6 +207,8 @@ const stepSchema: z.ZodType<FlowStep> = z.lazy(() =>
|
||||
kind: z.literal('action'),
|
||||
connectorId: z.string().min(1),
|
||||
operationId: z.string().min(1),
|
||||
// Klic je ID pole z katalogu, hodnota sablona. Overuje se nize.
|
||||
inputs: z.record(z.string()).optional(),
|
||||
}),
|
||||
z.object({
|
||||
id: z.string().min(1),
|
||||
@@ -298,12 +309,14 @@ function validateFlowReferences(flow: z.infer<typeof flowSchema>): string[] {
|
||||
names.add(field.name);
|
||||
}
|
||||
|
||||
const fieldById = new Map(flow.trigger.fields.map((field) => [field.id, field]));
|
||||
// Scope rika, co je v kterem miste stromu videt. Podminka se smi ptat
|
||||
// jen na parametry, ktere pred ni uz vznikly.
|
||||
const scopes = collectScopes(flow);
|
||||
|
||||
const walk = (steps: FlowStep[]) => {
|
||||
for (const step of steps) {
|
||||
if (step.kind === 'condition') {
|
||||
const field = fieldById.get(step.fieldId);
|
||||
const field = scopes.all.get(step.fieldId);
|
||||
if (!field) {
|
||||
problems.push(`Podmínka odkazuje na neexistující parametr (${step.fieldId}).`);
|
||||
} else if (!operatorAllowedForType(step.operator, field.type)) {
|
||||
@@ -315,8 +328,21 @@ function validateFlowReferences(flow: z.infer<typeof flowSchema>): string[] {
|
||||
walk(step.no);
|
||||
continue;
|
||||
}
|
||||
if (!findOperation(step.connectorId, step.operationId, 'action')) {
|
||||
const action = findOperation(step.connectorId, step.operationId, 'action');
|
||||
if (!action) {
|
||||
problems.push(`Akce ${step.connectorId}/${step.operationId} neexistuje v katalogu.`);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Nastaveni musi sedet na katalog. Neznamy klic je rozbity strom,
|
||||
// ne nedodelek - ulozit ho by znamenalo drzet data, ktera nikdo neprecte.
|
||||
const allowed = new Set((action.inputs ?? []).map((input) => input.id));
|
||||
for (const key of Object.keys(step.inputs ?? {})) {
|
||||
if (!allowed.has(key)) {
|
||||
problems.push(
|
||||
`Akce ${step.connectorId}/${step.operationId} nemá nastavitelné pole „${key}".`,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
+47
-1
@@ -30,8 +30,12 @@ const schema = z.discriminatedUnion('action', [
|
||||
z.object({
|
||||
action: z.literal('ticket.created'),
|
||||
/** Odkud pozadavek prisel. Podle toho se poskladá i log. */
|
||||
channel: z.enum(['whatsapp', 'email', 'voice', 'form', 'portal']).optional(),
|
||||
channel: z
|
||||
.enum(['whatsapp', 'facebook', 'instagram', 'email', 'voice', 'form', 'portal'])
|
||||
.optional(),
|
||||
subject: z.string().trim().min(3).optional(),
|
||||
/** Cely text pozadavku. Bez nej se doplni z predmetu a kanalu. */
|
||||
body: z.string().trim().min(3).optional(),
|
||||
contact: z.string().trim().min(2).optional(),
|
||||
priority: z.enum(['low', 'normal', 'high', 'critical']).optional(),
|
||||
/** false = CRM firmu nedohleda, ticket zustane bez zakaznika a bez resitele. */
|
||||
@@ -71,6 +75,8 @@ const channelPresets: Record<
|
||||
subjects: string[];
|
||||
contact: string;
|
||||
reply: string;
|
||||
/** Odkaz na zdrojovou zpravu u poskytovatele. null tam, kde zadny neni. */
|
||||
sourceRef: string | null;
|
||||
/** Co spoustec vratil. `%s` se nahradi predmetem. */
|
||||
payload: (contact: string, reply: string, subject: string) => string;
|
||||
}
|
||||
@@ -86,9 +92,40 @@ const channelPresets: Record<
|
||||
],
|
||||
contact: 'Bistro Kolektiv',
|
||||
reply: '+420 774 902 331',
|
||||
sourceRef: 'wamid.HBgLNDIwNzc0OTAyMzMx',
|
||||
payload: (contact, reply, subject) =>
|
||||
`{ "from": "${reply.replace(/\s/g, '')}", "profileName": "${contact}", "text": "${subject}" }`,
|
||||
},
|
||||
facebook: {
|
||||
connectorId: 'facebook',
|
||||
operationId: 'message-received',
|
||||
triggerLabel: 'Přijata zpráva z Facebook Messengeru',
|
||||
subjects: [
|
||||
'Dotaz na otevírací dobu poboček',
|
||||
'Nefunguje objednávkový formulář',
|
||||
'Reklamace poškozené zásilky',
|
||||
],
|
||||
contact: 'Martin Dvořák',
|
||||
reply: 'fb:1029384756',
|
||||
sourceRef: 'mid.simulace.fb',
|
||||
payload: (contact, reply, subject) =>
|
||||
`{ "senderId": "${reply.replace('fb:', '')}", "senderName": "${contact}", "text": "${subject}" }`,
|
||||
},
|
||||
instagram: {
|
||||
connectorId: 'instagram',
|
||||
operationId: 'message-received',
|
||||
triggerLabel: 'Přijata přímá zpráva na Instagramu',
|
||||
subjects: [
|
||||
'Máte to ještě skladem?',
|
||||
'Kdy dorazí objednávka',
|
||||
'Jde změnit adresa doručení',
|
||||
],
|
||||
contact: 'kavarna.uzastavky',
|
||||
reply: 'ig:kavarna.uzastavky',
|
||||
sourceRef: 'ig.simulace',
|
||||
payload: (contact, _reply, subject) =>
|
||||
`{ "username": "${contact}", "text": "${subject}" }`,
|
||||
},
|
||||
email: {
|
||||
connectorId: 'email',
|
||||
operationId: 'received',
|
||||
@@ -100,6 +137,7 @@ const channelPresets: Record<
|
||||
],
|
||||
contact: 'Tomáš Beran',
|
||||
reply: 'tomas.beran@nordis.cz',
|
||||
sourceRef: '<sim-4f8a@nordis.cz>',
|
||||
payload: (_contact, reply, subject) =>
|
||||
`{ "from": "${reply}", "subject": "${subject}", "attachments": 1 }`,
|
||||
},
|
||||
@@ -114,6 +152,7 @@ const channelPresets: Record<
|
||||
],
|
||||
contact: 'Petra Klientová',
|
||||
reply: '+420 601 118 224',
|
||||
sourceRef: 'cl_simulace',
|
||||
payload: (_contact, reply, _subject) =>
|
||||
`{ "callId": "cl_${Math.floor(Math.random() * 90_000) + 10_000}", "from": "${reply.replace(/\s/g, '')}", "durationSec": 74 }`,
|
||||
},
|
||||
@@ -128,6 +167,7 @@ const channelPresets: Record<
|
||||
],
|
||||
contact: 'Jana Sedláčková',
|
||||
reply: 'jana.sedlackova@logitrans.cz',
|
||||
sourceRef: null,
|
||||
payload: (contact, _reply, subject) =>
|
||||
`{ "company": "LogiTrans", "contact": "${contact}", "topic": "${subject}" }`,
|
||||
},
|
||||
@@ -142,6 +182,7 @@ const channelPresets: Record<
|
||||
],
|
||||
contact: 'Interní požadavek',
|
||||
reply: 'portal@automia.cz',
|
||||
sourceRef: null,
|
||||
payload: (contact, _reply, subject) => `{ "createdBy": "${contact}", "subject": "${subject}" }`,
|
||||
},
|
||||
};
|
||||
@@ -161,6 +202,8 @@ const routingRule: Record<TicketChannel, { id: string; name: string; why: string
|
||||
voice: { id: 'ppl_novakova', name: 'Eva Nováková', why: 'voice -> voiceboti' },
|
||||
email: { id: 'ppl_kriz', name: 'Martin Kříž', why: 'email -> integrace' },
|
||||
whatsapp: { id: 'ppl_vomacka', name: 'Karel Vomáčka', why: 'whatsapp -> servicedesk' },
|
||||
facebook: { id: 'ppl_vomacka', name: 'Karel Vomáčka', why: 'facebook -> servicedesk' },
|
||||
instagram: { id: 'ppl_vomacka', name: 'Karel Vomáčka', why: 'instagram -> servicedesk' },
|
||||
form: { id: 'ppl_vomacka', name: 'Karel Vomáčka', why: 'form -> servicedesk' },
|
||||
portal: { id: 'ppl_uhlir', name: 'Jiří Uhlíř', why: 'portal -> vedoucí týmu' },
|
||||
};
|
||||
@@ -314,6 +357,9 @@ simulateRouter.post('/', (req, res) => {
|
||||
|
||||
const ticket = createTicket({
|
||||
subject,
|
||||
// Kanal posila cely text, predmet je jen jeho zkraceni.
|
||||
body: input.body ?? `${subject}. Poslal ${contact} přes ${preset.triggerLabel}.`,
|
||||
sourceRef: preset.sourceRef,
|
||||
channel,
|
||||
priority,
|
||||
customer: {
|
||||
|
||||
Reference in New Issue
Block a user