diff --git a/crm/lead_syncing/doctype/failed_lead_sync_log/__init__.py b/crm/lead_syncing/doctype/failed_lead_sync_log/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/crm/lead_syncing/doctype/failed_lead_sync_log/failed_lead_sync_log.js b/crm/lead_syncing/doctype/failed_lead_sync_log/failed_lead_sync_log.js new file mode 100644 index 00000000..fddfef54 --- /dev/null +++ b/crm/lead_syncing/doctype/failed_lead_sync_log/failed_lead_sync_log.js @@ -0,0 +1,8 @@ +// Copyright (c) 2025, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +// frappe.ui.form.on("Failed Lead Sync Log", { +// refresh(frm) { + +// }, +// }); diff --git a/crm/lead_syncing/doctype/failed_lead_sync_log/failed_lead_sync_log.json b/crm/lead_syncing/doctype/failed_lead_sync_log/failed_lead_sync_log.json new file mode 100644 index 00000000..20183a51 --- /dev/null +++ b/crm/lead_syncing/doctype/failed_lead_sync_log/failed_lead_sync_log.json @@ -0,0 +1,58 @@ +{ + "actions": [], + "allow_rename": 1, + "creation": "2025-10-19 17:29:10.261307", + "doctype": "DocType", + "engine": "InnoDB", + "field_order": [ + "type", + "lead_data" + ], + "fields": [ + { + "default": "Failure", + "fieldname": "type", + "fieldtype": "Select", + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Type", + "options": "Duplicate\nFailure", + "read_only": 1 + }, + { + "fieldname": "lead_data", + "fieldtype": "Code", + "label": "Lead Data", + "options": "JSON", + "read_only": 1 + } + ], + "grid_page_length": 50, + "in_create": 1, + "index_web_pages_for_search": 1, + "links": [], + "modified": "2025-10-19 17:41:28.640446", + "modified_by": "Administrator", + "module": "Lead Syncing", + "name": "Failed Lead Sync Log", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, + "write": 1 + } + ], + "row_format": "Dynamic", + "rows_threshold_for_grid_search": 20, + "sort_field": "creation", + "sort_order": "DESC", + "states": [] +} diff --git a/crm/lead_syncing/doctype/failed_lead_sync_log/failed_lead_sync_log.py b/crm/lead_syncing/doctype/failed_lead_sync_log/failed_lead_sync_log.py new file mode 100644 index 00000000..aaef829c --- /dev/null +++ b/crm/lead_syncing/doctype/failed_lead_sync_log/failed_lead_sync_log.py @@ -0,0 +1,21 @@ +# Copyright (c) 2025, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +# import frappe +from frappe.model.document import Document + + +class FailedLeadSyncLog(Document): + # begin: auto-generated types + # This code is auto-generated. Do not modify anything in this block. + + from typing import TYPE_CHECKING + + if TYPE_CHECKING: + from frappe.types import DF + + lead_data: DF.Code | None + type: DF.Literal["Duplicate", "Failure"] + # end: auto-generated types + + pass diff --git a/crm/lead_syncing/doctype/failed_lead_sync_log/test_failed_lead_sync_log.py b/crm/lead_syncing/doctype/failed_lead_sync_log/test_failed_lead_sync_log.py new file mode 100644 index 00000000..8b9448f5 --- /dev/null +++ b/crm/lead_syncing/doctype/failed_lead_sync_log/test_failed_lead_sync_log.py @@ -0,0 +1,22 @@ +# Copyright (c) 2025, Frappe Technologies Pvt. Ltd. and Contributors +# See license.txt + +# import frappe +from frappe.tests import IntegrationTestCase + + +# On IntegrationTestCase, the doctype test records and all +# link-field test record dependencies are recursively loaded +# Use these module variables to add/remove to/from that list +EXTRA_TEST_RECORD_DEPENDENCIES = [] # eg. ["User"] +IGNORE_TEST_RECORD_DEPENDENCIES = [] # eg. ["User"] + + + +class IntegrationTestFailedLeadSyncLog(IntegrationTestCase): + """ + Integration tests for FailedLeadSyncLog. + Use this class for testing interactions between multiple components. + """ + + pass diff --git a/crm/lead_syncing/doctype/lead_sync_source/facebook.py b/crm/lead_syncing/doctype/lead_sync_source/facebook.py index f5f3c73b..53e27ca3 100644 --- a/crm/lead_syncing/doctype/lead_sync_source/facebook.py +++ b/crm/lead_syncing/doctype/lead_sync_source/facebook.py @@ -17,9 +17,9 @@ def sync_leads_from_facebook(access_token: str, lead_form_id: str) -> None: last_synced_at = frappe.db.get_value( "Lead Sync Source", {"facebook_lead_form": lead_form_id}, "last_synced_at" ) - timestamp = frappe.utils.data.get_timestamp(last_synced_at) - filtering = f"filtering=[{{'field':'time_created','operator':'GREATER_THAN','value':{timestamp}}}]" if last_synced_at: + timestamp = frappe.utils.data.get_timestamp(last_synced_at) + filtering = f"filtering=[{{'field':'time_created','operator':'GREATER_THAN','value':{timestamp}}}]" url = f"{url}?{filtering}" leads = make_get_request( @@ -57,13 +57,20 @@ def sync_leads_from_facebook(access_token: str, lead_form_id: str) -> None: ).insert(ignore_permissions=True) except frappe.UniqueValidationError: # Skip duplicate leads based on facebook_lead_id - frappe.log_error("Duplicate lead skipped") - continue + # TODO: de-duplication based on field values + frappe.get_doc( + {"doctype": "Failed Lead Sync Log", "type": "Duplicate", "lead_data": frappe.as_json(lead)} + ).insert(ignore_permissions=True) + except Exception: + frappe.get_doc( + {"doctype": "Failed Lead Sync Log", "type": "Failure", "lead_data": frappe.as_json(lead)} + ).insert(ignore_permissions=True) frappe.db.set_value( "Lead Sync Source", {"facebook_lead_form": lead_form_id}, "last_synced_at", frappe.utils.now() ) + @frappe.whitelist() def fetch_and_store_pages_from_facebook(access_token: str) -> list[dict]: if not access_token: @@ -85,6 +92,7 @@ def fetch_and_store_pages_from_facebook(access_token: str) -> list[dict]: return pages + def get_fb_account_details(access_token: str) -> dict: url = get_fb_graph_api_url("me") try: @@ -140,12 +148,11 @@ def create_facebook_lead_form_in_db(form: dict, page_id: str) -> None: ) form_doc.insert(ignore_permissions=True) + @frappe.whitelist() def get_pages_with_forms() -> list[dict]: pages = frappe.db.get_all("Facebook Page", fields=["id", "name"]) for page in pages: - forms = frappe.db.get_all( - "Facebook Lead Form", filters={"page": page["id"]}, fields=["id", "name"] - ) + forms = frappe.db.get_all("Facebook Lead Form", filters={"page": page["id"]}, fields=["id", "name"]) page["forms"] = forms - return pages \ No newline at end of file + return pages diff --git a/crm/lead_syncing/doctype/lead_sync_source/lead_sync_source.py b/crm/lead_syncing/doctype/lead_sync_source/lead_sync_source.py index 4dae3ce2..63a728c8 100644 --- a/crm/lead_syncing/doctype/lead_sync_source/lead_sync_source.py +++ b/crm/lead_syncing/doctype/lead_sync_source/lead_sync_source.py @@ -53,7 +53,8 @@ class LeadSyncSource(Document): @frappe.whitelist() def sync_leads(self): - frappe.enqueue_doc(self.doctype, self.name, "_sync_leads", queue="long") + self._sync_leads() + # frappe.enqueue_doc(self.doctype, self.name, "_sync_leads", queue="long") def _sync_leads(self): if self.type == "Facebook" and self.access_token: