From 81af69e68c08299042d18a0037110cd797e1b2d8 Mon Sep 17 00:00:00 2001 From: louispaulb Date: Sun, 19 Jul 2026 16:41:12 -0400 Subject: [PATCH] =?UTF-8?q?improve(booking):=20SMS=20de=20confirmation=20t?= =?UTF-8?q?ransactionnel=20quand=20le=20client=20r=C3=A9serve=20via=20/boo?= =?UTF-8?q?k?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit À la confirmation d'un créneau (confirmWindow, donc Option A « choisir » ET Option B « proposer 3 » qui aboutit), on texte le client : « votre rendez-vous technique est confirmé : à (technicien X) ». Déclenché par SA sélection (transactionnel), fire-and-forget (ne bloque pas la réponse). Complète le .ics — le client a une trace immédiate. Téléphone résolu depuis Customer.mobile_no. Déployé, hub sain ; pas de test d'envoi réel (client réel). Co-Authored-By: Claude Fable 5 --- services/targo-hub/lib/roster.js | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/services/targo-hub/lib/roster.js b/services/targo-hub/lib/roster.js index a9b215c..5543207 100644 --- a/services/targo-hub/lib/roster.js +++ b/services/targo-hub/lib/roster.js @@ -898,8 +898,8 @@ async function confirmWindow (jobName, date, start, duration, skill) { const st = start.length === 5 ? start + ':00' : start // Garde F : si le ticket legacy est déjà assigné à un AUTRE tech dans F, on N'ÉCRASE PAS (côté client, pas de force). // On enregistre le créneau choisi (booking_status Proposé) et on laisse le répartiteur confirmer le bon tech. - let legacyId = '' - try { const jb = await erp.get('Dispatch Job', jobName, { fields: ['legacy_ticket_id'] }); legacyId = (jb && jb.legacy_ticket_id) || '' } catch (e) {} + let legacyId = '', customer = '' + try { const jb = await erp.get('Dispatch Job', jobName, { fields: ['legacy_ticket_id', 'customer'] }); legacyId = (jb && jb.legacy_ticket_id) || ''; customer = (jb && jb.customer) || '' } catch (e) {} const cf = await fConflict(legacyId, slot.tech) if (cf) { const rp = await retryWrite(() => erp.update('Dispatch Job', jobName, { scheduled_date: date, start_time: st, booking_status: 'Proposé', booking_prefs: JSON.stringify([{ date, start }]) })) @@ -907,7 +907,19 @@ async function confirmWindow (jobName, date, start, duration, skill) { return { ok: true, confirmed: false, message: 'Créneau enregistré — nous confirmerons le technicien sous peu.' } } const r = await retryWrite(() => erp.update('Dispatch Job', jobName, { scheduled_date: date, start_time: st, assigned_tech: slot.tech, status: 'assigned', booking_status: 'Confirmé' })) - if (r.ok) releaseHold(date + '|' + start) + if (r.ok) { + releaseHold(date + '|' + start) + // SMS de confirmation TRANSACTIONNEL au client (déclenché par SA sélection) — fire-and-forget, ne bloque pas la réponse. + if (customer) { + (async () => { + try { + const c = await erp.get('Customer', customer, { fields: ['mobile_no'] }) + const phone = c && c.mobile_no + if (phone) await require('./twilio').sendSmsInternal(phone, 'Gigafibre — votre rendez-vous technique est confirmé : ' + date + ' à ' + start + (slot.tech_name ? ' (technicien ' + slot.tech_name + ')' : '') + '. Merci !', customer) + } catch (e) {} + })() + } + } return r.ok ? { ok: true, confirmed: true, date, start, tech: slot.tech_name } : { ok: false, message: r.error || 'échec' } }