feat(legacy-sync): child ticket → remonte l'arborescence parent pour l'adresse liée

Un sous-ticket (child) sans compte/adresse sur lui-même NI sur son parent direct (déjà COALESCÉ dans
la requête) récupère maintenant l'adresse LIÉE en remontant TOUTE l'arborescence parent : ancestorLink
(MariaDB WITH RECURSIVE, cap 8 niveaux) trouve le 1er ancêtre avec account_id/delivery_id, puis on
charge son compte + sa delivery. Ainsi le job du child pointe sur la bonne adresse de service.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
louispaulb 2026-07-24 14:15:17 -04:00
parent 03368b0a7b
commit 257924c9c6

View File

@ -516,7 +516,35 @@ async function cityFromSubject (subject) {
return ''
}
// Child ticket : remonte TOUTE l'arborescence parent (MariaDB WITH RECURSIVE, cap 8 niveaux) → 1er ancêtre avec
// account_id / delivery_id. La requête principale ne COALESCE que le parent DIRECT ; ceci couvre les sous-tickets profonds.
async function ancestorLink (p, ticketId) {
try {
const [rows] = await p.query(
`WITH RECURSIVE anc AS (
SELECT id, parent, account_id, delivery_id, 0 lvl FROM ticket WHERE id = ?
UNION ALL
SELECT pt.id, pt.parent, pt.account_id, pt.delivery_id, anc.lvl + 1
FROM ticket pt JOIN anc ON pt.id = anc.parent WHERE anc.lvl < 8 AND anc.parent > 0)
SELECT (SELECT account_id FROM anc WHERE account_id > 0 ORDER BY lvl LIMIT 1) AS account_id,
(SELECT delivery_id FROM anc WHERE delivery_id > 0 ORDER BY lvl LIMIT 1) AS delivery_id`, [ticketId])
return (rows && rows[0]) || null
} catch (e) { return null } // MariaDB < 10.2 ou erreur → on ne remonte pas (repli inchangé)
}
async function accountRow (p, id) { if (!id) return null; try { const [r] = await p.query('SELECT first_name, last_name, company, email, cell, tel_home, address1, address2, city, state, zip FROM account WHERE id = ? LIMIT 1', [id]); return (r && r[0]) || null } catch (e) { return null } }
async function deliveryRow (p, id) { if (!id) return null; try { const [r] = await p.query('SELECT latitude AS dv_lat, longitude AS dv_lon, placemarks_id AS dv_pmid, address1 AS dv_addr, city AS dv_city, zip AS dv_zip FROM delivery WHERE id = ? LIMIT 1', [id]); return (r && r[0]) || null } catch (e) { return null } }
async function buildJob (t, { dryRun = false } = {}) {
// Child ticket sans compte/adresse (ni sur lui, ni sur son parent DIRECT déjà COALESCÉ dans la requête) → REMONTE
// l'arborescence parent pour obtenir l'adresse LIÉE au ticket (compte + delivery de l'ancêtre le plus proche qui en a).
if (t.parent && (!t.account_id || (!t.dv_addr && !t.address1))) {
const p = pool()
const anc = p ? await ancestorLink(p, t.id) : null
if (anc) {
if (!t.account_id && anc.account_id) { t.account_id = anc.account_id; const ar = await accountRow(p, anc.account_id); if (ar) Object.assign(t, ar) }
if ((!t.delivery_id || (!t.dv_addr && !t.dv_lat)) && anc.delivery_id) { t.delivery_id = anc.delivery_id; const dr = await deliveryRow(p, anc.delivery_id); if (dr) Object.assign(t, dr) }
}
}
const cust = await resolveOrCreateCustomer(t.account_id, dryRun) // crée le compte s'il manque (sauf dry-run)
const subjCity = await cityFromSubject(t.subject) // ville RÉELLE du ticket (sujet) → apparie la BONNE Service Location
// SL appariée par la ville de SERVICE (sujet > delivery > facturation) — PAS la facturation seule (bug #255465 :