Files
upstream-mirror db69de1c11
Some checks failed
Frontend / Unit Tests & Coverage (push) Has been skipped
Regenerate POT file (translatable strings) / Regenerate POT file (develop) (push) Failing after 1m13s
Regenerate POT file (translatable strings) / Regenerate POT file (main-hotfix) (push) Failing after 6s
Server / Tests (frappe ${{ matrix.frappe-branch }}) (push) Has been skipped
Server / Resolve frappe matrix (push) Failing after 1s
Frontend / Lint (push) Failing after 1s
Frontend / Production Build (push) Failing after 1s
Server / Coverage Wrap Up (push) Has been skipped
Frontend / Coverage Wrap Up (push) Has been skipped
snapshot of https://github.com/frappe/crm.git (develop, 2026-09-18; blobs > 131072B omitted)
2026-09-18 01:36:01 +05:00

121 lines
4.0 KiB
Python

import frappe
from frappe import _
from frappe.model.rename_doc import rename_doc
CASCADE_FLAG = "crm_product_item_cascade_in_progress"
ALLOWED_DOCTYPES = ("CRM Product", "Item")
def should_sync() -> bool:
"""ERPNext Item to CRM Product sync runs when same-site integration is enabled."""
if "erpnext" not in frappe.get_installed_apps():
return False
settings = frappe.get_cached_doc("ERPNext CRM Settings")
return bool(settings.enabled) and not settings.is_erpnext_in_different_site
def should_push_to_erpnext() -> bool:
"""CRM Product to ERPNext Item sync is optional until CRM supports ERPNext tax details."""
if not should_sync():
return False
settings = frappe.get_cached_doc("ERPNext CRM Settings")
return bool(settings.sync_products)
def in_cascade() -> bool:
return bool(frappe.flags.get(CASCADE_FLAG))
def set_links(item_code: str, crm_product_name: str) -> None:
"""Permission-bypassing link bookkeeping. Mirrors HD's set_links."""
frappe.db.set_value(
"CRM Product", crm_product_name, "erpnext_item_code", item_code, update_modified=False
)
frappe.db.set_value("Item", item_code, "crm_product_code", crm_product_name, update_modified=False)
def _other_side(self_doctype: str) -> tuple[str, str]:
if self_doctype == "CRM Product":
return "Item", "crm_product_code"
return "CRM Product", "erpnext_item_code"
def find_target_for(doctype: str | None, value: str | None) -> tuple[str, str] | None:
if not doctype or not value:
return None
if doctype == "CRM Product":
item = frappe.db.get_value("Item", {"crm_product_code": value}, "name")
return ("Item", item) if item else None
if doctype == "Item":
prod = frappe.db.get_value("CRM Product", {"erpnext_item_code": value}, "name")
return ("CRM Product", prod) if prod else None
return None
def validate_rename_conflict(self_doctype, olddn, newdn, merge):
if in_cascade() or not _should_sync_direction(self_doctype):
return
other_doctype, other_link_field = _other_side(self_doctype)
linked = frappe.db.get_value(other_doctype, {other_link_field: olddn}, "name")
if not linked:
return
if merge:
surviving = frappe.db.get_value(other_doctype, {other_link_field: newdn}, "name")
if surviving:
return
if linked == newdn:
return
if frappe.db.exists(other_doctype, newdn):
existing_link = frappe.db.get_value(other_doctype, newdn, other_link_field)
if existing_link != olddn:
frappe.throw(
_(
"Cannot rename: an unrelated {0} '{1}' already exists on the "
"other side. Resolve manually first."
).format(other_doctype, newdn)
)
def cascade_rename(self_doctype, olddn, newdn, merge):
if in_cascade() or not _should_sync_direction(self_doctype):
return
other_doctype, other_link_field = _other_side(self_doctype)
linked = frappe.db.get_value(other_doctype, {other_link_field: olddn}, "name")
if not linked:
return
frappe.flags[CASCADE_FLAG] = True
try:
if merge:
surviving = frappe.db.get_value(other_doctype, {other_link_field: newdn}, "name")
if surviving and surviving != linked:
rename_doc(other_doctype, linked, surviving, merge=True, ignore_permissions=True)
target = surviving
else:
if linked != newdn:
rename_doc(other_doctype, linked, newdn, ignore_permissions=True)
target = newdn
else:
if linked != newdn:
rename_doc(other_doctype, linked, newdn, ignore_permissions=True)
target = newdn
_resync_links(self_doctype, newdn, target)
finally:
frappe.flags[CASCADE_FLAG] = False
def _resync_links(self_doctype, self_name, other_name):
if self_doctype == "CRM Product":
prod, item = self_name, other_name
else:
prod, item = other_name, self_name
if frappe.db.exists("CRM Product", prod):
frappe.db.set_value("CRM Product", prod, "erpnext_item_code", item, update_modified=False)
if frappe.db.exists("Item", item):
frappe.db.set_value("Item", item, "crm_product_code", prod, update_modified=False)
def _should_sync_direction(self_doctype):
if self_doctype == "CRM Product":
return should_push_to_erpnext()
return should_sync()