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 ──────────────────────────────────────────────────────