Accumulated work on the dispatch/legacy-writeback branch: - Communications UI: CommunicationsPage, ConversationFullPage, DepartmentBoard, PipelineBoard, ReaderStack, Orchestrator/NewTicket/ServiceStatus/Outbox dialogs; hub gmail.js, ticket-collab.js, outbox.js, coupon-triage.js, client-diag.js. - Billing/sync mirror (F→ERPNext): legacy-payments.js, legacy-sync.js, sync-orchestrator.js, supplier-invoices.js, municipality.js + incremental migration scripts; LegacySyncPage, SupplierInvoices + negative-billing / terminated-active reports. - Roster/campaigns/network/voice: roster + roster-assistant, campaigns, giftbit, olt-snmp, traccar, twilio, vision, tech-absence-sms, ai/agent/config/helpers, legacy-dispatch-sync; ops PlanificationPage, RapportsPage, Settings, Tickets, ClientDetail updates. - docs/ PLATFORM_GUIDE + UI_AND_OPTIMIZATION; .gitignore __pycache__. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
260 lines
9.8 KiB
JavaScript
260 lines
9.8 KiB
JavaScript
/**
|
|
* kind-catalogs.js — Pluggable definitions for flow step kinds.
|
|
*
|
|
* A "catalog" tells the FlowEditor which step kinds are allowed and how
|
|
* to render their editor form. One editor, many domains (project, agent,
|
|
* customer onboarding, etc.) — same UI, different catalogs.
|
|
*
|
|
* Field descriptor shape:
|
|
* {
|
|
* name: 'subject', // flattens into step.payload[name]
|
|
* type: 'text'|'textarea'|'number'|'select'|'datetime'|'webhook',
|
|
* label: 'Sujet',
|
|
* required: true|false,
|
|
* options: [...], // for select
|
|
* placeholder: '...',
|
|
* default: 'value',
|
|
* help: 'tooltip text',
|
|
* }
|
|
*/
|
|
|
|
// -----------------------------------------------------------------------------
|
|
// Trigger types (when a step executes) — shared across all catalogs
|
|
// -----------------------------------------------------------------------------
|
|
|
|
export const TRIGGER_TYPES = {
|
|
on_flow_start: {
|
|
label: 'Au démarrage du flow',
|
|
help: 'Cette étape s\'exécute dès que le flow démarre',
|
|
fields: [],
|
|
},
|
|
on_prev_complete: {
|
|
label: 'Après les dépendances (depends_on)',
|
|
help: 'Cette étape attend que les étapes listées dans depends_on soient complétées',
|
|
fields: [],
|
|
},
|
|
after_delay: {
|
|
label: 'Après un délai',
|
|
help: 'Cette étape attend X heures/jours après que ses dépendances soient complétées',
|
|
fields: [
|
|
{ name: 'delay_hours', type: 'number', label: 'Heures', placeholder: '24' },
|
|
{ name: 'delay_days', type: 'number', label: 'Jours', placeholder: '7' },
|
|
],
|
|
},
|
|
on_date: {
|
|
label: 'À une date précise',
|
|
help: 'Déclenchement à une date/heure fixe',
|
|
fields: [
|
|
{ name: 'at', type: 'datetime', label: 'Date/heure' },
|
|
],
|
|
},
|
|
on_webhook: {
|
|
label: 'Webhook externe reçu',
|
|
help: 'POST sur /flow/trigger/:run_id/:step_id depuis n8n/autre',
|
|
fields: [],
|
|
},
|
|
manual: {
|
|
label: 'Déclenchement manuel (bouton)',
|
|
help: 'L\'utilisateur clique un bouton pour déclencher',
|
|
fields: [],
|
|
},
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------
|
|
// Project kinds — for the project wizard + service orchestration flows
|
|
// -----------------------------------------------------------------------------
|
|
|
|
const JOB_TYPES = ['Installation', 'Réparation', 'Maintenance', 'Retrait', 'Dépannage', 'Autre']
|
|
const PRIORITIES = ['low', 'medium', 'high']
|
|
const GROUPS = ['Admin', 'Tech Targo', 'Support', 'NOC', 'Facturation']
|
|
|
|
export const PROJECT_KINDS = {
|
|
dispatch_job: {
|
|
label: 'Tâche dispatch',
|
|
icon: 'build',
|
|
color: '#6366f1',
|
|
fields: [
|
|
{ name: 'subject', type: 'text', label: 'Sujet', required: true },
|
|
{ name: 'job_type', type: 'select', label: 'Type', options: JOB_TYPES, default: 'Autre' },
|
|
{ name: 'priority', type: 'select', label: 'Priorité', options: PRIORITIES, default: 'medium' },
|
|
{ name: 'duration_h', type: 'number', label: 'Durée (h)', default: 1 },
|
|
{ name: 'assigned_group', type: 'select', label: 'Groupe', options: GROUPS, default: 'Tech Targo' },
|
|
{ name: 'on_open_webhook', type: 'text', label: 'Webhook à l\'ouverture (n8n)', placeholder: 'https://n8n.gigafibre.ca/webhook/...' },
|
|
{ name: 'on_close_webhook',type: 'text', label: 'Webhook à la fermeture (n8n)' },
|
|
{ name: 'merge_key', type: 'text', label: 'Merge key (optionnel)', help: 'Étapes avec le même merge_key fusionnent en une seule visite' },
|
|
],
|
|
},
|
|
issue: {
|
|
label: 'Ticket',
|
|
icon: 'confirmation_number',
|
|
color: '#f59e0b',
|
|
help: 'Crée un ticket (ERPNext Issue) quand le flow atteint cette étape — c\'est l\'action « créer un ticket ».',
|
|
fields: [
|
|
{ name: 'subject', type: 'text', label: 'Sujet', required: true },
|
|
{ name: 'description', type: 'textarea', label: 'Description' },
|
|
{ name: 'priority', type: 'select', label: 'Priorité', options: ['Low', 'Medium', 'High', 'Urgent'], default: 'Medium' },
|
|
{ name: 'issue_type', type: 'text', label: 'Type (texte libre)', placeholder: 'Suivi' },
|
|
],
|
|
},
|
|
notify: {
|
|
label: 'Notification (SMS/email)',
|
|
icon: 'send',
|
|
color: '#3b82f6',
|
|
fields: [
|
|
{ name: 'channel', type: 'select', label: 'Canal', options: ['sms', 'email'], default: 'sms' },
|
|
{ name: 'to', type: 'text', label: 'Destinataire (template)', placeholder: '{{customer.primary_phone}}', help: 'Supporte les templates {{customer.field}}' },
|
|
{ name: 'template_id', type: 'text', label: 'Template ID (depuis email-templates.js)', placeholder: 'welcome_residential' },
|
|
{ name: 'subject', type: 'text', label: 'Sujet (email uniquement)' },
|
|
{ name: 'body', type: 'textarea', label: 'Corps (si pas de template_id)' },
|
|
],
|
|
},
|
|
webhook: {
|
|
label: 'Webhook externe',
|
|
icon: 'webhook',
|
|
color: '#8b5cf6',
|
|
fields: [
|
|
{ name: 'url', type: 'text', label: 'URL', required: true, placeholder: 'https://n8n.gigafibre.ca/webhook/xxx' },
|
|
{ name: 'method', type: 'select', label: 'Méthode', options: ['POST', 'GET', 'PUT', 'DELETE'], default: 'POST' },
|
|
{ name: 'body_template', type: 'textarea', label: 'Body (JSON template)', placeholder: '{"customer": "{{customer.name}}", "contract": "{{contract.name}}"}' },
|
|
],
|
|
},
|
|
erp_update: {
|
|
label: 'Mise à jour ERPNext',
|
|
icon: 'edit_note',
|
|
color: '#10b981',
|
|
fields: [
|
|
{ name: 'doctype', type: 'text', label: 'DocType', required: true, placeholder: 'Customer' },
|
|
{ name: 'docname_ref', type: 'text', label: 'Nom du doc (template)', placeholder: '{{customer.name}}' },
|
|
{ name: 'fields_json', type: 'textarea', label: 'Champs à mettre à jour (JSON)', placeholder: '{"customer_group": "Active"}' },
|
|
],
|
|
},
|
|
wait: {
|
|
label: 'Attendre',
|
|
icon: 'hourglass_empty',
|
|
color: '#94a3b8',
|
|
help: 'Utilise le trigger « Après un délai » pour contrôler la durée',
|
|
fields: [],
|
|
},
|
|
condition: {
|
|
label: 'Condition (si / sinon)',
|
|
icon: 'fork_right',
|
|
color: '#eab308',
|
|
hasBranches: true,
|
|
branchLabels: { yes: 'Oui', no: 'Non' },
|
|
fields: [
|
|
{ name: 'field', type: 'text', label: 'Champ (chemin JSON)', placeholder: 'customer.primary_phone', required: true },
|
|
{ name: 'op', type: 'select', label: 'Opérateur',
|
|
options: ['==', '!=', '<', '>', '<=', '>=', 'in', 'not_in', 'empty', 'not_empty'], default: '==' },
|
|
{ name: 'value', type: 'text', label: 'Valeur', placeholder: 'Actif' },
|
|
],
|
|
},
|
|
subscription_activate: {
|
|
label: 'Activer l\'abonnement',
|
|
icon: 'autorenew',
|
|
color: '#ec4899',
|
|
fields: [
|
|
{ name: 'subscription_ref', type: 'text', label: 'Référence abonnement (template)', placeholder: '{{contract.subscription}}' },
|
|
],
|
|
},
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------
|
|
// Agent kinds — for the conversational agent flows (AgentFlowsPage)
|
|
// -----------------------------------------------------------------------------
|
|
// Preserved from the current AgentFlowsPage stepTypeLabels structure so the
|
|
// extracted FlowEditor can replace it as-is in a later refactor.
|
|
|
|
export const AGENT_KINDS = {
|
|
tool: {
|
|
label: 'Appel outil',
|
|
icon: 'settings',
|
|
color: '#6366f1',
|
|
fields: [
|
|
{ name: 'tool', type: 'text', label: 'Outil', placeholder: 'get_equipment' },
|
|
{ name: 'note', type: 'text', label: 'Note' },
|
|
],
|
|
},
|
|
condition: {
|
|
label: 'Condition',
|
|
icon: 'help',
|
|
color: '#eab308',
|
|
hasBranches: true,
|
|
branchLabels: { yes: 'Oui', no: 'Non' },
|
|
fields: [
|
|
{ name: 'field', type: 'text', label: 'Champ', placeholder: 'device.online' },
|
|
{ name: 'op', type: 'select', label: 'Opérateur', options: ['==', '!=', '<', '>', '<=', '>='] },
|
|
{ name: 'value', type: 'text', label: 'Valeur' },
|
|
],
|
|
},
|
|
switch: {
|
|
label: 'Switch',
|
|
icon: 'call_split',
|
|
color: '#f59e0b',
|
|
hasBranches: 'dynamic',
|
|
fields: [
|
|
{ name: 'field', type: 'text', label: 'Champ switch', placeholder: 'onu.alarm_type' },
|
|
],
|
|
},
|
|
respond: {
|
|
label: 'Réponse',
|
|
icon: 'chat',
|
|
color: '#10b981',
|
|
fields: [
|
|
{ name: 'message', type: 'textarea', label: 'Message', rows: 4 },
|
|
{ name: 'note', type: 'text', label: 'Note interne' },
|
|
],
|
|
},
|
|
action: {
|
|
label: 'Action',
|
|
icon: 'bolt',
|
|
color: '#8b5cf6',
|
|
fields: [
|
|
{ name: 'action', type: 'text', label: 'Action', placeholder: 'create_dispatch_job' },
|
|
{ name: 'params', type: 'json', label: 'Paramètres (JSON)' },
|
|
{ name: 'message', type: 'textarea', label: 'Message au client', rows: 2 },
|
|
],
|
|
},
|
|
goto: {
|
|
label: 'Aller à',
|
|
icon: 'arrow_forward',
|
|
color: '#64748b',
|
|
fields: [
|
|
{ name: 'target', type: 'text', label: 'Cible (intent ID)' },
|
|
],
|
|
},
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------
|
|
// Utilities
|
|
// -----------------------------------------------------------------------------
|
|
|
|
export function getKind (catalog, kindName) {
|
|
return catalog[kindName] || { label: kindName, icon: 'circle', color: '#94a3b8', fields: [] }
|
|
}
|
|
|
|
export function getTrigger (typeName) {
|
|
return TRIGGER_TYPES[typeName] || { label: typeName || 'Inconnu', fields: [] }
|
|
}
|
|
|
|
/**
|
|
* Build an empty step skeleton for a given kind.
|
|
* Applies defaults from field descriptors.
|
|
*/
|
|
export function buildEmptyStep (kindName, catalog) {
|
|
const kind = getKind(catalog, kindName)
|
|
const payload = {}
|
|
for (const f of kind.fields || []) {
|
|
if (f.default !== undefined) payload[f.name] = f.default
|
|
}
|
|
return {
|
|
id: 'step_' + Math.random().toString(36).slice(2, 9),
|
|
kind: kindName,
|
|
label: kind.label,
|
|
parent_id: null,
|
|
branch: null,
|
|
depends_on: [],
|
|
trigger: { type: 'on_prev_complete' },
|
|
payload,
|
|
}
|
|
}
|