feat: field based de-dupe

This commit is contained in:
Hussain Nagaria 2025-10-19 18:57:18 +05:30
parent 1b87635d3d
commit 38e10f91cb
3 changed files with 34 additions and 8 deletions

View File

@ -9,7 +9,9 @@
"column_break_dhay", "column_break_dhay",
"source", "source",
"section_break_fhot", "section_break_fhot",
"lead_data" "lead_data",
"section_break_knec",
"traceback"
], ],
"fields": [ "fields": [
{ {
@ -43,13 +45,23 @@
{ {
"fieldname": "section_break_fhot", "fieldname": "section_break_fhot",
"fieldtype": "Section Break" "fieldtype": "Section Break"
},
{
"fieldname": "section_break_knec",
"fieldtype": "Section Break"
},
{
"fieldname": "traceback",
"fieldtype": "Code",
"label": "Traceback",
"read_only": 1
} }
], ],
"grid_page_length": 50, "grid_page_length": 50,
"in_create": 1, "in_create": 1,
"index_web_pages_for_search": 1, "index_web_pages_for_search": 1,
"links": [], "links": [],
"modified": "2025-10-19 18:30:15.381205", "modified": "2025-10-19 18:53:32.192551",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Lead Syncing", "module": "Lead Syncing",
"name": "Failed Lead Sync Log", "name": "Failed Lead Sync Log",

View File

@ -16,6 +16,7 @@ class FailedLeadSyncLog(Document):
lead_data: DF.Code | None lead_data: DF.Code | None
source: DF.Link | None source: DF.Link | None
traceback: DF.Code | None
type: DF.Literal["Duplicate", "Failure"] type: DF.Literal["Duplicate", "Failure"]
# end: auto-generated types # end: auto-generated types

View File

@ -1,10 +1,15 @@
import frappe import frappe
from frappe.exceptions import ValidationError
from frappe.integrations.utils import make_get_request from frappe.integrations.utils import make_get_request
FB_GRAPH_API_BASE = "https://graph.facebook.com" FB_GRAPH_API_BASE = "https://graph.facebook.com"
FB_GRAPH_API_VERSION = "v23.0" FB_GRAPH_API_VERSION = "v23.0"
class DuplicateLeadError(ValidationError):
pass
def get_fb_graph_api_url(endpoint: str) -> str: def get_fb_graph_api_url(endpoint: str) -> str:
if endpoint.startswith("/"): if endpoint.startswith("/"):
endpoint = endpoint[1:] endpoint = endpoint[1:]
@ -40,18 +45,17 @@ class FacebookSyncSource:
crm_lead_data["facebook_form_id"] = self.form_id crm_lead_data["facebook_form_id"] = self.form_id
try: try:
self.validate_duplicate_lead(crm_lead_data, question_to_field_map)
frappe.get_doc( frappe.get_doc(
{ {
"doctype": "CRM Lead", "doctype": "CRM Lead",
**crm_lead_data, **crm_lead_data,
} }
).insert(ignore_permissions=True) ).insert(ignore_permissions=True)
except frappe.UniqueValidationError: except (frappe.UniqueValidationError, DuplicateLeadError):
# Skip duplicate leads based on facebook_lead_id
# TODO: de-duplication based on field values
self.create_failure_log(lead, "Duplicate") self.create_failure_log(lead, "Duplicate")
except Exception: except Exception:
self.create_failure_log(lead) self.create_failure_log(lead, traceback=frappe.get_traceback(with_context=True))
self.update_last_synced_at() self.update_last_synced_at()
@ -89,13 +93,16 @@ class FacebookSyncSource:
"Lead Sync Source", self.source_name or {"facebook_lead_form": self.form_id}, "last_synced_at" "Lead Sync Source", self.source_name or {"facebook_lead_form": self.form_id}, "last_synced_at"
) )
def create_failure_log(self, lead_data: dict | None = None, type: str = "Failure"): def create_failure_log(
self, lead_data: dict | None = None, type: str = "Failure", traceback: str | None = None
):
return frappe.get_doc( return frappe.get_doc(
{ {
"doctype": "Failed Lead Sync Log", "doctype": "Failed Lead Sync Log",
"type": type, "type": type,
"lead_data": frappe.as_json(lead_data), "lead_data": frappe.as_json(lead_data),
"source": self.get_source_name() "source": self.get_source_name(),
"traceback": traceback,
} }
).insert(ignore_permissions=True) ).insert(ignore_permissions=True)
@ -113,6 +120,12 @@ class FacebookSyncSource:
return frappe.db.get_value("Lead Sync Source", {"facebook_lead_form": self.form_id}, "name") return frappe.db.get_value("Lead Sync Source", {"facebook_lead_form": self.form_id}, "name")
def validate_duplicate_lead(self, lead_data: dict, field_mapping: dict):
validation_filters = {crm_field: lead_data[crm_field] for crm_field in field_mapping.values()}
validation_filters["facebook_form_id"] = lead_data["facebook_form_id"] # only for this campaign
if frappe.db.exists("CRM Lead", validation_filters):
raise DuplicateLeadError
@frappe.whitelist() @frappe.whitelist()
def fetch_and_store_pages_from_facebook(access_token: str) -> list[dict]: def fetch_and_store_pages_from_facebook(access_token: str) -> list[dict]: