├── mansico_meta_integration ├── config │ └── __init__.py ├── public │ └── .gitkeep ├── www │ └── __init__.py ├── templates │ ├── __init__.py │ └── pages │ │ └── __init__.py ├── __init__.py ├── mansico_meta_integration │ ├── __init__.py │ ├── doctype │ │ ├── __init__.py │ │ ├── page_id │ │ │ ├── __init__.py │ │ │ ├── page_id.js │ │ │ ├── test_page_id.py │ │ │ ├── page_id.py │ │ │ └── page_id.json │ │ ├── meta_forms │ │ │ ├── __init__.py │ │ │ ├── meta_forms.py │ │ │ └── meta_forms.json │ │ ├── sync_new_add │ │ │ ├── __init__.py │ │ │ ├── sync_new_add.js │ │ │ ├── test_sync_new_add.py │ │ │ ├── meta_integraion_objects.py │ │ │ ├── sync_new_add.json │ │ │ └── sync_new_add.py │ │ ├── map_lead_field │ │ │ ├── __init__.py │ │ │ ├── map_lead_field.py │ │ │ └── map_lead_field.json │ │ └── meta_facebook_settings │ │ │ ├── __init__.py │ │ │ ├── meta_facebook_settings.js │ │ │ ├── test_meta_facebook_settings.py │ │ │ ├── meta_facebook_settings.py │ │ │ └── meta_facebook_settings.json │ ├── workspace │ │ └── meta_leads │ │ │ └── meta_leads.json │ └── custom │ │ ├── note.json │ │ └── lead.json ├── modules.txt ├── patches.txt ├── hooks.py ├── overrides.py └── tasks.py ├── .gitignore ├── pyproject.toml ├── CHANGELOG.md ├── license.txt └── README.md /mansico_meta_integration/config/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /mansico_meta_integration/public/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /mansico_meta_integration/www/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /mansico_meta_integration/templates/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /mansico_meta_integration/templates/pages/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /mansico_meta_integration/__init__.py: -------------------------------------------------------------------------------- 1 | __version__ = "1.2.1" 2 | -------------------------------------------------------------------------------- /mansico_meta_integration/mansico_meta_integration/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /mansico_meta_integration/modules.txt: -------------------------------------------------------------------------------- 1 | Mansico Meta Integration -------------------------------------------------------------------------------- /mansico_meta_integration/mansico_meta_integration/doctype/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /mansico_meta_integration/mansico_meta_integration/doctype/page_id/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /mansico_meta_integration/mansico_meta_integration/doctype/meta_forms/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /mansico_meta_integration/mansico_meta_integration/doctype/sync_new_add/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /mansico_meta_integration/mansico_meta_integration/doctype/map_lead_field/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /mansico_meta_integration/mansico_meta_integration/doctype/meta_facebook_settings/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *.pyc 3 | *.egg-info 4 | *.swp 5 | tags 6 | node_modules 7 | __pycache__ -------------------------------------------------------------------------------- /mansico_meta_integration/mansico_meta_integration/doctype/page_id/page_id.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024, Mansy and contributors 2 | // For license information, please see license.txt 3 | 4 | // frappe.ui.form.on("Page ID", { 5 | // refresh(frm) { 6 | 7 | // }, 8 | // }); 9 | -------------------------------------------------------------------------------- /mansico_meta_integration/mansico_meta_integration/doctype/sync_new_add/sync_new_add.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023, mansy and contributors 2 | // For license information, please see license.txt 3 | 4 | // frappe.ui.form.on("Sync New Add", { 5 | // refresh(frm) { 6 | 7 | // }, 8 | // }); 9 | -------------------------------------------------------------------------------- /mansico_meta_integration/mansico_meta_integration/doctype/page_id/test_page_id.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2024, Mansy and Contributors 2 | # See license.txt 3 | 4 | # import frappe 5 | from frappe.tests.utils import FrappeTestCase 6 | 7 | 8 | class TestPageID(FrappeTestCase): 9 | pass 10 | -------------------------------------------------------------------------------- /mansico_meta_integration/mansico_meta_integration/doctype/page_id/page_id.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2024, Mansy and contributors 2 | # For license information, please see license.txt 3 | 4 | # import frappe 5 | from frappe.model.document import Document 6 | 7 | 8 | class PageID(Document): 9 | pass 10 | -------------------------------------------------------------------------------- /mansico_meta_integration/mansico_meta_integration/doctype/sync_new_add/test_sync_new_add.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2024, Mansy and Contributors 2 | # See license.txt 3 | 4 | # import frappe 5 | from frappe.tests.utils import FrappeTestCase 6 | 7 | 8 | class TestSyncNewAdd(FrappeTestCase): 9 | pass 10 | -------------------------------------------------------------------------------- /mansico_meta_integration/mansico_meta_integration/doctype/meta_forms/meta_forms.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2024, Mansy and contributors 2 | # For license information, please see license.txt 3 | 4 | # import frappe 5 | from frappe.model.document import Document 6 | 7 | 8 | class MetaForms(Document): 9 | pass 10 | -------------------------------------------------------------------------------- /mansico_meta_integration/mansico_meta_integration/doctype/meta_facebook_settings/meta_facebook_settings.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024, Mansy and contributors 2 | // For license information, please see license.txt 3 | 4 | // frappe.ui.form.on("Meta Facebook Settings", { 5 | // refresh(frm) { 6 | 7 | // }, 8 | // }); 9 | -------------------------------------------------------------------------------- /mansico_meta_integration/mansico_meta_integration/doctype/map_lead_field/map_lead_field.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2025, Mansy and contributors 2 | # For license information, please see license.txt 3 | 4 | # import frappe 5 | from frappe.model.document import Document 6 | 7 | 8 | class MapLeadField(Document): 9 | pass 10 | -------------------------------------------------------------------------------- /mansico_meta_integration/mansico_meta_integration/doctype/meta_facebook_settings/test_meta_facebook_settings.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2024, Mansy and Contributors 2 | # See license.txt 3 | 4 | # import frappe 5 | from frappe.tests.utils import FrappeTestCase 6 | 7 | 8 | class TestMetaFacebookSettings(FrappeTestCase): 9 | pass 10 | -------------------------------------------------------------------------------- /mansico_meta_integration/patches.txt: -------------------------------------------------------------------------------- 1 | [pre_model_sync] 2 | # Patches added in this section will be executed before doctypes are migrated 3 | # Read docs to understand patches: https://frappeframework.com/docs/v14/user/en/database-migrations 4 | 5 | [post_model_sync] 6 | # Patches added in this section will be executed after doctypes are migrated -------------------------------------------------------------------------------- /mansico_meta_integration/mansico_meta_integration/doctype/meta_facebook_settings/meta_facebook_settings.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2024, Mansy and contributors 2 | # For license information, please see license.txt 3 | 4 | # import frappe 5 | from frappe.model.document import Document 6 | 7 | 8 | class MetaFacebookSettings(Document): 9 | pass 10 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "mansico_meta_integration" 3 | authors = [ 4 | { name = "Mansy", email = "ahmedmansy265@gmail.com"} 5 | ] 6 | description = "This project is about syncing Facebook leads with ERPnext, When Clients fill Facebook ads instant forms app automatic fetch new created leads and create lead automatic in Lead doctype. Also on changing the Lead Status the new status sent to meta Pixel." 7 | requires-python = ">=3.10" 8 | readme = "README.md" 9 | dynamic = ["version"] 10 | dependencies = [ 11 | # "frappe~=15.0.0" # Installed and managed by bench. 12 | ] 13 | 14 | [build-system] 15 | requires = ["flit_core >=3.4,<4"] 16 | build-backend = "flit_core.buildapi" 17 | 18 | # These dependencies are only installed when developer mode is enabled 19 | [tool.bench.dev-dependencies] 20 | # package_name = "~=1.1.0" 21 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## [Unreleased] 4 | 5 | ## [1.2.1] 6 | ### Added 7 | - **fix wrong release version** 8 | 9 | ## [1.2.0] 10 | ### Added 11 | - **Support Facebook Fields Datatypes** 12 | - Bug fixes and improvements. 13 | 14 | ## [1.1.0] - 2025-02-09 15 | - **Dynamic Lead Field Mapping**: Introduced dynamic mapping in the `create_lead` method. Now, you can customize which form fields are mapped to lead fields. This adds flexibility when dealing with different field names. 16 | - **No Mandatory `email_id` for Lead**: Removed the mandatory requirement for `email_id` when creating a Lead. Only `first_name` is now required to create a Lead. 17 | - **Improved Error Handling**: Refined error handling logic to provide better error reporting and debugging capabilities during integration. 18 | 19 | ### Fixed 20 | - **Bug Fixes**: Fixed various issues related to lead processing and integration logic. 21 | 22 | ## [1.0.0] - 2025-02-01 23 | ### Added 24 | - **Initial Release**: Launched the first version of Mansico Meta Integration, which includes support for importing leads from Facebook via the Facebook Lead Ads API. 25 | -------------------------------------------------------------------------------- /license.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Ahmed Mansy 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /mansico_meta_integration/hooks.py: -------------------------------------------------------------------------------- 1 | app_name = "mansico_meta_integration" 2 | app_title = "Mansico Meta Integration" 3 | app_publisher = "Mansy" 4 | app_description = "This project is about syncing Facebook leads with ERPnext, When Clients fill Facebook ads instant forms app automatic fetch new created leads and create lead automatic in Lead doctype. Also on changing the Lead Status the new status sent to meta Pixel." 5 | app_email = "ahmedmansy265@gmail.com" 6 | app_license = "mit" 7 | required_apps = ["erpnext"] 8 | 9 | doc_events = { 10 | "Lead": { 11 | # will run before a ToDo record is inserted into database 12 | "validate": "mansico_meta_integration.overrides.validate_lead", 13 | } 14 | } 15 | 16 | 17 | doc_events["CRM Lead"] = { 18 | "validate": "mansico_meta_integration.overrides.validate_crmlead", 19 | } 20 | # Scheduled Tasks 21 | # --------------- 22 | 23 | scheduler_events = { 24 | "all": [ 25 | "mansico_meta_integration.tasks.all" 26 | ], 27 | "daily": [ 28 | "mansico_meta_integration.tasks.daily" 29 | ], 30 | "hourly": [ 31 | "mansico_meta_integration.tasks.hourly" 32 | ], 33 | "weekly": [ 34 | "mansico_meta_integration.tasks.weekly" 35 | ], 36 | "monthly": [ 37 | "mansico_meta_integration.tasks.monthly" 38 | ], 39 | } 40 | -------------------------------------------------------------------------------- /mansico_meta_integration/mansico_meta_integration/doctype/sync_new_add/meta_integraion_objects.py: -------------------------------------------------------------------------------- 1 | class UserData: 2 | def __init__(self, lead_id): 3 | self.lead_id = lead_id 4 | 5 | def to_dict(self): 6 | return { 7 | "lead_id": self.lead_id 8 | } 9 | 10 | class CustomData: 11 | def __init__(self, event_source, lead_event_source): 12 | self.event_source = event_source 13 | self.lead_event_source = lead_event_source 14 | 15 | def to_dict(self): 16 | return { 17 | "event_source": self.event_source, 18 | "lead_event_source": self.lead_event_source 19 | } 20 | class Payload: 21 | def __init__(self, event_name, event_time, action_source, user_data, custom_data): 22 | self.event_name = event_name 23 | self.event_time = event_time 24 | self.action_source = action_source 25 | self.user_data = user_data 26 | self.custom_data = custom_data 27 | 28 | def to_dict(self): 29 | return { 30 | "event_name": self.event_name, 31 | "event_time": self.event_time, 32 | "action_source": self.action_source, 33 | "user_data": self.user_data.to_dict(), 34 | "custom_data": self.custom_data.to_dict() 35 | } -------------------------------------------------------------------------------- /mansico_meta_integration/overrides.py: -------------------------------------------------------------------------------- 1 | import frappe 2 | from frappe.utils.scheduler import is_scheduler_disabled 3 | from frappe import _ 4 | from mansico_meta_integration.mansico_meta_integration.doctype.sync_new_add.sync_new_add import FetchLeads 5 | 6 | def validate_lead(doc, method=None): 7 | _validate_lead_status_change(doc, "Lead") 8 | 9 | def validate_crmlead(doc, method=None): 10 | _validate_lead_status_change(doc, "CRM Lead") 11 | 12 | def _validate_lead_status_change(doc, doctype): 13 | """ 14 | Helper function to validate status change and trigger Facebook lead creation. 15 | """ 16 | if is_scheduler_disabled(): 17 | frappe.throw(_("Please enable the Scheduler first.")) 18 | 19 | if not doc.is_new() and doc.custom_meta_lead_id: 20 | old_doc = doc.get_doc_before_save() 21 | if old_doc and old_doc.status != doc.status: 22 | try: 23 | lead = frappe.get_doc(doctype, doc.name) 24 | FetchLeads.create_lead_in_facebook(lead) 25 | except Exception as e: 26 | frappe.log_error( 27 | title=f"Error in {doctype} Facebook Lead Creation", 28 | message=f"An error occurred while creating a Facebook lead for {doctype} {doc.name}: {str(e)}" 29 | ) -------------------------------------------------------------------------------- /mansico_meta_integration/mansico_meta_integration/doctype/map_lead_field/map_lead_field.json: -------------------------------------------------------------------------------- 1 | { 2 | "actions": [], 3 | "allow_rename": 1, 4 | "creation": "2025-02-09 17:55:46.121568", 5 | "doctype": "DocType", 6 | "editable_grid": 1, 7 | "engine": "InnoDB", 8 | "field_order": [ 9 | "form_field", 10 | "form_field_label", 11 | "form_field_type", 12 | "lead_field" 13 | ], 14 | "fields": [ 15 | { 16 | "fieldname": "form_field", 17 | "fieldtype": "Data", 18 | "in_list_view": 1, 19 | "label": "Form Field", 20 | "read_only": 1 21 | }, 22 | { 23 | "fieldname": "form_field_label", 24 | "fieldtype": "Data", 25 | "in_list_view": 1, 26 | "label": "Form Field Label", 27 | "read_only": 1 28 | }, 29 | { 30 | "fieldname": "form_field_type", 31 | "fieldtype": "Data", 32 | "in_list_view": 1, 33 | "label": "Form Field Type", 34 | "read_only": 1 35 | }, 36 | { 37 | "fieldname": "lead_field", 38 | "fieldtype": "Data", 39 | "in_list_view": 1, 40 | "label": "Lead Field Name", 41 | "reqd": 1 42 | } 43 | ], 44 | "index_web_pages_for_search": 1, 45 | "istable": 1, 46 | "links": [], 47 | "modified": "2025-02-09 19:24:01.715671", 48 | "modified_by": "Administrator", 49 | "module": "Mansico Meta Integration", 50 | "name": "Map Lead Field", 51 | "owner": "Administrator", 52 | "permissions": [], 53 | "sort_field": "modified", 54 | "sort_order": "DESC", 55 | "states": [] 56 | } -------------------------------------------------------------------------------- /mansico_meta_integration/tasks.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | import frappe 4 | from mansico_meta_integration.mansico_meta_integration.doctype.sync_new_add.sync_new_add import FetchLeads 5 | 6 | 7 | @frappe.whitelist() 8 | def all(): 9 | sync_new_add = frappe.db.get_all("Sync New Add", {"event_frequency": "All", "docstatus": 1}, pluck="name") 10 | for name in sync_new_add: 11 | fetch = FetchLeads(name) 12 | fetch.fetch_leads() 13 | 14 | @frappe.whitelist() 15 | def daily(): 16 | sync_new_add = frappe.db.get_all("Sync New Add", {"event_frequency": "Daily", "docstatus": 1}, pluck="name") 17 | for name in sync_new_add: 18 | fetch = FetchLeads(name) 19 | fetch.fetch_leads() 20 | 21 | @frappe.whitelist() 22 | def hourly(): 23 | sync_new_add = frappe.db.get_all("Sync New Add", {"event_frequency": "Hourly", "docstatus": 1}, pluck="name") 24 | for name in sync_new_add: 25 | fetch = FetchLeads(name) 26 | fetch.fetch_leads() 27 | 28 | @frappe.whitelist() 29 | def weekly(): 30 | sync_new_add = frappe.db.get_all("Sync New Add", {"event_frequency": "Weekly", "docstatus": 1}, pluck="name") 31 | for name in sync_new_add: 32 | fetch = FetchLeads(name) 33 | fetch.fetch_leads() 34 | 35 | @frappe.whitelist() 36 | def monthly(): 37 | sync_new_add = frappe.db.get_all("Sync New Add", {"event_frequency": "Monthly", "docstatus": 1}, pluck="name") 38 | for name in sync_new_add: 39 | fetch = FetchLeads(name) 40 | fetch.fetch_leads() -------------------------------------------------------------------------------- /mansico_meta_integration/mansico_meta_integration/doctype/meta_forms/meta_forms.json: -------------------------------------------------------------------------------- 1 | { 2 | "actions": [], 3 | "allow_rename": 1, 4 | "creation": "2024-01-12 04:02:17.085239", 5 | "default_view": "List", 6 | "doctype": "DocType", 7 | "editable_grid": 1, 8 | "engine": "InnoDB", 9 | "field_order": [ 10 | "form_id", 11 | "form_name", 12 | "created_time", 13 | "leads_count", 14 | "page", 15 | "questions" 16 | ], 17 | "fields": [ 18 | { 19 | "fieldname": "form_id", 20 | "fieldtype": "Data", 21 | "in_list_view": 1, 22 | "label": "Form Id", 23 | "read_only": 1 24 | }, 25 | { 26 | "fieldname": "form_name", 27 | "fieldtype": "Data", 28 | "in_list_view": 1, 29 | "label": "Form", 30 | "read_only": 1 31 | }, 32 | { 33 | "fieldname": "created_time", 34 | "fieldtype": "Data", 35 | "in_list_view": 1, 36 | "label": "created_time", 37 | "read_only": 1 38 | }, 39 | { 40 | "fieldname": "leads_count", 41 | "fieldtype": "Data", 42 | "in_list_view": 1, 43 | "label": "leads_count", 44 | "read_only": 1 45 | }, 46 | { 47 | "fieldname": "page", 48 | "fieldtype": "JSON", 49 | "in_list_view": 1, 50 | "label": "page", 51 | "read_only": 1 52 | }, 53 | { 54 | "fieldname": "questions", 55 | "fieldtype": "JSON", 56 | "label": "questions", 57 | "read_only": 1 58 | } 59 | ], 60 | "index_web_pages_for_search": 1, 61 | "istable": 1, 62 | "links": [], 63 | "modified": "2024-01-12 04:02:17.085239", 64 | "modified_by": "Administrator", 65 | "module": "Mansico Meta Integration", 66 | "name": "Meta Forms", 67 | "owner": "Administrator", 68 | "permissions": [], 69 | "sort_field": "modified", 70 | "sort_order": "DESC", 71 | "states": [] 72 | } -------------------------------------------------------------------------------- /mansico_meta_integration/mansico_meta_integration/workspace/meta_leads/meta_leads.json: -------------------------------------------------------------------------------- 1 | { 2 | "charts": [], 3 | "content": "[{\"id\":\"kV9jRvU-En\",\"type\":\"card\",\"data\":{\"card_name\":\"Facebook Integration\",\"col\":4}}]", 4 | "creation": "2024-01-12 04:12:45.415313", 5 | "custom_blocks": [], 6 | "docstatus": 0, 7 | "doctype": "Workspace", 8 | "for_user": "", 9 | "hide_custom": 0, 10 | "icon": "color-review-points", 11 | "idx": 0, 12 | "indicator_color": "green", 13 | "is_hidden": 0, 14 | "label": "Meta Leads", 15 | "links": [ 16 | { 17 | "description": "Welcome To Mansico Meta Integration Go throw the doctypes below sequentially", 18 | "hidden": 0, 19 | "is_query_report": 0, 20 | "label": "Facebook Integration", 21 | "link_count": 3, 22 | "link_type": "DocType", 23 | "onboard": 0, 24 | "type": "Card Break" 25 | }, 26 | { 27 | "hidden": 0, 28 | "is_query_report": 0, 29 | "label": "Meta Facebook Settings", 30 | "link_count": 0, 31 | "link_to": "Meta Facebook Settings", 32 | "link_type": "DocType", 33 | "onboard": 0, 34 | "type": "Link" 35 | }, 36 | { 37 | "hidden": 0, 38 | "is_query_report": 0, 39 | "label": "Page ID", 40 | "link_count": 0, 41 | "link_to": "Page ID", 42 | "link_type": "DocType", 43 | "onboard": 0, 44 | "type": "Link" 45 | }, 46 | { 47 | "hidden": 0, 48 | "is_query_report": 0, 49 | "label": "Sync Leads", 50 | "link_count": 0, 51 | "link_to": "Sync New Add", 52 | "link_type": "DocType", 53 | "onboard": 0, 54 | "type": "Link" 55 | } 56 | ], 57 | "modified": "2025-02-09 20:20:35.643671", 58 | "modified_by": "Administrator", 59 | "module": "Mansico Meta Integration", 60 | "name": "Meta Leads", 61 | "number_cards": [], 62 | "owner": "Administrator", 63 | "parent_page": "", 64 | "public": 1, 65 | "quick_lists": [], 66 | "roles": [], 67 | "sequence_id": 1.0, 68 | "shortcuts": [], 69 | "title": "Meta Leads" 70 | } -------------------------------------------------------------------------------- /mansico_meta_integration/mansico_meta_integration/doctype/page_id/page_id.json: -------------------------------------------------------------------------------- 1 | { 2 | "actions": [], 3 | "allow_rename": 1, 4 | "autoname": "field:page_id", 5 | "creation": "2024-01-12 04:02:35.302590", 6 | "default_view": "List", 7 | "doctype": "DocType", 8 | "engine": "InnoDB", 9 | "field_order": [ 10 | "page_id", 11 | "name1", 12 | "pixel_id", 13 | "pixel_access_token", 14 | "page_json" 15 | ], 16 | "fields": [ 17 | { 18 | "allow_in_quick_entry": 1, 19 | "fieldname": "page_id", 20 | "fieldtype": "Data", 21 | "in_list_view": 1, 22 | "label": "Page ID", 23 | "reqd": 1, 24 | "unique": 1 25 | }, 26 | { 27 | "allow_in_quick_entry": 1, 28 | "fieldname": "name1", 29 | "fieldtype": "Data", 30 | "label": "Page Name", 31 | "reqd": 1 32 | }, 33 | { 34 | "fieldname": "page_json", 35 | "fieldtype": "JSON", 36 | "label": "page_json", 37 | "read_only": 1 38 | }, 39 | { 40 | "fieldname": "pixel_access_token", 41 | "fieldtype": "Small Text", 42 | "label": "Pixel Access Token", 43 | "reqd": 1 44 | }, 45 | { 46 | "fieldname": "pixel_id", 47 | "fieldtype": "Data", 48 | "in_list_view": 1, 49 | "in_standard_filter": 1, 50 | "label": "Pixel ID", 51 | "reqd": 1 52 | } 53 | ], 54 | "index_web_pages_for_search": 1, 55 | "links": [], 56 | "modified": "2025-02-12 21:30:54.210403", 57 | "modified_by": "Administrator", 58 | "module": "Mansico Meta Integration", 59 | "name": "Page ID", 60 | "naming_rule": "By fieldname", 61 | "owner": "Administrator", 62 | "permissions": [ 63 | { 64 | "create": 1, 65 | "delete": 1, 66 | "email": 1, 67 | "export": 1, 68 | "print": 1, 69 | "read": 1, 70 | "report": 1, 71 | "role": "System Manager", 72 | "share": 1, 73 | "write": 1 74 | } 75 | ], 76 | "quick_entry": 1, 77 | "show_title_field_in_link": 1, 78 | "sort_field": "modified", 79 | "sort_order": "DESC", 80 | "states": [], 81 | "title_field": "name1" 82 | } -------------------------------------------------------------------------------- /mansico_meta_integration/mansico_meta_integration/doctype/meta_facebook_settings/meta_facebook_settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "actions": [], 3 | "allow_rename": 1, 4 | "creation": "2024-01-12 04:03:00.629477", 5 | "default_view": "List", 6 | "doctype": "DocType", 7 | "engine": "InnoDB", 8 | "field_order": [ 9 | "section_break_0lkx", 10 | "access_token", 11 | "graph_api_version", 12 | "api_url", 13 | "audience_name", 14 | "column_break_nezf", 15 | "app_secret", 16 | "app_id", 17 | "ad_account_id", 18 | "audience_retention_days" 19 | ], 20 | "fields": [ 21 | { 22 | "fieldname": "section_break_0lkx", 23 | "fieldtype": "Section Break" 24 | }, 25 | { 26 | "fieldname": "app_secret", 27 | "fieldtype": "Data", 28 | "label": "App Secret" 29 | }, 30 | { 31 | "fieldname": "audience_name", 32 | "fieldtype": "Data", 33 | "label": "Audience Name" 34 | }, 35 | { 36 | "fieldname": "graph_api_version", 37 | "fieldtype": "Data", 38 | "in_list_view": 1, 39 | "label": "Graph API Version", 40 | "reqd": 1 41 | }, 42 | { 43 | "fieldname": "column_break_nezf", 44 | "fieldtype": "Column Break" 45 | }, 46 | { 47 | "fieldname": "access_token", 48 | "fieldtype": "Data", 49 | "in_list_view": 1, 50 | "label": "Access Tocken", 51 | "reqd": 1 52 | }, 53 | { 54 | "fieldname": "ad_account_id", 55 | "fieldtype": "Data", 56 | "label": "AD Account ID" 57 | }, 58 | { 59 | "fieldname": "audience_retention_days", 60 | "fieldtype": "Data", 61 | "label": "Audience Retention Days" 62 | }, 63 | { 64 | "fieldname": "app_id", 65 | "fieldtype": "Data", 66 | "label": "App ID" 67 | }, 68 | { 69 | "fieldname": "api_url", 70 | "fieldtype": "Data", 71 | "label": "API URL", 72 | "reqd": 1 73 | } 74 | ], 75 | "index_web_pages_for_search": 1, 76 | "issingle": 1, 77 | "links": [], 78 | "modified": "2025-02-12 21:58:54.139663", 79 | "modified_by": "Administrator", 80 | "module": "Mansico Meta Integration", 81 | "name": "Meta Facebook Settings", 82 | "owner": "Administrator", 83 | "permissions": [ 84 | { 85 | "create": 1, 86 | "delete": 1, 87 | "email": 1, 88 | "print": 1, 89 | "read": 1, 90 | "role": "System Manager", 91 | "share": 1, 92 | "write": 1 93 | } 94 | ], 95 | "sort_field": "modified", 96 | "sort_order": "DESC", 97 | "states": [] 98 | } -------------------------------------------------------------------------------- /mansico_meta_integration/mansico_meta_integration/custom/note.json: -------------------------------------------------------------------------------- 1 | { 2 | "custom_fields": [ 3 | { 4 | "_assign": null, 5 | "_comments": null, 6 | "_liked_by": null, 7 | "_user_tags": null, 8 | "allow_in_quick_entry": 0, 9 | "allow_on_submit": 0, 10 | "bold": 0, 11 | "collapsible": 0, 12 | "collapsible_depends_on": null, 13 | "columns": 0, 14 | "creation": "2024-01-12 04:18:02.270770", 15 | "default": null, 16 | "depends_on": null, 17 | "description": null, 18 | "docstatus": 0, 19 | "dt": "Note", 20 | "fetch_from": null, 21 | "fetch_if_empty": 0, 22 | "fieldname": "custom_reference_name", 23 | "fieldtype": "Link", 24 | "hidden": 0, 25 | "hide_border": 0, 26 | "hide_days": 0, 27 | "hide_seconds": 0, 28 | "idx": 6, 29 | "ignore_user_permissions": 0, 30 | "ignore_xss_filter": 0, 31 | "in_global_search": 0, 32 | "in_list_view": 0, 33 | "in_preview": 0, 34 | "in_standard_filter": 0, 35 | "insert_after": "content", 36 | "is_system_generated": 0, 37 | "is_virtual": 0, 38 | "label": "Reference Name", 39 | "length": 0, 40 | "mandatory_depends_on": null, 41 | "modified": "2024-01-12 04:18:02.270770", 42 | "modified_by": "Administrator", 43 | "module": null, 44 | "name": "Note-custom_reference_name", 45 | "no_copy": 0, 46 | "non_negative": 0, 47 | "options": "Lead", 48 | "owner": "Administrator", 49 | "permlevel": 0, 50 | "precision": "", 51 | "print_hide": 0, 52 | "print_hide_if_no_value": 0, 53 | "print_width": null, 54 | "read_only": 1, 55 | "read_only_depends_on": null, 56 | "report_hide": 0, 57 | "reqd": 0, 58 | "search_index": 0, 59 | "sort_options": 0, 60 | "translatable": 0, 61 | "unique": 0, 62 | "width": null 63 | } 64 | ], 65 | "custom_perms": [], 66 | "doctype": "Note", 67 | "links": [], 68 | "property_setters": [ 69 | { 70 | "_assign": null, 71 | "_comments": null, 72 | "_liked_by": null, 73 | "_user_tags": null, 74 | "creation": "2024-01-12 04:18:02.169047", 75 | "default_value": null, 76 | "doc_type": "Note", 77 | "docstatus": 0, 78 | "doctype_or_field": "DocType", 79 | "field_name": null, 80 | "idx": 0, 81 | "is_system_generated": 0, 82 | "modified": "2024-01-12 04:18:02.169047", 83 | "modified_by": "Administrator", 84 | "module": null, 85 | "name": "Note-main-field_order", 86 | "owner": "Administrator", 87 | "property": "field_order", 88 | "property_type": "Data", 89 | "row_name": null, 90 | "value": "[\"title\", \"public\", \"notify_on_login\", \"notify_on_every_login\", \"expire_notification_on\", \"content\", \"reference_name\", \"seen_by_section\", \"seen_by\"]" 91 | } 92 | ], 93 | "sync_on_migrate": 1 94 | } -------------------------------------------------------------------------------- /mansico_meta_integration/mansico_meta_integration/doctype/sync_new_add/sync_new_add.json: -------------------------------------------------------------------------------- 1 | { 2 | "actions": [], 3 | "allow_rename": 1, 4 | "autoname": "naming_series:", 5 | "creation": "2024-01-12 04:09:09.565387", 6 | "default_view": "List", 7 | "doctype": "DocType", 8 | "engine": "InnoDB", 9 | "field_order": [ 10 | "section_break_qhoi", 11 | "column_break_rlez", 12 | "column_break_jjze", 13 | "naming_series", 14 | "section_break_msmq", 15 | "based_on", 16 | "page_id", 17 | "column_break_blgv", 18 | "lead_doctype_name", 19 | "event_frequency", 20 | "section_break_snbb", 21 | "fetch_map_lead_fields", 22 | "map_lead_fields", 23 | "meta_forms_section", 24 | "force_fetch", 25 | "table_hsya", 26 | "amended_from" 27 | ], 28 | "fields": [ 29 | { 30 | "fieldname": "section_break_qhoi", 31 | "fieldtype": "Section Break", 32 | "read_only": 1 33 | }, 34 | { 35 | "fieldname": "amended_from", 36 | "fieldtype": "Link", 37 | "label": "Amended From", 38 | "no_copy": 1, 39 | "options": "Sync New Add", 40 | "print_hide": 1, 41 | "read_only": 1, 42 | "search_index": 1 43 | }, 44 | { 45 | "fieldname": "column_break_jjze", 46 | "fieldtype": "Column Break" 47 | }, 48 | { 49 | "fieldname": "naming_series", 50 | "fieldtype": "Select", 51 | "hidden": 1, 52 | "label": "naming_series", 53 | "options": "AD-SYNC-" 54 | }, 55 | { 56 | "fieldname": "based_on", 57 | "fieldtype": "Select", 58 | "in_list_view": 1, 59 | "label": "Based On", 60 | "options": "\nPage ID", 61 | "reqd": 1 62 | }, 63 | { 64 | "fieldname": "section_break_msmq", 65 | "fieldtype": "Section Break" 66 | }, 67 | { 68 | "depends_on": "eval:doc.based_on==\"Page ID\"", 69 | "fieldname": "page_id", 70 | "fieldtype": "Link", 71 | "in_list_view": 1, 72 | "in_standard_filter": 1, 73 | "label": "Page ID", 74 | "mandatory_depends_on": "eval:doc.based_on==\"Page ID\"", 75 | "options": "Page ID" 76 | }, 77 | { 78 | "fieldname": "column_break_blgv", 79 | "fieldtype": "Column Break" 80 | }, 81 | { 82 | "fieldname": "event_frequency", 83 | "fieldtype": "Select", 84 | "label": "Event Frequency", 85 | "options": "All\nHourly\nDaily\nWeekly\nMonthly" 86 | }, 87 | { 88 | "fieldname": "meta_forms_section", 89 | "fieldtype": "Section Break", 90 | "label": "Meta Forms" 91 | }, 92 | { 93 | "default": "1", 94 | "fieldname": "force_fetch", 95 | "fieldtype": "Check", 96 | "label": "Force Fetch" 97 | }, 98 | { 99 | "description": "Uncheck Force Fetch to Remove form rows if you don't want to sync them.", 100 | "fieldname": "table_hsya", 101 | "fieldtype": "Table", 102 | "options": "Meta Forms" 103 | }, 104 | { 105 | "fieldname": "amended_from", 106 | "fieldtype": "Link", 107 | "label": "Amended From", 108 | "no_copy": 1, 109 | "options": "Sync New Add", 110 | "print_hide": 1, 111 | "read_only": 1, 112 | "search_index": 1 113 | }, 114 | { 115 | "fieldname": "column_break_rlez", 116 | "fieldtype": "Column Break" 117 | }, 118 | { 119 | "fieldname": "section_break_snbb", 120 | "fieldtype": "Section Break" 121 | }, 122 | { 123 | "description": "Lead Field Name\nYou Must set field name same as field name in Lead", 124 | "fieldname": "map_lead_fields", 125 | "fieldtype": "Table", 126 | "label": "Map Lead Fields", 127 | "options": "Map Lead Field" 128 | }, 129 | { 130 | "default": "0", 131 | "fieldname": "fetch_map_lead_fields", 132 | "fieldtype": "Check", 133 | "label": "Fetch Map Lead Fields" 134 | }, 135 | { 136 | "default": "Lead", 137 | "fieldname": "lead_doctype_name", 138 | "fieldtype": "Select", 139 | "in_list_view": 1, 140 | "in_standard_filter": 1, 141 | "label": "Lead Doctype Name", 142 | "options": "Lead\nCRM Lead" 143 | } 144 | ], 145 | "index_web_pages_for_search": 1, 146 | "is_submittable": 1, 147 | "links": [], 148 | "modified": "2025-03-11 22:34:48.117485", 149 | "modified_by": "Administrator", 150 | "module": "Mansico Meta Integration", 151 | "name": "Sync New Add", 152 | "naming_rule": "By \"Naming Series\" field", 153 | "owner": "Administrator", 154 | "permissions": [ 155 | { 156 | "create": 1, 157 | "delete": 1, 158 | "email": 1, 159 | "export": 1, 160 | "print": 1, 161 | "read": 1, 162 | "report": 1, 163 | "role": "System Manager", 164 | "share": 1, 165 | "write": 1 166 | } 167 | ], 168 | "sort_field": "modified", 169 | "sort_order": "DESC", 170 | "states": [] 171 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Mansico Meta Integration 2 | 3 |
4 | 5 |

