diff --git a/apps/ops/src/modules/campaigns/pages/CampaignNewPage.vue b/apps/ops/src/modules/campaigns/pages/CampaignNewPage.vue index e6fc7c4..e4238f9 100644 --- a/apps/ops/src/modules/campaigns/pages/CampaignNewPage.vue +++ b/apps/ops/src/modules/campaigns/pages/CampaignNewPage.vue @@ -220,6 +220,18 @@ il y a plus de cartes-cadeaux que de contacts. Le surplus sera perdu si la campagne est envoyée tel quel. + +
+ Ventilation des contacts droppés au parsing du Map CSV + (sur {{ parseSkipped.total_rows }} lignes brutes) : + {{ parseSkipped.no_email }} sans email valide · + {{ parseSkipped.duplicate }} emails en double (déjà vu plus haut) · + {{ parseSkipped.multi_skip }} couples ignorés (selon le réglage "Emails multiples") · + {{ parseSkipped.no_name }} sans nom (gardés, utilisent "cher client" à l'envoi) +
@@ -621,6 +633,10 @@ const sending = ref(false) const recipients = ref([]) const unpairedContacts = ref([]) const unusedGifts = ref([]) +// Map CSV parse skip counters surfaced from the hub so the operator can see +// exactly which rows didn't make it into the pairing (no email, duplicates, +// multi-skip). +const parseSkipped = ref(null) // row_index (#1, #2, ...) is the source-CSV position — invaluable for the // user to cross-reference what they see here against the file they uploaded. @@ -820,6 +836,7 @@ async function goPreview () { recipients.value = r.recipients || [] unpairedContacts.value = r.unpaired_contacts || [] unusedGifts.value = r.unused_gifts || [] + parseSkipped.value = r.skipped || null step.value = 2 } catch (e) { $q.notify({ type: 'negative', message: 'Erreur de parsing: ' + e.message }) diff --git a/services/targo-hub/lib/campaigns.js b/services/targo-hub/lib/campaigns.js index 33bd6a5..46d5622 100644 --- a/services/targo-hub/lib/campaigns.js +++ b/services/targo-hub/lib/campaigns.js @@ -390,7 +390,7 @@ function parseMapCsv (text, multi = 'first') { const rows = parseCsv(text, { skipPreamble: true }) const contacts = [] const seen = new Set() - let skippedNoEmail = 0, skippedNoName = 0 + let skippedNoEmail = 0, skippedNoName = 0, skippedDuplicate = 0, skippedMultiSkip = 0 for (const r of rows) { // Pull emails from either source column @@ -404,10 +404,15 @@ function parseMapCsv (text, multi = 'first') { const sendEmails = multi === 'split' ? emails : multi === 'skip' ? (emails.length > 1 ? [] : emails) : emails.slice(0, 1) - if (!sendEmails.length) continue + if (!sendEmails.length) { skippedMultiSkip++; continue } + // We used to skip rows without a name here. That dropped the contact AND + // wasted its paired Giftbit shortlink — the worker already defaults + // firstname to "cher client" / "dear customer" when missing, so we now + // keep the row and just flag it with a name warning. const full = (r['nom au compte'] || r["nom à l'adresse"] || '').trim() - if (!full) { skippedNoName++; continue } + const hasName = !!full + if (!hasName) skippedNoName++ // informational counter, NOT a continue const parts = full.split(/\s+/, 2) const firstnameRaw = parts[0] || '' const lastnameRaw = parts.length > 1 ? full.slice(parts[0].length).trim() : '' @@ -419,7 +424,7 @@ function parseMapCsv (text, multi = 'first') { const firstname = cleanName(firstnameRaw) const lastname = cleanName(lastnameRaw) const name_warnings = { - firstname: nameWarning(firstname), + firstname: !hasName ? 'pas de nom dans le CSV — utilisera "cher client" à l\'envoi' : nameWarning(firstname), lastname: nameWarning(lastname), } const cleaned_changed = (firstname !== firstnameRaw || lastname !== lastnameRaw) @@ -429,7 +434,7 @@ function parseMapCsv (text, multi = 'first') { const phone = normalizePhone(r['telephone au compte'] || r["telephone à l'adresse"] || '') for (const em of sendEmails) { - if (seen.has(em)) continue + if (seen.has(em)) { skippedDuplicate++; continue } seen.add(em) contacts.push({ firstname, lastname, @@ -443,7 +448,16 @@ function parseMapCsv (text, multi = 'first') { }) } } - return { contacts, skipped: { no_email: skippedNoEmail, no_name: skippedNoName, total_rows: rows.length } } + return { + contacts, + skipped: { + no_email: skippedNoEmail, + no_name: skippedNoName, // informational only — these rows are KEPT now + duplicate: skippedDuplicate, + multi_skip: skippedMultiSkip, + total_rows: rows.length, + }, + } } // ── Giftbit CSV parsing ──────────────────────────────────────────────────────