feat(dispatch): provisioning identites enrichi Employee+Authentik (multi-courriels)
extEmailIndex(): index nom->emails depuis ERPNext Employee (company/prefered/ personal/user_id) + Authentik /core/users. provisionIdentities l utilise pour: - CREATE des identites d ex-staff sans courriel legacy (si une source externe en a un) - ENRICH: tous les courriels decouverts s accumulent en alias_emails (union, jamais overwrite) -> multi-courriels par personne, resolvable par chaque alias - skip des comptes systeme (ticket dispatch, Gestion Inventaire, Tech Targo) Applique en prod: +1 lien, +5 identites enrichies d alias (perso+corpo). Idempotent (re-run 0 ecriture). Les 41 restants = ex-employes absents de toute source -> mapping manuel via l onglet Identites (liste unresolved du rapport). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
e8fd69c6f8
commit
9c1b578d5d
|
|
@ -2624,27 +2624,60 @@ async function techHistory ({ tech = '', limit = 200 } = {}) {
|
|||
// assignations : garantit une identité canonique portant son legacy_staff_id (+ tech_id SI Dispatch Technician → les
|
||||
// admins restent SANS tech_id). LINK (identité trouvée par courriel/nom → renseigne legacy_staff_id) ou CREATE (aucune →
|
||||
// crée depuis le staff). Idempotent. dryRun => 0 écriture. scope 'ledger' (défaut : staff du ledger) | 'active' (tout staff actif).
|
||||
// Comptes SYSTÈME du legacy staff (pas des personnes) → jamais d'identité auto.
|
||||
const SYS_STAFF_RE = /ticket\s*dispatch|gestion\s*inventaire|^tech\s*targo$|^test\b/i
|
||||
// Index nom→emails depuis les sources EXTERNES (ERPNext Employee + Authentik) — enrichit les staff sans courriel
|
||||
// legacy (ex-employés hors staff.email) ET alimente les alias multi-courriels (cf 3 sources de noms/identité).
|
||||
async function extEmailIndex () {
|
||||
const byName = new Map() // norm(nom complet) → Set(emails)
|
||||
const add = (name, mail) => { const k = norm(name || ''); const v = String(mail || '').trim().toLowerCase(); if (!k || !v || !v.includes('@')) return; const set = byName.get(k) || new Set(); set.add(v); byName.set(k, set) }
|
||||
try {
|
||||
const emps = await erp.list('Employee', { fields: ['employee_name', 'company_email', 'prefered_email', 'personal_email', 'user_id'], limit: 2000 })
|
||||
for (const e of (emps || [])) for (const m of [e.company_email, e.prefered_email, e.personal_email, e.user_id]) add(e.employee_name, m)
|
||||
} catch (e) {}
|
||||
try {
|
||||
const d = await akGet('/core/users/?page_size=500')
|
||||
for (const u of ((d && d.results) || [])) add(u.name, u.email)
|
||||
} catch (e) {}
|
||||
return byName
|
||||
}
|
||||
async function provisionIdentities ({ dryRun = true, scope = 'ledger' } = {}) {
|
||||
const p = pool(); const pg = techHistPg(); const idn = require('./identity')
|
||||
let staffIds = []
|
||||
if (scope === 'active') { const [r] = await p.query('SELECT id FROM staff WHERE status=1'); staffIds = r.map(x => x.id) } else { const r = await pg.query('SELECT DISTINCT to_staff_id FROM ops_tech_ticket_history WHERE to_staff_id IS NOT NULL'); staffIds = r.rows.map(x => x.to_staff_id) }
|
||||
if (!staffIds.length) return { ok: true, scope, targets: 0, linked: 0, created: 0, already: 0, no_email: [], samples: [] }
|
||||
if (!staffIds.length) return { ok: true, scope, targets: 0, linked: 0, created: 0, enriched: 0, already: 0, system: 0, no_email: [], samples: [] }
|
||||
const [staff] = await p.query('SELECT id, first_name, last_name, email, status FROM staff WHERE id IN (?)', [staffIds])
|
||||
const byId = new Map(staff.map(s => [String(s.id), s]))
|
||||
const techs = await erp.list('Dispatch Technician', { fields: ['name', 'technician_id'], limit: 1000 })
|
||||
const techByStaff = {}; for (const t of (techs || [])) { const m = String(t.technician_id || '').match(/(\d+)$/); if (m) techByStaff[m[1]] = t.technician_id || t.name }
|
||||
const res = { ok: true, scope, dryRun, targets: staffIds.length, linked: 0, created: 0, already: 0, no_email: [], samples: [] }
|
||||
const ext = await extEmailIndex()
|
||||
const res = { ok: true, scope, dryRun, targets: staffIds.length, linked: 0, created: 0, enriched: 0, already: 0, system: 0, no_email: [], samples: [] }
|
||||
for (const sid of staffIds) {
|
||||
const s = byId.get(String(sid)); if (!s) continue
|
||||
const full = [s.first_name, s.last_name].filter(Boolean).join(' ').trim(); const email = norm(s.email); const techId = techByStaff[String(sid)] || ''
|
||||
const full = [s.first_name, s.last_name].filter(Boolean).join(' ').trim(); const techId = techByStaff[String(sid)] || ''
|
||||
if (SYS_STAFF_RE.test(full)) { res.system++; continue } // compte système → pas d'identité personne
|
||||
// TOUS les courriels connus (legacy → primaire préféré, puis Employee/Authentik) : le 1er = primaire, le reste = alias.
|
||||
const legacyMail = norm(s.email)
|
||||
const allEmails = [...new Set([legacyMail, ...(ext.get(norm(full)) || [])].filter(Boolean))]
|
||||
const existing = idn.resolveIdentity(String(sid))
|
||||
if (existing && String(existing.legacy_staff_id) === String(sid)) {
|
||||
if (techId && !existing.tech_id) { if (!dryRun) idn.upsert({ key: existing.key, label: existing.label, primary_email: existing.primary_email, legacy_staff_id: sid, tech_id: techId }); res.linked++; if (res.samples.length < 30) res.samples.push({ staff: sid, action: 'fill-tech', key: existing.key }) } else res.already++
|
||||
// Déjà lié : enrichit quand même (tech_id manquant / NOUVEAUX courriels → alias, union jamais overwrite).
|
||||
const have = new Set([existing.primary_email, ...(existing.alias_emails || [])].map(x => String(x || '').toLowerCase()))
|
||||
const newMails = allEmails.filter(e => !have.has(e))
|
||||
const needTech = techId && !existing.tech_id
|
||||
if (needTech || newMails.length) {
|
||||
if (!dryRun) idn.upsert({ key: existing.key, label: existing.label, primary_email: existing.primary_email, legacy_staff_id: sid, tech_id: techId || existing.tech_id, alias_emails: newMails })
|
||||
res.enriched++; if (res.samples.length < 40) res.samples.push({ staff: sid, action: 'enrich', key: existing.key, add_aliases: newMails, tech: needTech ? techId : undefined })
|
||||
} else res.already++
|
||||
continue
|
||||
}
|
||||
const cand = (email && idn.resolveIdentity(email)) || (full && idn.resolveIdentity(full)) || null
|
||||
if (cand) { if (!dryRun) idn.upsert({ key: cand.key, label: cand.label, primary_email: cand.primary_email, legacy_staff_id: sid, tech_id: techId || cand.tech_id }); res.linked++; if (res.samples.length < 30) res.samples.push({ staff: sid, name: full, action: 'link', key: cand.key, tech: !!techId }); continue }
|
||||
if (email) { if (!dryRun) idn.upsert({ label: full || email, primary_email: email, legacy_staff_id: sid, tech_id: techId, kind: 'employee', active: Number(s.status) === 1 }); res.created++; if (res.samples.length < 30) res.samples.push({ staff: sid, name: full, action: 'create', email, tech: !!techId }); continue }
|
||||
// Identité existante par un des courriels ou par nom → LINK (renseigne legacy_staff_id + alias découverts).
|
||||
let cand = null
|
||||
for (const e of allEmails) { cand = idn.resolveIdentity(e); if (cand) break }
|
||||
if (!cand && full) cand = idn.resolveIdentity(full)
|
||||
if (cand) { if (!dryRun) idn.upsert({ key: cand.key, label: cand.label, primary_email: cand.primary_email, alias_emails: allEmails, legacy_staff_id: sid, tech_id: techId || cand.tech_id }); res.linked++; if (res.samples.length < 40) res.samples.push({ staff: sid, name: full, action: 'link', key: cand.key, tech: !!techId }); continue }
|
||||
// Aucune identité → CREATE avec le 1er courriel connu en primaire + les autres en alias.
|
||||
if (allEmails.length) { if (!dryRun) idn.upsert({ label: full || allEmails[0], primary_email: allEmails[0], alias_emails: allEmails.slice(1), legacy_staff_id: sid, tech_id: techId, kind: 'employee', active: Number(s.status) === 1 }); res.created++; if (res.samples.length < 40) res.samples.push({ staff: sid, name: full, action: 'create', primary: allEmails[0], aliases: allEmails.slice(1), tech: !!techId }); continue }
|
||||
res.no_email.push({ staff: sid, name: full })
|
||||
}
|
||||
return res
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user