diff --git a/services/targo-hub/lib/events.js b/services/targo-hub/lib/events.js
index 4ded31b..2127ca5 100644
--- a/services/targo-hub/lib/events.js
+++ b/services/targo-hub/lib/events.js
@@ -507,6 +507,32 @@ async function cityCustomerIds (city) {
for (let p = 0; p < 80; p++) { const b = await erp.list('Service Location', { filters: [['city', 'like', '%' + term + '%']], fields: ['customer'], limit: 500, start: s }); b.forEach(x => { if (x.customer) ids.add(x.customer) }); if (b.length < 500) break; s += 500 }
return ids
}
+
+// Filtre « nom » de l'audience — FUZZY comme la recherche app-wide : accent/casse-insensible ET multi-champs
+// (customer_name + ID de compte `name` + mandataire + contact_name_legacy). Ex. « lpb » → C-LPB4 (Louis-Paul
+// Bourdon, matché par ID/mandataire, pas par customer_name). Via PG ; repli REST (customer_name, sensible à la casse).
+async function nameLikeCustomerIds (term) {
+ const t = String(term || '').trim(); const ids = new Set()
+ if (!t) return ids
+ let pool = null
+ try { pool = require('./address-db').pool() } catch { pool = null }
+ if (pool) {
+ const like = '%' + t + '%'
+ try {
+ const r = await pool.query("SELECT name FROM \"tabCustomer\" WHERE unaccent(lower(coalesce(customer_name,''))) LIKE unaccent(lower($1)) OR lower(coalesce(name,'')) LIKE lower($1)", [like])
+ r.rows.forEach(x => { if (x.name) ids.add(x.name) })
+ } catch (e) { log('nameLikeCustomerIds pg: ' + e.message) }
+ // Champs legacy (colonnes custom possibles) — try/catch séparé pour ne pas casser si absentes.
+ try {
+ const r = await pool.query("SELECT name FROM \"tabCustomer\" WHERE unaccent(lower(coalesce(mandataire,''))) LIKE unaccent(lower($1)) OR unaccent(lower(coalesce(contact_name_legacy,''))) LIKE unaccent(lower($1))", [like])
+ r.rows.forEach(x => { if (x.name) ids.add(x.name) })
+ } catch { /* colonnes absentes → ignore */ }
+ return ids
+ }
+ const erp = require('./erp'); let s = 0
+ for (let p = 0; p < 80; p++) { const b = await erp.list('Customer', { filters: [['customer_name', 'like', '%' + t + '%']], fields: ['name'], limit: 500, start: s }); b.forEach(x => { if (x.name) ids.add(x.name) }); if (b.length < 500) break; s += 500 }
+ return ids
+}
async function resolveAudience ({ mode = 'filter', filters = {}, csv = '' } = {}) {
let rows = []
let resiliatedExcluded = 0
@@ -529,13 +555,14 @@ async function resolveAudience ({ mode = 'filter', filters = {}, csv = '' } = {}
if (filters.statut === 'active') f.push(['disabled', '=', 0])
else if (filters.statut === 'disabled') f.push(['disabled', '=', 1])
if (filters.customer_type) f.push(['customer_type', '=', filters.customer_type]) // Individual = résidentiel · Company = commercial
- if (filters.name_like) f.push(['customer_name', 'like', '%' + String(filters.name_like).trim() + '%']) // ex. « Proprio » → comptes dont le nom le contient
const minMonthly = Number(filters.min_monthly) || 0
- // Ensembles d'IDs à INTERSECTER (ville via Service Location, abonnements via Service Subscription).
+ // Ensembles d'IDs à INTERSECTER (nom fuzzy, ville via Service Location, abonnements via Service Subscription).
let restrict = null // null = pas de restriction ; sinon Set d'IDs Customer
const intersect = (set) => { restrict = restrict ? new Set([...restrict].filter(x => set.has(x))) : set }
+ // NOM (fuzzy, multi-champs, insensible casse/accents) — ex. « lpb » → C-LPB4.
+ if (filters.name_like && String(filters.name_like).trim()) intersect(await nameLikeCustomerIds(String(filters.name_like)))
// VILLE → Service Location.customer (champ `city` peuplé, contrairement à `territory`).
if (filters.city && String(filters.city).trim()) intersect(await cityCustomerIds(String(filters.city)))
@@ -629,7 +656,6 @@ function page (event, lang, prefill = {}, opts = {}) {
lang = normLang(lang)
const t = event[lang]
const other = lang === 'fr' ? 'en' : 'fr'
- const known = !!prefill.customer_id
const full = !!opts.full
const greet = t.greet(firstName(prefill.name))
const program = (t.program || []).map(p =>
@@ -726,7 +752,6 @@ button:hover{filter:brightness(1.05)}button:active{transform:translateY(1px)}but
${field('nm', 'name', esc(t.f_name), ``, 'e_nm', t.err_name)}
${field('em', 'email', esc(t.f_email), ``, 'e_em', t.err_email)}
${field('ph', 'phone', optlbl(t.f_phone), ``)}
- ${field('cn', 'customer_number', optlbl(t.f_number), ``)}
${field('ps', 'party_size', esc(t.f_party), ``, 'e_ps', t.err_party)}
${field('al', 'allergies', optlbl(t.f_allerg), ``)}
@@ -769,7 +794,7 @@ function submitRsvp(e){
if(!ok)return false;
var b=document.getElementById('sub');b.disabled=true;
fetch('/rsvp/'+encodeURIComponent(EVENT)+'/submit',{method:'POST',headers:{'Content-Type':'application/json'},
- body:JSON.stringify({token:TOKEN,name:nm,email:em,phone:(f.phone.value||'').trim(),customer_number:(f.customer_number.value||'').trim(),party_size:ps,allergies:(f.allergies.value||'').trim(),lang:LANG})})
+ body:JSON.stringify({token:TOKEN,name:nm,email:em,phone:(f.phone.value||'').trim(),party_size:ps,allergies:(f.allergies.value||'').trim(),lang:LANG})})
.then(function(r){return r.json()})
.then(function(d){
if(d&&d.ok){