From e1d3212bf7ddf0c6d5561b197e4f695a49c24597 Mon Sep 17 00:00:00 2001 From: louispaulb Date: Fri, 10 Jul 2026 15:04:08 -0400 Subject: [PATCH] =?UTF-8?q?feat(ops):=20ticket=20Statut=20control=20(Compl?= =?UTF-8?q?=C3=A9t=C3=A9/Annul=C3=A9)=20+=20sale=20activation=20=E2=86=92?= =?UTF-8?q?=20draft=20invoice?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a "Statut" button on the ticket (distinct from the snooze "Reporter"): Ouvert / En attente / Complété (Resolved) / Annulé (Closed). Additive — does not touch the shared TicketStatusControl. For a ticket with a linked open install job, an explicit "Activer + facture brouillon" button (also offered when set to Complété) reuses the EXISTING hub flow: POST /dispatch/job-status Completed → chain unblock → activateSubscription ForJob → one consolidated prorated DRAFT invoice, gated by PRORATION_WRITE / BILLING_APPROVAL_GATE (never auto-charges; billing manager edits/approves). No new billing logic, no hub change. Co-Authored-By: Claude Opus 4.8 --- apps/ops/src/api/dispatch.js | 12 +++ .../components/shared/TicketStatusMenu.vue | 39 ++++++++ .../shared/detail-sections/IssueDetail.vue | 91 +++++++++++++++++-- 3 files changed, 133 insertions(+), 9 deletions(-) create mode 100644 apps/ops/src/components/shared/TicketStatusMenu.vue diff --git a/apps/ops/src/api/dispatch.js b/apps/ops/src/api/dispatch.js index 70007ef..3a24bfb 100644 --- a/apps/ops/src/api/dispatch.js +++ b/apps/ops/src/api/dispatch.js @@ -98,6 +98,18 @@ export async function updateJob (name, payload) { return apiPut('Dispatch Job', name, payload) } +// Complète un job VIA le chaînage canonique du hub (déblocage dépendances + activation abonnement +// + facture prorata BROUILLON en bout de chaîne, gatée PRORATION_WRITE / BILLING_APPROVAL_GATE). +// Réutilisé par la complétion d'un ticket de VENTE/installation. Renvoie { activated, invoices, pending_approval? }. +export async function setJobStatusChain (job, status) { + const res = await fetch(`${HUB_URL}/dispatch/job-status`, { + method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ job, status }), + }) + const data = await res.json().catch(() => ({})) + if (!res.ok) throw new Error(data.error || ('Dispatch API ' + res.status)) + return data +} + export async function createJob (payload) { const res = await authFetch( `${BASE_URL}/api/resource/Dispatch%20Job`, diff --git a/apps/ops/src/components/shared/TicketStatusMenu.vue b/apps/ops/src/components/shared/TicketStatusMenu.vue new file mode 100644 index 0000000..507f98b --- /dev/null +++ b/apps/ops/src/components/shared/TicketStatusMenu.vue @@ -0,0 +1,39 @@ + + + diff --git a/apps/ops/src/components/shared/detail-sections/IssueDetail.vue b/apps/ops/src/components/shared/detail-sections/IssueDetail.vue index fbeb5c4..f972407 100644 --- a/apps/ops/src/components/shared/detail-sections/IssueDetail.vue +++ b/apps/ops/src/components/shared/detail-sections/IssueDetail.vue @@ -1,9 +1,14 @@