From 9c7a383ee280d9b095921e97d573bffb2c1c7a96 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cau=C3=AA=20Faleiros?= Date: Wed, 29 Jul 2026 10:44:22 -0300 Subject: [PATCH] Fix duplicate top campaign clients by phone --- backend/services/campaignService.js | 23 +++-------------------- backend/test/campaignService.test.js | 7 ++++--- 2 files changed, 7 insertions(+), 23 deletions(-) diff --git a/backend/services/campaignService.js b/backend/services/campaignService.js index f0d941f..320dc29 100644 --- a/backend/services/campaignService.js +++ b/backend/services/campaignService.js @@ -25,24 +25,6 @@ const WHATSAPP_CUSTOMER_PHONE_SQL = ` ELSE ${NORMALIZED_CUSTOMER_PHONE_SQL} END `; -const CANONICAL_CAMPAIGN_CUSTOMER_NAME_SQL = ` - NULLIF(TRIM(regexp_replace( - regexp_replace( - regexp_replace( - regexp_replace(LOWER(COALESCE(cliente_nome, '')), '[^[:alnum:][:space:]]+', ' ', 'g'), - '(^|[[:space:]])[0-9]{2,14}([[:space:]]|$)', - ' ', - 'g' - ), - '(^|[[:space:]])(ltda|me|eireli|epp)([[:space:]]|$)', - ' ', - 'g' - ), - '[[:space:]]+', - ' ', - 'g' - )), '') -`; const CUSTOMER_IDENTITY_CTE = ` WITH customer_phone_by_name AS ( SELECT @@ -177,7 +159,6 @@ const getTopClientsForCampaign = async ({ days, limit, start, end } = {}) => { WITH campaign_orders AS ( SELECT orders.*, - ${CANONICAL_CAMPAIGN_CUSTOMER_NAME_SQL} as canonical_customer_name, ${WHATSAPP_CUSTOMER_PHONE_SQL} as whatsapp_phone FROM orders ) @@ -199,7 +180,9 @@ const getTopClientsForCampaign = async ({ days, limit, start, end } = {}) => { WHERE data_pedido_date >= $1::date AND data_pedido_date <= $2::date AND whatsapp_phone IS NOT NULL - GROUP BY COALESCE(canonical_customer_name, whatsapp_phone) + -- A campaign recipient is a WhatsApp destination, so each normalized + -- phone number must produce exactly one exported customer. + GROUP BY whatsapp_phone ORDER BY total_gasto DESC LIMIT $3; `, [range.start, range.end, normalizedLimit]); diff --git a/backend/test/campaignService.test.js b/backend/test/campaignService.test.js index e27d08c..242c047 100644 --- a/backend/test/campaignService.test.js +++ b/backend/test/campaignService.test.js @@ -75,7 +75,8 @@ test('getTopClientsForCampaign returns top clients for an explicit date range', assert.equal(queries.length, 1); assert.deepEqual(queries[0].params, ['2026-06-28', '2026-07-27', 1000]); - assert.match(queries[0].sql, /GROUP BY COALESCE\(canonical_customer_name, whatsapp_phone\)/); + assert.match(queries[0].sql, /GROUP BY whatsapp_phone/); + assert.doesNotMatch(queries[0].sql, /GROUP BY COALESCE\(canonical_customer_name, whatsapp_phone\)/); assert.match(queries[0].sql, /ARRAY_AGG\(DISTINCT whatsapp_phone\)/); assert.match(queries[0].sql, /ORDER BY total_gasto DESC/); }); @@ -95,7 +96,7 @@ test('getTopClientsForCampaign derives an inclusive 30 day range from the end da }); }); -test('getTopClientsForCampaign normalizes phones and ranks one row per canonical client name', async () => { +test('getTopClientsForCampaign normalizes phones and groups one row per WhatsApp number', async () => { await withCampaignService(async () => ({ rows: [] }), async ({ getTopClientsForCampaign }, queries) => { await getTopClientsForCampaign({ days: '30', @@ -105,6 +106,6 @@ test('getTopClientsForCampaign normalizes phones and ranks one row per canonical assert.match(queries[0].sql, /regexp_replace\(COALESCE\(cliente_fone, ''\), '\\D', '', 'g'\)/); assert.match(queries[0].sql, /WHEN length\(/); assert.match(queries[0].sql, /'55' \|\|/); - assert.match(queries[0].sql, /canonical_customer_name/); + assert.match(queries[0].sql, /GROUP BY whatsapp_phone/); }); });