From cfd37f22db24cdd7493b4076426393776b05e7a0 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Fri, 29 May 2026 12:19:09 +0530 Subject: [PATCH] refactor(customer): move mapping functions to mapper.py --- erpnext/selling/doctype/customer/customer.py | 215 +------------------ erpnext/selling/doctype/customer/mapper.py | 212 ++++++++++++++++++ 2 files changed, 221 insertions(+), 206 deletions(-) create mode 100644 erpnext/selling/doctype/customer/mapper.py diff --git a/erpnext/selling/doctype/customer/customer.py b/erpnext/selling/doctype/customer/customer.py index 8368fd90ee9..f4787a6ab9b 100644 --- a/erpnext/selling/doctype/customer/customer.py +++ b/erpnext/selling/doctype/customer/customer.py @@ -12,7 +12,6 @@ from frappe.contacts.address_and_contact import ( load_address_and_contact, ) from frappe.model.document import Document -from frappe.model.mapper import get_mapped_doc from frappe.model.naming import set_name_by_naming_series, set_name_from_naming_options from frappe.model.utils.rename_doc import update_linked_doctypes from frappe.query_builder import CustomFunction, Field, functions @@ -28,6 +27,15 @@ from erpnext.accounts.party import ( from erpnext.controllers.website_list_for_contact import add_role_for_portal_user from erpnext.utilities.transaction_base import TransactionBase +from .mapper import ( + make_address, + make_contact, + make_opportunity, + make_payment_entry, + make_quotation, + parse_full_name, +) + class Customer(TransactionBase): # begin: auto-generated types @@ -440,117 +448,6 @@ class Customer(TransactionBase): return None -@frappe.whitelist() -def make_quotation(source_name: str, target_doc: str | Document | None = None): - def set_missing_values(source, target): - _set_missing_values(source, target) - - target_doc = get_mapped_doc( - "Customer", - source_name, - {"Customer": {"doctype": "Quotation", "field_map": {"name": "party_name"}}}, - target_doc, - set_missing_values, - ) - - target_doc.quotation_to = "Customer" - target_doc.run_method("set_missing_values") - target_doc.run_method("set_other_charges") - target_doc.run_method("calculate_taxes_and_totals") - - price_list, currency = frappe.db.get_value( - "Customer", {"name": source_name}, ["default_price_list", "default_currency"] - ) - if price_list: - target_doc.selling_price_list = price_list - if currency: - target_doc.currency = currency - - return target_doc - - -@frappe.whitelist() -def make_opportunity(source_name: str, target_doc: str | Document | None = None): - def set_missing_values(source, target): - _set_missing_values(source, target) - - target_doc = get_mapped_doc( - "Customer", - source_name, - { - "Customer": { - "doctype": "Opportunity", - "field_map": { - "name": "party_name", - "doctype": "opportunity_from", - }, - } - }, - target_doc, - set_missing_values, - ) - - return target_doc - - -@frappe.whitelist() -def make_payment_entry(source_name: str, target_doc: str | Document | None = None): - def set_missing_values(source, target): - _set_missing_values(source, target) - - target_doc = get_mapped_doc( - "Customer", - source_name, - { - "Customer": { - "doctype": "Payment Entry", - "field_map": { - "name": "party", - }, - } - }, - target_doc, - set_missing_values, - ) - target_doc.party_type = "Customer" - target_doc.party_name = target_doc.party - - return target_doc - - -def _set_missing_values(source, target): - address = frappe.get_all( - "Dynamic Link", - { - "link_doctype": source.doctype, - "link_name": source.name, - "parenttype": "Address", - }, - ["parent"], - limit=1, - ) - - contact = frappe.get_all( - "Dynamic Link", - { - "link_doctype": source.doctype, - "link_name": source.name, - "parenttype": "Contact", - }, - ["parent"], - limit=1, - ) - - if address: - target.customer_address = address[0].parent - - if contact: - target.contact_person = contact[0].parent - target.contact_display, target.contact_email, target.contact_mobile = frappe.get_value( - "Contact", contact[0].parent, ["full_name", "email_id", "mobile_no"] - ) - - @frappe.whitelist() def get_loyalty_programs(doc: Document): """returns applicable loyalty programs for a customer""" @@ -790,90 +687,6 @@ def get_credit_limit(customer, company): return flt(credit_limit) -def make_contact(args, is_primary_contact=1): - values = { - "doctype": "Contact", - "is_primary_contact": is_primary_contact, - "links": [{"link_doctype": args.get("doctype"), "link_name": args.get("name")}], - } - - party_type = args.customer_type if args.doctype == "Customer" else args.supplier_type - party_name_key = "customer_name" if args.doctype == "Customer" else "supplier_name" - - if party_type == "Individual": - first, middle, last = parse_full_name(args.get(party_name_key)) - values.update( - { - "first_name": first, - "middle_name": middle, - "last_name": last, - } - ) - else: - values.update( - { - "company_name": args.get(party_name_key), - } - ) - - contact = frappe.get_doc(values) - - if args.get("email_id"): - contact.add_email(args.get("email_id"), is_primary=True) - if args.get("mobile_no"): - contact.add_phone(args.get("mobile_no"), is_primary_mobile_no=True) - if args.get("first_name"): - contact.first_name = args.get("first_name") - if args.get("last_name"): - contact.last_name = args.get("last_name") - - if flags := args.get("flags"): - contact.insert(ignore_permissions=flags.get("ignore_permissions")) - else: - contact.insert() - - return contact - - -def make_address(args, is_primary_address=1, is_shipping_address=1): - reqd_fields = [] - for field in ["city", "country"]: - if not args.get(field): - reqd_fields.append("
  • " + field.title() + "
  • ") - - if reqd_fields: - msg = _("Following fields are mandatory to create address:") - frappe.throw( - "{}

    ".format(msg, "\n".join(reqd_fields)), - title=_("Missing Values Required"), - ) - - party_name_key = "customer_name" if args.doctype == "Customer" else "supplier_name" - - address = frappe.get_doc( - { - "doctype": "Address", - "address_title": args.get(party_name_key), - "address_line1": args.get("address_line1"), - "address_line2": args.get("address_line2"), - "city": args.get("city"), - "state": args.get("state"), - "pincode": args.get("pincode"), - "country": args.get("country"), - "is_primary_address": is_primary_address, - "is_shipping_address": is_shipping_address, - "links": [{"link_doctype": args.get("doctype"), "link_name": args.get("name")}], - } - ) - - if flags := args.get("flags"): - address.insert(ignore_permissions=flags.get("ignore_permissions")) - else: - address.insert() - - return address - - @frappe.whitelist() @frappe.validate_and_sanitize_search_inputs def get_customer_primary(doctype: str, txt: str, searchfield: str, start: int, page_len: int, filters: dict): @@ -898,13 +711,3 @@ def get_customer_primary(doctype: str, txt: str, searchfield: str, start: int, p query = query.select(type_doctype.email_id) return query.run() - - -def parse_full_name(full_name: str) -> tuple[str, str | None, str | None]: - """Parse full name into first name, middle name and last name""" - names = full_name.split() - first_name = names[0] - middle_name = " ".join(names[1:-1]) if len(names) > 2 else None - last_name = names[-1] if len(names) > 1 else None - - return first_name, middle_name, last_name diff --git a/erpnext/selling/doctype/customer/mapper.py b/erpnext/selling/doctype/customer/mapper.py new file mode 100644 index 00000000000..7f30aef8cc0 --- /dev/null +++ b/erpnext/selling/doctype/customer/mapper.py @@ -0,0 +1,212 @@ +# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors +# License: GNU General Public License v3. See license.txt + +import frappe +from frappe import _ +from frappe.model.document import Document +from frappe.model.mapper import get_mapped_doc + + +@frappe.whitelist() +def make_quotation(source_name: str, target_doc: str | Document | None = None): + def set_missing_values(source, target): + _set_missing_values(source, target) + + target_doc = get_mapped_doc( + "Customer", + source_name, + {"Customer": {"doctype": "Quotation", "field_map": {"name": "party_name"}}}, + target_doc, + set_missing_values, + ) + + target_doc.quotation_to = "Customer" + target_doc.run_method("set_missing_values") + target_doc.run_method("set_other_charges") + target_doc.run_method("calculate_taxes_and_totals") + + price_list, currency = frappe.db.get_value( + "Customer", {"name": source_name}, ["default_price_list", "default_currency"] + ) + if price_list: + target_doc.selling_price_list = price_list + if currency: + target_doc.currency = currency + + return target_doc + + +@frappe.whitelist() +def make_opportunity(source_name: str, target_doc: str | Document | None = None): + def set_missing_values(source, target): + _set_missing_values(source, target) + + target_doc = get_mapped_doc( + "Customer", + source_name, + { + "Customer": { + "doctype": "Opportunity", + "field_map": { + "name": "party_name", + "doctype": "opportunity_from", + }, + } + }, + target_doc, + set_missing_values, + ) + + return target_doc + + +@frappe.whitelist() +def make_payment_entry(source_name: str, target_doc: str | Document | None = None): + def set_missing_values(source, target): + _set_missing_values(source, target) + + target_doc = get_mapped_doc( + "Customer", + source_name, + { + "Customer": { + "doctype": "Payment Entry", + "field_map": { + "name": "party", + }, + } + }, + target_doc, + set_missing_values, + ) + target_doc.party_type = "Customer" + target_doc.party_name = target_doc.party + + return target_doc + + +def _set_missing_values(source, target): + address = frappe.get_all( + "Dynamic Link", + { + "link_doctype": source.doctype, + "link_name": source.name, + "parenttype": "Address", + }, + ["parent"], + limit=1, + ) + + contact = frappe.get_all( + "Dynamic Link", + { + "link_doctype": source.doctype, + "link_name": source.name, + "parenttype": "Contact", + }, + ["parent"], + limit=1, + ) + + if address: + target.customer_address = address[0].parent + + if contact: + target.contact_person = contact[0].parent + target.contact_display, target.contact_email, target.contact_mobile = frappe.get_value( + "Contact", contact[0].parent, ["full_name", "email_id", "mobile_no"] + ) + + +def make_contact(args, is_primary_contact=1): + values = { + "doctype": "Contact", + "is_primary_contact": is_primary_contact, + "links": [{"link_doctype": args.get("doctype"), "link_name": args.get("name")}], + } + + party_type = args.customer_type if args.doctype == "Customer" else args.supplier_type + party_name_key = "customer_name" if args.doctype == "Customer" else "supplier_name" + + if party_type == "Individual": + first, middle, last = parse_full_name(args.get(party_name_key)) + values.update( + { + "first_name": first, + "middle_name": middle, + "last_name": last, + } + ) + else: + values.update( + { + "company_name": args.get(party_name_key), + } + ) + + contact = frappe.get_doc(values) + + if args.get("email_id"): + contact.add_email(args.get("email_id"), is_primary=True) + if args.get("mobile_no"): + contact.add_phone(args.get("mobile_no"), is_primary_mobile_no=True) + if args.get("first_name"): + contact.first_name = args.get("first_name") + if args.get("last_name"): + contact.last_name = args.get("last_name") + + if flags := args.get("flags"): + contact.insert(ignore_permissions=flags.get("ignore_permissions")) + else: + contact.insert() + + return contact + + +def make_address(args, is_primary_address=1, is_shipping_address=1): + reqd_fields = [] + for field in ["city", "country"]: + if not args.get(field): + reqd_fields.append("
  • " + field.title() + "
  • ") + + if reqd_fields: + msg = _("Following fields are mandatory to create address:") + frappe.throw( + "{}

    ".format(msg, "\n".join(reqd_fields)), + title=_("Missing Values Required"), + ) + + party_name_key = "customer_name" if args.doctype == "Customer" else "supplier_name" + + address = frappe.get_doc( + { + "doctype": "Address", + "address_title": args.get(party_name_key), + "address_line1": args.get("address_line1"), + "address_line2": args.get("address_line2"), + "city": args.get("city"), + "state": args.get("state"), + "pincode": args.get("pincode"), + "country": args.get("country"), + "is_primary_address": is_primary_address, + "is_shipping_address": is_shipping_address, + "links": [{"link_doctype": args.get("doctype"), "link_name": args.get("name")}], + } + ) + + if flags := args.get("flags"): + address.insert(ignore_permissions=flags.get("ignore_permissions")) + else: + address.insert() + + return address + + +def parse_full_name(full_name: str) -> tuple[str, str | None, str | None]: + """Parse full name into first name, middle name and last name""" + names = full_name.split() + first_name = names[0] + middle_name = " ".join(names[1:-1]) if len(names) > 2 else None + last_name = names[-1] if len(names) > 1 else None + + return first_name, middle_name, last_name