Seamlessly Sync Facebook Leads with ERPNext

6 |
7 | 8 | **Mansico Meta Integration** is an open-source application designed to automate the synchronization of Facebook leads with ERPNext. When clients fill out Facebook Ads instant forms, the app automatically fetches the newly created leads and generates corresponding entries in ERPNext's **Lead** doctype. Additionally, when the Lead Status is updated in ERPNext, the new status is sent back to the Meta Pixel for real-time tracking and analytics. 9 | 10 | --- 11 | 12 | ## Key Features 13 | 14 | - **Automated Lead Sync**: Fetches new leads from Facebook Ads instant forms and creates them in ERPNext automatically. 15 | - **Real-Time Status Updates**: Sends updated lead statuses from ERPNext back to the Meta Pixel for enhanced tracking. 16 | - **Customizable Sync Frequency**: Configure how often the app fetches new leads (e.g., every 15 minutes, hourly, etc.). 17 | - **Seamless Integration**: Works with Meta Business Accounts, Marketing API, and ERPNext for a smooth setup process. 18 | - **Open-Source and Extendable**: Fully customizable to meet specific business needs. 19 | 20 | --- 21 | 22 | ## Installation 23 | 24 | ### Frappe Cloud (One-Click Install) 25 | Great new now available for FREE on [Frappe Cloud Marketplace](https://frappecloud.com/marketplace/apps/mansico_meta_integration). Stay tuned for updates! 26 | 27 | ### Self-Hosting 28 | 29 | 1. **Clone the App Repository**: 30 | ```bash 31 | bench get-app https://github.com/splinter-NGoH/mansico_meta_integration.git 32 | ``` 33 | 34 | 2. **Install the App**: 35 | ```bash 36 | bench --site [your.site.name] install-app mansico_meta_integration 37 | ``` 38 | 39 | 3. **Run Database Migrations**: 40 | ```bash 41 | bench --site [your.site.name] migrate 42 | ``` 43 | 44 | --- 45 | 46 | ## Facebook Requirements 47 | 48 | To use this integration, you need the following: 49 | 50 | 1. **Meta Business Account**: 51 | - If you don’t already have one, create a [Meta Business Account](https://www.facebook.com/business/help/1710077379203657?id=180505742745347). 52 | 53 | 2. **Meta App**: 54 | - Create a Meta App to access the Marketing API. Follow the steps below: 55 | ![Meta App Creation](https://github.com/splinter-NGoH/mansico_meta_integration/assets/73743592/70138d92-07c2-4e05-8a6b-a408854a3900) 56 | 57 | 3. **Marketing API Setup**: 58 | - Enable the Marketing API for your app. 59 | ![Marketing API Setup](https://github.com/splinter-NGoH/mansico_meta_integration/assets/73743592/7b81826e-1ffe-46e7-9954-b7b38d522f8e) 60 | 61 | 4. **Access Token for System User**: 62 | - Go to **Meta Settings > Users > System Users** and add a new user or use an existing one. 63 | - Assign the necessary permissions (`leads_retrieval`, `manage_pages`, `ads_management`, `business_management`). 64 | - Generate an access token and copy it. 65 | ![Access Token Generation](https://github.com/splinter-NGoH/mansico_meta_integration/assets/73743592/273d3c1b-766e-4bab-9b2e-fca3213b916b) 66 | 67 | 5. **Pixel ID and Pixel Access Token**: 68 | - Go to **Events Manager** in your Meta Business Account. 69 | - Locate your Pixel ID and generate a Pixel Access Token. 70 | ![Pixel Setup](https://github.com/splinter-NGoH/mansico_meta_integration/assets/73743592/8dd287ee-4903-4285-9ba4-2808a7827aa9) 71 | 72 | --- 73 | 74 | ## Configuration in ERPNext 75 | 76 | 1. **Meta Facebook Settings**: 77 | - Paste the **Access Token** in the respective field. 78 | - Set the **API URL** (e.g., `https://graph.facebook.com`) and **Graph API Version** (e.g., `v16.0`). 79 | ![Meta Settings](https://github.com/splinter-NGoH/mansico_meta_integration/assets/73743592/d802a857-7807-4cd6-ae4e-cef10466816a) 80 | 81 | 2. **Page ID Doctype**: 82 | - Add your Facebook Page Name, Page ID, Pixel ID, and Pixel Access Token. 83 | ![Page ID Setup](https://github.com/splinter-NGoH/mansico_meta_integration/assets/73743592/164bbb69-9539-4579-a7bf-568d91c74bcc) 84 | 85 | 3. **Sync New Lead**: 86 | - Create a new sync job and configure the **Event Frequency** (e.g., every 15 minutes). 87 | - Ensure the **Lead Status Mapping** is correctly set up. 88 | ![Sync Setup](https://github.com/splinter-NGoH/mansico_meta_integration/assets/73743592/4cbc636a-181b-483d-9e25-7bc15cf9c5dd) 89 | 90 | 4. **Schedule Job**: 91 | - The sync job will be queued. You can manually execute it from **Schedule Job Type**. 92 | ![Schedule Job](https://github.com/splinter-NGoH/mansico_meta_integration/assets/73743592/89f8893d-165e-4700-a939-76e8b5e9fa04) 93 | 94 | 5. **Verify Leads**: 95 | - Check the **Lead** doctype to confirm that new leads have been created. 96 | 97 | --- 98 | 99 | ## Support the Project 100 | 101 | If you find this project useful, consider supporting its development! Your contributions help maintain and improve the app. 102 | 103 | [![Donate via PayPal](https://img.shields.io/badge/Donate-PayPal-blue?logo=paypal)](https://paypal.me/AhmedMansyArt?country.x=EG&locale.x=en_US) 104 | 105 | --- 106 | 107 | ## Dependencies 108 | 109 | - [Frappe Framework](https://github.com/frappe/frappe) 110 | - [ERPNext](https://github.com/frappe/erpnext) 111 | 112 | --- 113 | 114 | ## License 115 | 116 | This project is licensed under the **MIT License**. See the [LICENSE](https://github.com/splinter-NGoH/mansico_meta_integration/blob/main/LICENSE) file for details. 117 | 118 | --- 119 | 120 | ## Contributing 121 | 122 | We welcome contributions from the community! To contribute: 123 | 124 | 1. Fork the repository. 125 | 2. Create a new branch for your feature or bug fix. 126 | 3. Submit a pull request with a detailed description of your changes. 127 | 128 | --- 129 | 130 | ## Support 131 | 132 | For support or questions, open an issue on the [GitHub repository](https://github.com/splinter-NGoH/mansico_meta_integration) or contact the maintainers. 133 | 134 | --- 135 | 136 | ## Important Note 137 | 138 | This app is actively maintained, and new updates will be released regularly. Don’t forget to ⭐️ the repository to show your support! Pull requests from developers are highly encouraged. 139 | 140 | Cheers from Mansy! 🚀 141 | -------------------------------------------------------------------------------- /mansico_meta_integration/mansico_meta_integration/custom/lead.json: -------------------------------------------------------------------------------- 1 | { 2 | "custom_fields": [ 3 | { 4 | "_assign": null, 5 | "_comments": null, 6 | "_liked_by": null, 7 | "_user_tags": null, 8 | "allow_in_quick_entry": 0, 9 | "allow_on_submit": 0, 10 | "bold": 0, 11 | "collapsible": 0, 12 | "collapsible_depends_on": null, 13 | "columns": 0, 14 | "creation": "2025-02-09 19:41:36.275710", 15 | "default": null, 16 | "depends_on": null, 17 | "description": null, 18 | "docstatus": 0, 19 | "dt": "Lead", 20 | "fetch_from": null, 21 | "fetch_if_empty": 0, 22 | "fieldname": "custom_meta_lead_id", 23 | "fieldtype": "Data", 24 | "hidden": 0, 25 | "hide_border": 0, 26 | "hide_days": 0, 27 | "hide_seconds": 0, 28 | "idx": 61, 29 | "ignore_user_permissions": 0, 30 | "ignore_xss_filter": 0, 31 | "in_global_search": 0, 32 | "in_list_view": 0, 33 | "in_preview": 0, 34 | "in_standard_filter": 0, 35 | "insert_after": "custom_section_break_dmvos", 36 | "is_system_generated": 0, 37 | "is_virtual": 0, 38 | "label": "Meta Lead ID", 39 | "length": 0, 40 | "mandatory_depends_on": null, 41 | "modified": "2025-02-09 19:41:36.275710", 42 | "modified_by": "Administrator", 43 | "module": null, 44 | "name": "Lead-custom_meta_lead_id", 45 | "no_copy": 0, 46 | "non_negative": 0, 47 | "options": null, 48 | "owner": "Administrator", 49 | "permlevel": 0, 50 | "precision": "", 51 | "print_hide": 0, 52 | "print_hide_if_no_value": 0, 53 | "print_width": null, 54 | "read_only": 1, 55 | "read_only_depends_on": null, 56 | "report_hide": 0, 57 | "reqd": 0, 58 | "search_index": 0, 59 | "sort_options": 0, 60 | "translatable": 0, 61 | "unique": 0, 62 | "width": null 63 | }, 64 | { 65 | "_assign": null, 66 | "_comments": null, 67 | "_liked_by": null, 68 | "_user_tags": null, 69 | "allow_in_quick_entry": 0, 70 | "allow_on_submit": 0, 71 | "bold": 0, 72 | "collapsible": 0, 73 | "collapsible_depends_on": null, 74 | "columns": 0, 75 | "creation": "2024-01-12 04:16:35.419449", 76 | "default": null, 77 | "depends_on": null, 78 | "description": null, 79 | "docstatus": 0, 80 | "dt": "Lead", 81 | "fetch_from": null, 82 | "fetch_if_empty": 0, 83 | "fieldname": "custom_lead_json", 84 | "fieldtype": "JSON", 85 | "hidden": 0, 86 | "hide_border": 0, 87 | "hide_days": 0, 88 | "hide_seconds": 0, 89 | "idx": 62, 90 | "ignore_user_permissions": 0, 91 | "ignore_xss_filter": 0, 92 | "in_global_search": 0, 93 | "in_list_view": 0, 94 | "in_preview": 0, 95 | "in_standard_filter": 0, 96 | "insert_after": "custom_meta_lead_id", 97 | "is_system_generated": 0, 98 | "is_virtual": 0, 99 | "label": "Lead Json", 100 | "length": 0, 101 | "mandatory_depends_on": null, 102 | "modified": "2024-01-12 04:16:35.419449", 103 | "modified_by": "Administrator", 104 | "module": null, 105 | "name": "Lead-custom_lead_json", 106 | "no_copy": 0, 107 | "non_negative": 0, 108 | "options": null, 109 | "owner": "Administrator", 110 | "permlevel": 0, 111 | "precision": "", 112 | "print_hide": 0, 113 | "print_hide_if_no_value": 0, 114 | "print_width": null, 115 | "read_only": 1, 116 | "read_only_depends_on": null, 117 | "report_hide": 0, 118 | "reqd": 0, 119 | "search_index": 0, 120 | "sort_options": 0, 121 | "translatable": 0, 122 | "unique": 0, 123 | "width": null 124 | }, 125 | { 126 | "_assign": null, 127 | "_comments": null, 128 | "_liked_by": null, 129 | "_user_tags": null, 130 | "allow_in_quick_entry": 0, 131 | "allow_on_submit": 0, 132 | "bold": 0, 133 | "collapsible": 0, 134 | "collapsible_depends_on": null, 135 | "columns": 0, 136 | "creation": "2024-01-12 04:16:35.195670", 137 | "default": null, 138 | "depends_on": null, 139 | "description": null, 140 | "docstatus": 0, 141 | "dt": "Lead", 142 | "fetch_from": null, 143 | "fetch_if_empty": 0, 144 | "fieldname": "custom_section_break_dmvos", 145 | "fieldtype": "Section Break", 146 | "hidden": 0, 147 | "hide_border": 0, 148 | "hide_days": 0, 149 | "hide_seconds": 0, 150 | "idx": 60, 151 | "ignore_user_permissions": 0, 152 | "ignore_xss_filter": 0, 153 | "in_global_search": 0, 154 | "in_list_view": 0, 155 | "in_preview": 0, 156 | "in_standard_filter": 0, 157 | "insert_after": "blog_subscriber", 158 | "is_system_generated": 0, 159 | "is_virtual": 0, 160 | "label": "", 161 | "length": 0, 162 | "mandatory_depends_on": null, 163 | "modified": "2024-01-12 04:16:35.195670", 164 | "modified_by": "Administrator", 165 | "module": null, 166 | "name": "Lead-custom_section_break_dmvos", 167 | "no_copy": 0, 168 | "non_negative": 0, 169 | "options": null, 170 | "owner": "Administrator", 171 | "permlevel": 0, 172 | "precision": "", 173 | "print_hide": 0, 174 | "print_hide_if_no_value": 0, 175 | "print_width": null, 176 | "read_only": 0, 177 | "read_only_depends_on": null, 178 | "report_hide": 0, 179 | "reqd": 0, 180 | "search_index": 0, 181 | "sort_options": 0, 182 | "translatable": 0, 183 | "unique": 0, 184 | "width": null 185 | } 186 | ], 187 | "custom_perms": [], 188 | "doctype": "Lead", 189 | "links": [], 190 | "property_setters": [ 191 | { 192 | "_assign": null, 193 | "_comments": null, 194 | "_liked_by": null, 195 | "_user_tags": null, 196 | "creation": "2025-02-09 19:53:51.208741", 197 | "default_value": null, 198 | "doc_type": "Lead", 199 | "docstatus": 0, 200 | "doctype_or_field": "DocType", 201 | "field_name": null, 202 | "idx": 0, 203 | "is_system_generated": 0, 204 | "modified": "2025-02-09 19:53:51.208741", 205 | "modified_by": "Administrator", 206 | "module": null, 207 | "name": "Lead-main-field_order", 208 | "owner": "Administrator", 209 | "property": "field_order", 210 | "property_type": "Data", 211 | "row_name": null, 212 | "value": "[\"naming_series\", \"salutation\", \"first_name\", \"middle_name\", \"last_name\", \"column_break_1\", \"lead_name\", \"job_title\", \"gender\", \"source\", \"col_break123\", \"lead_owner\", \"status\", \"customer\", \"type\", \"request_type\", \"contact_info_tab\", \"email_id\", \"website\", \"column_break_20\", \"mobile_no\", \"whatsapp_no\", \"column_break_16\", \"phone\", \"phone_ext\", \"organization_section\", \"company_name\", \"no_of_employees\", \"column_break_28\", \"annual_revenue\", \"industry\", \"market_segment\", \"column_break_31\", \"territory\", \"fax\", \"address_section\", \"address_html\", \"column_break_38\", \"city\", \"state\", \"country\", \"column_break2\", \"contact_html\", \"qualification_tab\", \"qualification_status\", \"column_break_64\", \"qualified_by\", \"qualified_on\", \"other_info_tab\", \"campaign_name\", \"company\", \"column_break_22\", \"language\", \"image\", \"title\", \"column_break_50\", \"disabled\", \"unsubscribed\", \"blog_subscriber\", \"custom_section_break_dmvos\", \"custom_meta_lead_id\", \"custom_lead_json\", \"activities_tab\", \"open_activities_html\", \"all_activities_section\", \"all_activities_html\", \"notes_tab\", \"notes_html\", \"notes\", \"dashboard_tab\"]" 213 | } 214 | ], 215 | "sync_on_migrate": 1 216 | } -------------------------------------------------------------------------------- /mansico_meta_integration/mansico_meta_integration/doctype/sync_new_add/sync_new_add.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2023, mansy and contributors 2 | # For license information, please see license.txt 3 | 4 | import frappe 5 | from frappe.model.document import Document 6 | 7 | 8 | @frappe.whitelist() 9 | def get_credentials(): 10 | return frappe.get_doc("Meta Facebook Settings") 11 | 12 | 13 | import requests 14 | import json 15 | 16 | 17 | class Request: 18 | def __init__(self, url, version, page_id, f_payload=None, params=None): 19 | self.url = url 20 | self.version = "v" + str(version) 21 | self.page_id = page_id 22 | self.f_payload = f_payload 23 | self.params = params 24 | 25 | @property 26 | def get_url(self): 27 | return self.url + "/" + self.version + "/" + self.page_id 28 | 29 | 30 | class RequestPageAccessToken: 31 | def __init__(self, request): 32 | self.request = request 33 | 34 | def get_page_access_token(self): 35 | response = requests.get( 36 | self.request.get_url, params=self.request.params, json=self.request.params 37 | ) 38 | 39 | if frappe._dict(response.json()).get("error"): 40 | _error_message = "" 41 | _error_message += "url" + " : " + str(self.request.get_url) + "
" 42 | _error_message += "params" + " : " + str(self.request.params) + "
" 43 | _error_message += "
" 44 | for key in frappe._dict(response.json()).get("error").keys(): 45 | _error_message += ( 46 | key 47 | + " : " 48 | + str(frappe._dict(response.json()).get("error").get(key)) 49 | + "
" 50 | ) 51 | frappe.throw(_error_message, title="Error") 52 | else: 53 | self.page_access_token = frappe._dict(response.json()).get("access_token") 54 | return self.page_access_token 55 | 56 | 57 | class RequestLeadGenFroms: 58 | def __init__(self, request): 59 | self.request = request 60 | 61 | def get_lead_forms(self): 62 | response = requests.get( 63 | self.request.get_url, params=self.request.params, json=self.request.params 64 | ) 65 | if frappe._dict(response.json()).get("error"): 66 | _error_message = "" 67 | _error_message += "url" + " : " + str(self.request.get_url) + "
" 68 | _error_message += "params" + " : " + str(self.request.params) + "
" 69 | _error_message += "
" 70 | for key in frappe._dict(response.json()).get("error").keys(): 71 | _error_message += ( 72 | key 73 | + " : " 74 | + str(frappe._dict(response.json()).get("error").get(key)) 75 | + "
" 76 | ) 77 | frappe.throw(_error_message, title="Error") 78 | else: 79 | self.lead_forms = frappe._dict(response.json()) 80 | return self.lead_forms 81 | 82 | 83 | class AppendForms: 84 | def __init__(self, lead_forms, doc): 85 | self.lead_forms = lead_forms 86 | self.doc = doc 87 | 88 | def append_forms(self): 89 | if self.doc.force_fetch: 90 | self.doc.set("table_hsya", []) 91 | 92 | for lead_form in self.lead_forms.get("data"): 93 | self.doc.append( 94 | "table_hsya", 95 | { 96 | "form_id": lead_form.get("id"), 97 | "form_name": lead_form.get("name"), 98 | "created_time": lead_form.get("created_time"), 99 | "leads_count": lead_form.get("leads_count"), 100 | "page": lead_form.get("page"), 101 | "questions": frappe._dict( 102 | {"questions": lead_form.get("questions")} 103 | ), 104 | }, 105 | ) 106 | if self.doc.fetch_map_lead_fields: 107 | self.doc.set("map_lead_fields", []) 108 | form_fields = [] # Initialize an empty list to track form fields 109 | for lead in self.doc.table_hsya: 110 | self.set_map_lead_fields( 111 | json.loads(lead.questions).get("questions") 112 | if isinstance(lead.questions, str) 113 | else lead.questions.get("questions"), 114 | form_fields, 115 | ) 116 | 117 | def set_map_lead_fields(self, questions, form_fields): 118 | for question in questions: 119 | if ( 120 | question.get("key") not in form_fields 121 | ): # Check if the key is not already in the list 122 | if question.get("type") == "EMAIL": 123 | self.doc.append( 124 | "map_lead_fields", 125 | { 126 | "lead_field": "email_id", 127 | "form_field": question.get("key"), 128 | "form_field_label": question.get("label"), 129 | "form_field_type": question.get("type"), 130 | }, 131 | ) 132 | elif question.get("type") == "FULL_NAME": 133 | self.doc.append( 134 | "map_lead_fields", 135 | { 136 | "lead_field": "first_name", 137 | "form_field": question.get("key"), 138 | "form_field_label": question.get("label"), 139 | "form_field_type": question.get("type"), 140 | }, 141 | ) 142 | elif question.get("type") == "PHONE": 143 | self.doc.append( 144 | "map_lead_fields", 145 | { 146 | "lead_field": "phone_number", 147 | "form_field": question.get("key"), 148 | "form_field_label": question.get("label"), 149 | "form_field_type": question.get("type"), 150 | }, 151 | ) 152 | elif question.get("type") == "CUSTOM": 153 | self.doc.append( 154 | "map_lead_fields", 155 | { 156 | "lead_field": question.get("key"), 157 | "form_field": question.get("key"), 158 | "form_field_label": question.get("label"), 159 | "form_field_type": question.get("type"), 160 | }, 161 | ) 162 | else: 163 | self.doc.append( 164 | "map_lead_fields", 165 | { 166 | "lead_field": question.get("key"), 167 | "form_field": question.get("key"), 168 | "form_field_label": question.get("label"), 169 | "form_field_type": question.get("type"), 170 | }, 171 | ) 172 | # Add the key to form_fields to avoid duplicating it 173 | form_fields.append(question.get("key")) 174 | 175 | 176 | class ServerScript: 177 | def __init__(self, doc): 178 | self.doc = doc 179 | 180 | def create_server_script(self): 181 | self.server_script = frappe.get_doc( 182 | { 183 | "doctype": "Server Script", 184 | "name": str(str(self.doc.name).replace("-", "_")).lower(), 185 | "script_type": "Scheduler Event", 186 | "event_frequency": self.doc.event_frequency, 187 | "module": "Mansico Meta Integration", 188 | "script": self.generate_script(), 189 | } 190 | ) 191 | 192 | def generate_script(self): 193 | _script = "" 194 | 195 | _script += """from mansico_meta_integration.mansico_meta_integration.doctype.sync_new_add.sync_new_add import FetchLeads\n""" 196 | _script += """import frappe\n""" 197 | _script += """fetch = FetchLeads("{0}")\n""".format( 198 | str(str(self.doc.name).replace("-", "_")).lower() 199 | ) 200 | _script += """fetch.fetch_leads()\n""" 201 | return _script 202 | 203 | 204 | class RequestSendLead: 205 | def __init__(self, request): 206 | self.request = request 207 | 208 | def send_lead(self): 209 | response = requests.post( 210 | self.request.get_url, 211 | params=self.request.params, 212 | json=self.request.f_payload, 213 | ) 214 | if frappe._dict(response.json()).get("error"): 215 | error_message = "" 216 | error_message += "url" + " : " + str(self.request.get_url) + "
" 217 | error_message += "params" + " : " + str(self.request.params) + "
" 218 | error_message += "
" 219 | for key in json.dumps(response.json()).get("error").keys(): 220 | error_message += ( 221 | key 222 | + " : " 223 | + str(json.dumps(response.json()).get("error").get(key)) 224 | + "
" 225 | ) 226 | frappe.throw(error_message, title="Error") 227 | else: 228 | return json.dumps(response.json()) 229 | 230 | 231 | class FetchLeads: 232 | def __init__(self, name): 233 | self.name = name 234 | 235 | @property 236 | def get_form_ids(self): 237 | form_ids = [] 238 | for form in self.doc.table_hsya: 239 | form_ids.append(form.form_id) 240 | return form_ids 241 | 242 | @frappe.whitelist() 243 | def fetch_leads(self): 244 | self.doc = frappe.get_doc("Sync New Add", self.name) 245 | self.page = frappe.get_doc("Page ID", self.doc.page_id) 246 | self.form_ids = self.get_form_ids 247 | for form_id in self.form_ids: 248 | defaults = get_credentials() 249 | # init Request 250 | request = Request( 251 | defaults.api_url, 252 | defaults.graph_api_version, 253 | self.doc.page_id, 254 | None, 255 | params={ 256 | "fields": "access_token", 257 | "transport": "cors", 258 | "access_token": defaults.access_token, 259 | }, 260 | ) 261 | # init RequestPageAccessToken 262 | request_page_access_token = RequestPageAccessToken(request) 263 | # get page access token 264 | request_page_access_token.get_page_access_token() 265 | # init Request 266 | request = Request( 267 | defaults.api_url, 268 | defaults.graph_api_version, 269 | form_id + "/leads", 270 | None, 271 | params={ 272 | "access_token": request_page_access_token.page_access_token, 273 | "fields": "ad_id,ad_name,adset_id,adset_name,\ 274 | campaign_id,campaign_name,created_time,custom_disclaimer_responses,\ 275 | field_data,form_id,id,home_listing,is_organic,partner_name,\ 276 | platform,post,retailer_item_id,vehicle", 277 | }, 278 | ) 279 | # init RequestLeadGenFroms 280 | request_lead_gen_forms = RequestLeadGenFroms(request) 281 | # get lead forms 282 | request_lead_gen_forms.get_lead_forms() 283 | 284 | if request_lead_gen_forms.lead_forms.get("data"): 285 | # use self.lead_forms 286 | # fetch all leads then create them using create_lead 287 | # filter leads by created_time and id to avoid duplication 288 | self.paginate_lead_forms(request_lead_gen_forms.lead_forms) 289 | 290 | def paginate_lead_forms(self, lead_forms): 291 | if lead_forms.paging.get("next"): 292 | self.create_lead(lead_forms.get("data")) 293 | next_page = lead_forms.paging.get("next") 294 | response = requests.get(next_page) 295 | lead_forms = frappe._dict(response.json()) 296 | return self.paginate_lead_forms(lead_forms) 297 | else: 298 | if lead_forms: 299 | self.create_lead(lead_forms.get("data")) 300 | return lead_forms 301 | 302 | def create_lead(self, leads): 303 | import traceback 304 | 305 | for lead in leads: 306 | # Initialize an empty dictionary to store lead data dynamically 307 | lead_data = {} 308 | # Loop through the field_data and extract the values dynamically 309 | for field in lead.get("field_data", []): 310 | field_name = field.get("name") 311 | field_value = field.get("values", [None])[ 312 | 0 313 | ] # Get the first value or None if no value is present 314 | 315 | # Check if the field_name exists in the map_lead_fields of the current doc 316 | for mapping in self.doc.map_lead_fields: 317 | if mapping.get("form_field") == field_name: 318 | # Dynamically map field_data to the Lead fields based on map_lead_fields 319 | lead_data[mapping.get("lead_field")] = field_value 320 | 321 | if lead.get("id") and not frappe.db.exists( 322 | self.doc.lead_doctype_name, {"custom_meta_lead_id": lead.get("id")} 323 | ): 324 | try: 325 | # Create a new Lead document dynamically based on available fields 326 | new_lead_data = { 327 | "doctype": self.doc.lead_doctype_name, 328 | "custom_meta_lead_id": lead.get("id"), 329 | "custom_lead_json": frappe._dict(lead), 330 | } 331 | 332 | # Dynamically populate lead fields from lead_data 333 | for field_name, field_value in lead_data.items(): 334 | new_lead_data[field_name] = field_value 335 | 336 | new_lead = frappe.get_doc(new_lead_data) 337 | new_lead.insert(ignore_permissions=True) 338 | 339 | # Optionally, create the lead in Facebook 340 | FetchLeads.create_lead_in_facebook(new_lead, self.page) 341 | 342 | except Exception as e: 343 | # Log errors and traceback for better debugging 344 | frappe.log_error("Error in Lead Creation", str(e)) 345 | frappe.log_error("Traceback", str(traceback.format_exc())) 346 | frappe.log_error("Lead Data", str(lead_data)) 347 | 348 | @staticmethod 349 | def create_lead_in_facebook(lead, page): 350 | import datetime 351 | import json 352 | from mansico_meta_integration.mansico_meta_integration.doctype.sync_new_add.meta_integraion_objects import ( 353 | UserData, 354 | CustomData, 355 | Payload, 356 | ) 357 | 358 | now = datetime.datetime.now() 359 | unixtime = int(now.timestamp()) 360 | 361 | if lead.custom_meta_lead_id: 362 | # Create UserData and CustomData objects 363 | user_data = UserData(lead.custom_meta_lead_id) 364 | custom_data = CustomData("crm", "ERP Next") 365 | 366 | # Create Payload object 367 | payload = Payload( 368 | event_name=lead.status, 369 | event_time=unixtime, 370 | action_source="system_generated", 371 | user_data=user_data, 372 | custom_data=custom_data, 373 | ) 374 | 375 | # Convert Payload to dictionary 376 | f_payload = {"data": [payload.to_dict()]} 377 | 378 | # Send request to Facebook 379 | defaults = get_credentials() 380 | request = Request( 381 | defaults.api_url, 382 | defaults.graph_api_version, 383 | page.pixel_id + "/events", 384 | f_payload, 385 | params={"access_token": page.pixel_access_token}, 386 | ) 387 | 388 | # Send the lead 389 | request_send_lead = RequestSendLead(request) 390 | response = request_send_lead.send_lead() 391 | 392 | # Insert a note with the response and payload 393 | note = frappe.get_doc( 394 | { 395 | "doctype": "Note", 396 | "title": "Lead Created in Facebook Successfully", 397 | "public": 1, 398 | "content": ( 399 | "Lead Created in Facebook Successfully
Response: " 400 | + str(response) 401 | + "
Payload: " 402 | + json.dumps(f_payload, indent=2) 403 | ), 404 | "custom_reference_name": lead.name, 405 | } 406 | ) 407 | note.insert(ignore_permissions=True) 408 | 409 | 410 | class SyncNewAdd(Document): 411 | def validate(self): 412 | defaults = get_credentials() 413 | # init Request 414 | request = Request( 415 | defaults.api_url, 416 | defaults.graph_api_version, 417 | self.page_id, 418 | None, 419 | params={ 420 | "fields": "access_token", 421 | "transport": "cors", 422 | "access_token": defaults.access_token, 423 | }, 424 | ) 425 | # init RequestPageAccessToken 426 | request_page_access_token = RequestPageAccessToken(request) 427 | # get page access token 428 | request_page_access_token.get_page_access_token() 429 | # init Request 430 | request = Request( 431 | defaults.api_url, 432 | defaults.graph_api_version, 433 | self.page_id + f"/leadgen_forms", 434 | None, 435 | params={ 436 | "access_token": request_page_access_token.page_access_token, 437 | "fields": "name,id,created_time,leads_count,page,page_id,\ 438 | questions,leads {\ 439 | ad_id,campaign_id,adset_id,campaign_name,ad_name,form_id,id,\ 440 | adset_name,created_time\ 441 | }", 442 | }, 443 | ) 444 | # init RequestLeadGenFroms 445 | request_lead_gen_forms = RequestLeadGenFroms(request) 446 | # get lead forms 447 | request_lead_gen_forms.get_lead_forms() 448 | # init AppendForms 449 | append_forms = AppendForms(request_lead_gen_forms.lead_forms, self) 450 | # append forms 451 | append_forms.append_forms() 452 | 453 | def check_email_id(self): 454 | first_name = False 455 | for row in self.map_lead_fields: 456 | if row.lead_field == "first_name": 457 | first_name = True 458 | if not first_name: 459 | frappe.throw("Please map First Name Field") 460 | 461 | def check_meta_fields_found(self): 462 | if frappe.get_meta(self.lead_doctype_name).has_field("custom_meta_lead_id"): 463 | pass 464 | else: 465 | # create custom fields 466 | frappe.get_doc( 467 | { 468 | "doctype": "Custom Field", 469 | "dt": "Lead", 470 | "fieldname": "custom_meta_lead_id", 471 | "label": "Custom Meta Lead ID", 472 | "fieldtype": "Data", 473 | "insert_after": "name", 474 | "read_only": 1, 475 | } 476 | ).insert(ignore_permissions=True) 477 | if frappe.get_meta(self.lead_doctype_name).has_field("custom_lead_json"): 478 | pass 479 | else: 480 | # create custom fields 481 | frappe.get_doc( 482 | { 483 | "doctype": "Custom Field", 484 | "dt": "Lead", 485 | "fieldname": "custom_lead_json", 486 | "label": "Custom Lead JSON", 487 | "fieldtype": "Text", 488 | "insert_after": "custom_meta_lead_id", 489 | "read_only": 1, 490 | } 491 | ).insert(ignore_permissions=True) 492 | 493 | def on_submit(self): 494 | self.check_meta_fields_found() 495 | self.check_email_id() 496 | # i want to check if site hase enable_schedule = 1 497 | # create Server Script 498 | # server_script = ServerScript(self) 499 | # server_script.create_server_script() 500 | # server_script.server_script.insert(ignore_permissions=True) 501 | # frappe.db.commit() 502 | # frappe.msgprint("Server Script Created Successfully") 503 | 504 | def on_cancel(self): 505 | pass 506 | # delete Server Script 507 | # frappe.delete_doc("Server Script", str(self.name).lower().replace("-","_"), ignore_permissions=True) 508 | # frappe.msgprint("Server Script Deleted Successfully") 509 | --------------------------------------------------------------------------------