Inbox identity/grouping (fixes mislabeled threads):
- customerName = real From display name, then a customer matched BY EMAIL,
then the raw address — never the AI's content-guessed name (a gilles@ relay
showed as "Sylvie Juteau").
- never group a thread by one of our own domains (support@targo.ca,
*@targointernet.com): re-ingested outbound was collapsing unrelated threads
into one mislabeled "Guylaine Gagnon" thread.
Refactor: queues/TYPES/QUEUE_OF/CAT centralized in lib/categories.js (were
drifting across conversation.js + inbox-triage.js; telephonie/television had
no matching triage type). Removed dead export findConversationByEmail.
Feat: addMessage stamps msg.agent on outbound replies -> per-agent stats.
Historique (/historique, HistoriquePage.vue): tournées par technicien (ALL
statuses incl. Completed/Cancelled — the board hid them), tech leaderboard,
inbox leaderboard. Hub: /dispatch/history, /dispatch/leaderboard,
/conversations/leaderboard.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- contracts.js: _inferPlanName now reads the Quotation's first positive-rate
item ("Internet Megafibre 80 Mbps") instead of generic fallback.
- contracts.js: subPayload writes service_contract back-ref so an active/
pending sub blocks its parent contract's deletion (LinkExistsError).
- contracts.js: GET /contract/audit-orphans[?fix=1] scans for orphaned subs
(dangling contract link or no link at all) and contracts without a sub;
filters out 2026-03-29 legacy-migration batch via LEGACY_CUTOFF.
- dispatch.js: deleteJobSafely() rewires children's depends_on to the
victim's parent, re-parents descendants if victim was chain root, then
deletes. POST /dispatch/job-delete exposes it. Fixes LinkExistsError
when users delete a middle step in the UI.
- TaskNode.vue: confirmDelete calls /dispatch/job-delete and surfaces a
warning when dependents will be rewired.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- contracts.js: built-in install chain fallback when no Flow Template matches
on_contract_signed — every accepted contract now creates a master Issue +
chained Dispatch Jobs (fiber_install template) so we never lose a signed
contract to a missing flow config.
- acceptance.js: export createDeferredJobs + propagate assigned_group into
Dispatch Job payload (was only in notes, not queryable).
- dispatch.js: chain-walk helpers (unblockDependents, _isChainTerminal,
setJobStatusWithChain) + terminal-node detection that activates pending
Service Subscriptions (En attente → Actif, start_date=tomorrow) and emits
a prorated Sales Invoice covering tomorrow → EOM. Courtesy-day billing
convention: activation day is free, first period starts next day.
- dispatch.js: fix Sales Invoice 417 by resolving company default income
account (Ventes - T) and passing company + income_account on each item.
- dispatch.js: GET /dispatch/group-jobs + POST /dispatch/claim-job for tech
self-assignment from the group queue; enriches with customer_name /
service_location via per-job fetches since those fetch_from fields aren't
queryable in list API.
- TechTasksPage.vue: redesigned mobile-first UI with progress arc, status
chips, and new "Tâches du groupe" section showing claimable unassigned
jobs with a "Prendre" CTA. Live updates via SSE job-claimed / job-unblocked.
- NetworkPage.vue + poller-control.js: poller toggle semantics flipped —
green when enabled, red/gray when paused; explicit status chips for clarity.
E2E verified end-to-end: CTR-00007 → 4 chained jobs → claim → In Progress →
Completed walks chain → SUB-0000100002 activated (start=2026-04-24) →
SINV-2026-700012 prorata $9.32 (= 39.95 × 7/30).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>