Add shared-number detection and override data layer
- Add migration v19: creates outreach_shared_number_overrides table - Implement 7 new data-layer functions: - leadsSharingNumber: find leads sharing a phone number variant - isNumberSharedAcrossLeads: detect if number is shared by 2+ leads - getSharedNumberOverride: retrieve override entry for a number - isSharedNumberOverridden: check if number has an override - addSharedNumberOverride: create or update override with reason - removeSharedNumberOverride: delete override record - listSharedNumberGroups: group leads by shared numbers, reflect override state - Add 4 comprehensive tests covering migration, detection, override crud, and grouping - Update version assertions from 18 to 19 - Fix v17 migration test to properly clean up v19 table for schema reset Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
d330bb5ff0
commit
fe70436dbb
+76
@@ -643,6 +643,16 @@ const MIGRATIONS = [
|
||||
BEGIN
|
||||
SELECT RAISE(ABORT, 'transicao de status da fila invalida');
|
||||
END;
|
||||
`,
|
||||
// v19 - numeros compartilhados por mais de um lead (ex.: contabilidade que
|
||||
// registra o proprio telefone para varios CNPJs de clientes) podem ser
|
||||
// liberados manualmente apos revisao, revertendo o bloqueio automatico.
|
||||
`
|
||||
CREATE TABLE outreach_shared_number_overrides (
|
||||
numero TEXT PRIMARY KEY,
|
||||
motivo TEXT NOT NULL DEFAULT '',
|
||||
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
||||
);
|
||||
`
|
||||
];
|
||||
|
||||
@@ -946,6 +956,72 @@ export function removeOptOut(db, numero) {
|
||||
});
|
||||
}
|
||||
|
||||
export function leadsSharingNumber(db, numero) {
|
||||
const numbers = equivalentPhoneNumbers(numero);
|
||||
if (!numbers.length) return [];
|
||||
const placeholders = numbers.map(() => '?').join(', ');
|
||||
return db.prepare(`SELECT id, empresa, cnpj FROM leads WHERE whatsapp IN (${placeholders})`).all(...numbers);
|
||||
}
|
||||
|
||||
export function isNumberSharedAcrossLeads(db, numero) {
|
||||
return leadsSharingNumber(db, numero).length >= 2;
|
||||
}
|
||||
|
||||
export function getSharedNumberOverride(db, numero) {
|
||||
const numbers = equivalentPhoneNumbers(numero);
|
||||
if (!numbers.length) return null;
|
||||
const placeholders = numbers.map(() => '?').join(', ');
|
||||
return db.prepare(
|
||||
`SELECT numero, motivo, created_at FROM outreach_shared_number_overrides WHERE numero IN (${placeholders})`
|
||||
).get(...numbers) || null;
|
||||
}
|
||||
|
||||
export function isSharedNumberOverridden(db, numero) {
|
||||
return Boolean(getSharedNumberOverride(db, numero));
|
||||
}
|
||||
|
||||
export function addSharedNumberOverride(db, numero, motivo = '') {
|
||||
const numbers = equivalentPhoneNumbers(numero);
|
||||
if (!numbers.length) throw new Error('numero obrigatorio');
|
||||
return transaction(db, () => {
|
||||
const placeholders = numbers.map(() => '?').join(', ');
|
||||
db.prepare(`DELETE FROM outreach_shared_number_overrides WHERE numero IN (${placeholders})`).run(...numbers);
|
||||
db.prepare('INSERT INTO outreach_shared_number_overrides (numero, motivo) VALUES (?, ?)')
|
||||
.run(numbers[0], String(motivo || '').trim());
|
||||
return db.prepare(
|
||||
'SELECT numero, motivo, created_at FROM outreach_shared_number_overrides WHERE numero = ?'
|
||||
).get(numbers[0]);
|
||||
});
|
||||
}
|
||||
|
||||
export function removeSharedNumberOverride(db, numero) {
|
||||
const numbers = equivalentPhoneNumbers(numero);
|
||||
if (!numbers.length) return false;
|
||||
return transaction(db, () => {
|
||||
const placeholders = numbers.map(() => '?').join(', ');
|
||||
return db.prepare(`DELETE FROM outreach_shared_number_overrides WHERE numero IN (${placeholders})`).run(...numbers).changes > 0;
|
||||
});
|
||||
}
|
||||
|
||||
export function listSharedNumberGroups(db) {
|
||||
const rows = db.prepare("SELECT id, empresa, cnpj, whatsapp FROM leads WHERE whatsapp <> ''").all();
|
||||
const groups = new Map();
|
||||
for (const row of rows) {
|
||||
const equivalents = equivalentPhoneNumbers(row.whatsapp);
|
||||
if (!equivalents.length) continue;
|
||||
const key = equivalents.slice().sort().join(':');
|
||||
if (!groups.has(key)) groups.set(key, { numero: equivalents[0], leads: [] });
|
||||
groups.get(key).leads.push({ id: row.id, empresa: row.empresa, cnpj: row.cnpj });
|
||||
}
|
||||
return [...groups.values()]
|
||||
.filter((group) => group.leads.length >= 2)
|
||||
.map((group) => {
|
||||
const override = getSharedNumberOverride(db, group.numero);
|
||||
return { ...group, liberado: Boolean(override), motivo: override?.motivo || null };
|
||||
})
|
||||
.sort((a, b) => b.leads.length - a.leads.length || a.numero.localeCompare(b.numero));
|
||||
}
|
||||
|
||||
export function saveWhatsAppVerification(db, result) {
|
||||
const numero = normalizePhone(result?.numero);
|
||||
if (!numero) throw new Error('numero obrigatorio');
|
||||
|
||||
Reference in New Issue
Block a user