diff --git a/apps/ops/src/components/shared/detail-sections/EquipmentDetail.vue b/apps/ops/src/components/shared/detail-sections/EquipmentDetail.vue
index a36a2ed..2f64065 100644
--- a/apps/ops/src/components/shared/detail-sections/EquipmentDetail.vue
+++ b/apps/ops/src/components/shared/detail-sections/EquipmentDetail.vue
@@ -130,8 +130,11 @@
+
+
+ MAC synchronisée automatiquement depuis RADIUS : {{ airos.radiusMac }} (était {{ airos.macWas }})
+
+
MAC RADIUS {{ airos.radiusMac }} ≠ enregistrée {{ doc.mac_address || '—' }}
@@ -642,6 +645,8 @@ async function fetchAiros () {
try {
const r = await fetch(`${HUB_URL}/collab/airos-signal?serial=${encodeURIComponent(props.doc.serial_number || '')}&ip=${encodeURIComponent(props.doc.ip_address || '')}`)
airos.value = await r.json()
+ // Le hub a auto-synchronisé la MAC depuis RADIUS → refléter localement (l'InlineField Identité suit).
+ if (airos.value && airos.value.macSynced && airos.value.radiusMac) props.doc.mac_address = airos.value.radiusMac
} catch (e) { airos.value = { ok: false, error: e.message } } finally { airosLoading.value = false }
}
// MAC RADIUS (WPA2) = source autoritaire, s'auto-MàJ au remplacement d'équipement. Si elle diffère de la MAC
diff --git a/services/targo-hub/lib/ticket-collab.js b/services/targo-hub/lib/ticket-collab.js
index 5de698d..1843564 100644
--- a/services/targo-hub/lib/ticket-collab.js
+++ b/services/targo-hub/lib/ticket-collab.js
@@ -842,29 +842,39 @@ function httpsGetJson (targetUrl, headers, timeoutMs) {
})
}
+const macNorm = (m) => String(m || '').replace(/[^0-9a-fA-F]/g, '').toUpperCase()
async function airosSignal ({ sn, ip, port, user, mac } = {}) {
- // Résout ip/mac depuis Service Equipment (source unique du parc) si non fournis.
- if ((!ip || !mac) && sn) {
- try {
- const rows = await erp.list('Service Equipment', { filters: [['serial_number', '=', String(sn)]], fields: ['ip_address', 'mac_address'], limit: 1 })
- const e = rows && rows[0]
- if (e) { if (!ip) ip = e.ip_address || ''; if (!mac) mac = e.mac_address || '' }
- } catch (e) { /* best-effort */ }
- }
+ // Résout le Service Equipment (nom + IP + MAC enregistrée) — sert à l'AUTO-SYNC de la MAC depuis RADIUS.
+ let seName = null, seMac = ''
+ try {
+ let rows = []
+ if (sn) rows = await erp.list('Service Equipment', { filters: [['serial_number', '=', String(sn)]], fields: ['name', 'ip_address', 'mac_address'], limit: 1 })
+ if ((!rows || !rows.length) && ip) rows = await erp.list('Service Equipment', { filters: [['ip_address', '=', String(ip)]], fields: ['name', 'ip_address', 'mac_address'], limit: 1 })
+ const e = rows && rows[0]
+ if (e) { seName = e.name; seMac = e.mac_address || ''; if (!ip) ip = e.ip_address || ''; if (!mac) mac = e.mac_address || '' }
+ } catch (e) { /* best-effort */ }
if (!sn && !ip && !mac) return { ok: false, error: 'serial, ip ou mac requis' }
// PRIMAIRE : pont F ops_airos.php (même hôte + token que ops_reassign.php).
const token = opsBridgeToken()
const base = (process.env.OPS_LEGACY_URL || 'https://facturation.targo.ca/ops_reassign.php').replace(/ops_reassign\.php.*$/, 'ops_airos.php')
+ let out = null
if (token && base) {
const q = new URLSearchParams()
if (sn) q.set('serial', sn); if (ip) q.set('ip', ip); if (mac) q.set('mac', mac); if (port) q.set('port', String(port))
const r = await httpsGetJson(base + '?' + q.toString(), { 'X-Ops-Token': token }, 25000)
- if (r && r.ok === true) return normalizeAiros(r)
- if (r && r.ok === false && r.error && !r._err) return { ok: false, error: r.error } // réponse F propre (device introuvable / CPE injoignable)
+ if (r && r.ok === true) out = normalizeAiros(r)
+ else if (r && r.ok === false && r.error && !r._err) return { ok: false, error: r.error } // réponse F propre (device introuvable / CPE injoignable)
// transport KO (r._err) → tenter n8n en repli
}
// REPLI : webhook n8n get_signal_airos (post-migration F ; 404 = pas encore créé).
- return await airosViaN8n({ sn, ip, port, user })
+ if (!out) out = await airosViaN8n({ sn, ip, port, user })
+ // AUTO-SYNC MAC : RADIUS (WPA2) = autoritaire, s'auto-MàJ au remplacement. Si la MAC courante diffère de
+ // l'enregistrée → on met à jour Service Equipment AUTOMATIQUEMENT (côté serveur, fill/correct, idempotent).
+ if (out && out.ok && out.radiusMac && seName && macNorm(out.radiusMac) && macNorm(out.radiusMac) !== macNorm(seMac)) {
+ try { await erp.update('Service Equipment', seName, { mac_address: out.radiusMac }); out.macSynced = true; out.macWas = seMac || null; log('[airos] MAC auto-sync ' + seName + ' ' + (seMac || '∅') + ' → ' + out.radiusMac + ' (RADIUS)') }
+ catch (e) { out.macSyncError = e.message }
+ }
+ return out
}
function airosViaN8n ({ sn, ip, port, user } = {}) {