From 5ab14cac44ba0e6a57817cfd47aa308367c3d4df Mon Sep 17 00:00:00 2001 From: louispaulb Date: Mon, 15 Jun 2026 06:12:12 -0400 Subject: [PATCH] =?UTF-8?q?feat(fsm):=20platform=20build=20=E2=80=94=20com?= =?UTF-8?q?ms=20UI,=20F=E2=86=92ERPNext=20sync/billing,=20roster,=20campai?= =?UTF-8?q?gns,=20network,=20reports?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) --- .gitignore | 4 + apps/ops/src/api/roster.js | 12 + .../src/components/customer/PhoneModal.vue | 2 +- apps/ops/src/components/dispatch/NlpInput.vue | 2 +- .../components/flow-editor/kind-catalogs.js | 1 + .../src/components/settings/AiRoutingCard.vue | 150 +++ .../settings/CannedResponsesCard.vue | 105 ++ .../components/settings/QueueTeamsCard.vue | 185 +++ .../src/components/shared/DepartmentBoard.vue | 157 +++ .../ops/src/components/shared/InlineField.vue | 4 +- .../src/components/shared/InterveneDialog.vue | 151 +++ .../src/components/shared/NewTicketDialog.vue | 145 ++ .../components/shared/OrchestratorDialog.vue | 218 +++ .../components/shared/OutageAlertsPanel.vue | 2 +- .../ops/src/components/shared/OutboxPanel.vue | 88 ++ .../src/components/shared/PipelineBoard.vue | 109 ++ .../src/components/shared/ProjectWizard.vue | 7 +- .../ops/src/components/shared/ReaderStack.vue | 33 + .../components/shared/ServiceStatusDialog.vue | 207 +++ .../detail-sections/EquipmentDetail.vue | 223 +++- .../shared/detail-sections/InvoiceDetail.vue | 2 +- apps/ops/src/composables/useClientData.js | 3 +- apps/ops/src/composables/useFormatters.js | 53 + apps/ops/src/config/departments.js | 39 + apps/ops/src/config/hub.js | 9 +- .../src/features/workforce/api/assignment.js | 39 + .../workforce/composables/useAssignment.js | 62 + .../workforce/composables/useResources.js | 93 ++ .../components/PublishScheduleModal.vue | 2 +- .../components/SuggestSlotsDialog.vue | 2 +- apps/ops/src/pages/ClientDetailPage.vue | 12 +- apps/ops/src/pages/CommunicationsPage.vue | 13 + apps/ops/src/pages/ConversationFullPage.vue | 39 + apps/ops/src/pages/LegacySyncPage.vue | 316 +++++ apps/ops/src/pages/PipelinePage.vue | 13 + apps/ops/src/pages/PlanificationPage.vue | 1168 +++++++++++++++-- apps/ops/src/pages/RapportsPage.vue | 14 + .../src/pages/ReportNegativeBillingPage.vue | 171 +++ .../src/pages/ReportTerminatedActivePage.vue | 130 ++ apps/ops/src/pages/SettingsPage.vue | 22 +- apps/ops/src/pages/SupplierInvoicesPage.vue | 160 +++ apps/ops/src/pages/TicketsPage.vue | 10 + docs/PLATFORM_GUIDE.md | 395 ++++++ docs/UI_AND_OPTIMIZATION.md | 136 ++ erpnext/setup_fsm_doctypes.py | 32 + erpnext/setup_supplier_invoice.py | 69 + .../migration/sync_invoices_incremental.py | 233 ++++ .../migration/sync_services_incremental.py | 228 ++++ services/legacy-bridge/ops_reassign.php | 72 + services/targo-hub/docs/legacy-sync.md | 71 + services/targo-hub/lib/agent.js | 23 +- services/targo-hub/lib/ai.js | 196 ++- services/targo-hub/lib/campaigns.js | 25 +- services/targo-hub/lib/client-diag.js | 138 ++ services/targo-hub/lib/cloud-ips.js | 83 ++ services/targo-hub/lib/config.js | 6 + services/targo-hub/lib/coupon-triage.js | 92 ++ services/targo-hub/lib/giftbit.js | 300 +++++ services/targo-hub/lib/gmail.js | 185 +++ services/targo-hub/lib/helpers.js | 66 +- .../targo-hub/lib/legacy-dispatch-sync.js | 254 +++- services/targo-hub/lib/legacy-payments.js | 582 ++++++++ services/targo-hub/lib/legacy-sync.js | 749 +++++++++++ services/targo-hub/lib/municipality.js | 121 ++ services/targo-hub/lib/olt-snmp.js | 74 +- services/targo-hub/lib/outbox.js | 74 ++ services/targo-hub/lib/roster-assistant.js | 19 +- services/targo-hub/lib/roster.js | 67 +- services/targo-hub/lib/supplier-invoices.js | 137 ++ services/targo-hub/lib/sync-orchestrator.js | 121 ++ services/targo-hub/lib/tech-absence-sms.js | 34 +- services/targo-hub/lib/ticket-collab.js | 627 +++++++++ services/targo-hub/lib/traccar.js | 48 + services/targo-hub/lib/twilio.js | 5 +- services/targo-hub/lib/vision.js | 88 +- services/targo-hub/server.js | 70 + 76 files changed, 9276 insertions(+), 321 deletions(-) create mode 100644 apps/ops/src/components/settings/AiRoutingCard.vue create mode 100644 apps/ops/src/components/settings/CannedResponsesCard.vue create mode 100644 apps/ops/src/components/settings/QueueTeamsCard.vue create mode 100644 apps/ops/src/components/shared/DepartmentBoard.vue create mode 100644 apps/ops/src/components/shared/InterveneDialog.vue create mode 100644 apps/ops/src/components/shared/NewTicketDialog.vue create mode 100644 apps/ops/src/components/shared/OrchestratorDialog.vue create mode 100644 apps/ops/src/components/shared/OutboxPanel.vue create mode 100644 apps/ops/src/components/shared/PipelineBoard.vue create mode 100644 apps/ops/src/components/shared/ReaderStack.vue create mode 100644 apps/ops/src/components/shared/ServiceStatusDialog.vue create mode 100644 apps/ops/src/config/departments.js create mode 100644 apps/ops/src/features/workforce/api/assignment.js create mode 100644 apps/ops/src/features/workforce/composables/useAssignment.js create mode 100644 apps/ops/src/features/workforce/composables/useResources.js create mode 100644 apps/ops/src/pages/CommunicationsPage.vue create mode 100644 apps/ops/src/pages/ConversationFullPage.vue create mode 100644 apps/ops/src/pages/LegacySyncPage.vue create mode 100644 apps/ops/src/pages/PipelinePage.vue create mode 100644 apps/ops/src/pages/ReportNegativeBillingPage.vue create mode 100644 apps/ops/src/pages/ReportTerminatedActivePage.vue create mode 100644 apps/ops/src/pages/SupplierInvoicesPage.vue create mode 100644 docs/PLATFORM_GUIDE.md create mode 100644 docs/UI_AND_OPTIMIZATION.md create mode 100644 erpnext/setup_supplier_invoice.py create mode 100644 scripts/migration/sync_invoices_incremental.py create mode 100644 scripts/migration/sync_services_incremental.py create mode 100644 services/targo-hub/docs/legacy-sync.md create mode 100644 services/targo-hub/lib/client-diag.js create mode 100644 services/targo-hub/lib/cloud-ips.js create mode 100644 services/targo-hub/lib/coupon-triage.js create mode 100644 services/targo-hub/lib/giftbit.js create mode 100644 services/targo-hub/lib/gmail.js create mode 100644 services/targo-hub/lib/legacy-payments.js create mode 100644 services/targo-hub/lib/legacy-sync.js create mode 100644 services/targo-hub/lib/municipality.js create mode 100644 services/targo-hub/lib/outbox.js create mode 100644 services/targo-hub/lib/supplier-invoices.js create mode 100644 services/targo-hub/lib/sync-orchestrator.js create mode 100644 services/targo-hub/lib/ticket-collab.js diff --git a/.gitignore b/.gitignore index 14de1ea..8467616 100644 --- a/.gitignore +++ b/.gitignore @@ -51,3 +51,7 @@ services/targo-hub/templates/*.bak-*.html # Legacy refresh creds (prod-only, never commit) .refresh.env **/.refresh.env + +# Python bytecode +__pycache__/ +*.pyc diff --git a/apps/ops/src/api/roster.js b/apps/ops/src/api/roster.js index a176ff0..621fef5 100644 --- a/apps/ops/src/api/roster.js +++ b/apps/ops/src/api/roster.js @@ -112,6 +112,10 @@ export const reorderJobs = (updates) => jpost('/roster/reorder-jobs', { updates export const unassignJobRoster = (job) => jpost('/roster/unassign-job', { job }) // Situer manuellement un job « hors carte » : pose latitude/longitude (+ adresse) choisis sur la carte export const setJobLocation = (job, lat, lon, address) => jpost('/roster/job/set-location', { job, lat, lon, address }) +// ÉQUIPE d'un job (assistants en renfort) — le lead reste inchangé ; l'assistant épinglé voit un bloc hachuré dans son horaire. +export const getJobTeam = (job) => jget('/roster/job/team?job=' + encodeURIComponent(job)) +export const addAssistant = (job, a) => jpost('/roster/job/team', { job, add: a }) +export const removeAssistant = (job, techId) => jpost('/roster/job/team', { job, remove: techId }) // Réconciliation des techniciens (staff legacy ↔ Dispatch Technician ↔ groupe Authentik tech) : rapport + apply manuel // Lien app PWA du tech (à copier / SMS) + son téléphone export const techAppLink = (tech) => jget('/roster/tech-link?tech=' + encodeURIComponent(tech)) @@ -123,7 +127,15 @@ export const startJob = (job) => jpost('/roster/job/start', { job }) export const finishJob = (job) => jpost('/roster/job/finish', { job }) export const techSyncReport = () => jget('/dispatch/legacy-sync/tech-sync') export const techSyncApply = (ids) => jpost('/dispatch/legacy-sync/tech-sync/apply?ids=' + encodeURIComponent((ids || []).join(',')), {}) +// Jobs Legacy (osTicket) ouverts assignés à UN tech (lecture seule) — vue « jobs courants du tech » +export const legacyTechTickets = (tech) => jget('/dispatch/legacy-sync/tech-tickets?tech=' + encodeURIComponent(tech)) +// Charge Legacy DATÉE par tech×jour sur la fenêtre affichée → durées estimées appliquées aux timelines +export const legacyWindowLoad = (start, days) => jget('/dispatch/legacy-sync/window-load?start=' + encodeURIComponent(start) + '&days=' + encodeURIComponent(days)) // Retour au POOL legacy (action EXPLICITE) : désassigne (ERPNext) + réécrit le ticket à Tech Targo (3301) dans osTicket export const returnJobToPool = (job) => jpost('/dispatch/legacy-sync/return-to-pool?job=' + encodeURIComponent(job), {}) // Aviser le client d'un report : désassigne + SMS lien /book — { job, phone?, message? } export const notifyReschedule = (body) => jpost('/roster/job/notify-reschedule', body) +// URL d'export GPX du parcours GPS d'un tech (jour courant) via Traccar — { tech, from, to } ISO 8601. window.open() = téléchargement. +export const gpxUrl = (tech, from, to) => HUB + '/traccar/gpx?tech=' + encodeURIComponent(tech) + '&from=' + encodeURIComponent(from) + '&to=' + encodeURIComponent(to) +// Tracé GPS (breadcrumb) d'un tech sur UNE journée → { coords:[[lon,lat]…], count }. Plage exacte (1 jour) obligatoire (Traccar lourd sinon). +export const traccarTrack = (tech, from, to) => jget('/traccar/track?tech=' + encodeURIComponent(tech) + '&from=' + encodeURIComponent(from) + '&to=' + encodeURIComponent(to)) diff --git a/apps/ops/src/components/customer/PhoneModal.vue b/apps/ops/src/components/customer/PhoneModal.vue index 73e6715..9a64193 100644 --- a/apps/ops/src/components/customer/PhoneModal.vue +++ b/apps/ops/src/components/customer/PhoneModal.vue @@ -104,7 +104,7 @@ import { createDoc } from 'src/api/erp' import { Device } from '@twilio/voice-sdk' import { UserAgent, Registerer, Inviter, SessionState } from 'sip.js' -const HUB_URL = window.location.hostname === 'localhost' ? 'http://localhost:3300' : 'https://msg.gigafibre.ca' +const HUB_URL = window.location.hostname === 'localhost' ? 'http://localhost:3300' : (import.meta.env.BASE_URL||'/').replace(/\/$/,'')+'/hub' const SIP_WSS_URL = 'wss://voice.gigafibre.ca/ws' const props = defineProps({ diff --git a/apps/ops/src/components/dispatch/NlpInput.vue b/apps/ops/src/components/dispatch/NlpInput.vue index 6ed3a2f..b7c80be 100644 --- a/apps/ops/src/components/dispatch/NlpInput.vue +++ b/apps/ops/src/components/dispatch/NlpInput.vue @@ -64,7 +64,7 @@ const props = defineProps({ const emit = defineEmits(['action', 'applied']) const $q = useQuasar() -const HUB_URL = window.location.hostname === 'localhost' ? 'http://localhost:3300' : 'https://msg.gigafibre.ca' +const HUB_URL = window.location.hostname === 'localhost' ? 'http://localhost:3300' : (import.meta.env.BASE_URL||'/').replace(/\/$/,'')+'/hub' const inputRef = ref(null) const text = ref('') diff --git a/apps/ops/src/components/flow-editor/kind-catalogs.js b/apps/ops/src/components/flow-editor/kind-catalogs.js index 6f2331c..68850bb 100644 --- a/apps/ops/src/components/flow-editor/kind-catalogs.js +++ b/apps/ops/src/components/flow-editor/kind-catalogs.js @@ -88,6 +88,7 @@ export const PROJECT_KINDS = { 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' }, diff --git a/apps/ops/src/components/settings/AiRoutingCard.vue b/apps/ops/src/components/settings/AiRoutingCard.vue new file mode 100644 index 0000000..d819431 --- /dev/null +++ b/apps/ops/src/components/settings/AiRoutingCard.vue @@ -0,0 +1,150 @@ + + + + + diff --git a/apps/ops/src/components/settings/CannedResponsesCard.vue b/apps/ops/src/components/settings/CannedResponsesCard.vue new file mode 100644 index 0000000..2d9c106 --- /dev/null +++ b/apps/ops/src/components/settings/CannedResponsesCard.vue @@ -0,0 +1,105 @@ + + + + + diff --git a/apps/ops/src/components/settings/QueueTeamsCard.vue b/apps/ops/src/components/settings/QueueTeamsCard.vue new file mode 100644 index 0000000..9b8eb7b --- /dev/null +++ b/apps/ops/src/components/settings/QueueTeamsCard.vue @@ -0,0 +1,185 @@ + + + + + diff --git a/apps/ops/src/components/shared/DepartmentBoard.vue b/apps/ops/src/components/shared/DepartmentBoard.vue new file mode 100644 index 0000000..3f3b6ca --- /dev/null +++ b/apps/ops/src/components/shared/DepartmentBoard.vue @@ -0,0 +1,157 @@ + + + + + diff --git a/apps/ops/src/components/shared/InlineField.vue b/apps/ops/src/components/shared/InlineField.vue index 366ecd2..79c4562 100644 --- a/apps/ops/src/components/shared/InlineField.vue +++ b/apps/ops/src/components/shared/InlineField.vue @@ -11,7 +11,7 @@