Merge pull request #56213 from mihir-kandoi/pg-customer-name-pg-extract

fix(selling): make Customer name de-duplication work on Postgres
This commit is contained in:
Mihir Kandoi
2026-06-21 07:45:37 +05:30
committed by GitHub
2 changed files with 25 additions and 4 deletions

View File

@@ -15,7 +15,7 @@ from frappe.model.document import Document
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
from frappe.query_builder.functions import Cast, Coalesce, Max, Substring
from frappe.query_builder.functions import Cast, Coalesce, Max
from frappe.utils import cint, cstr, flt, get_formatted_email, today
from frappe.utils.user import get_users_with_role
@@ -128,9 +128,15 @@ class Customer(TransactionBase):
Customer = frappe.qb.DocType("Customer")
if frappe.db.db_type == "postgres":
# Postgres: extract trailing digits (e.g. "Customer - 3") and cast to int.
# NOTE: PostgreSQL is strict about types; MySQL's UNSIGNED cast does not exist.
extracted_part = Substring(Customer.name, r"\d+$")
# Postgres: extract the TRAILING digits (e.g. "Customer - 3" -> "3") and cast to int.
# A non-numeric trailing token (e.g. "Customer - Foo") strips to an empty string, which
# NULLIF turns into NULL: MAX() then skips it and COALESCE floors to 0, matching
# MariaDB's CAST(... AS UNSIGNED) -> 0. (pypika's Substring is start/length, not a
# regex, so it can't be used here; UNSIGNED also doesn't exist on postgres, and a raw
# CAST of a non-numeric token to INTEGER would raise instead of yielding NULL.)
regexp_replace = CustomFunction("regexp_replace", ["source", "pattern", "replacement"])
nullif = CustomFunction("NULLIF", ["expr", "value"])
extracted_part = nullif(regexp_replace(Customer.name, r"^.*?(\d*)$", r"\1"), "")
casted_part = Cast(extracted_part, "INTEGER")
else:
# MariaDB/MySQL: keep existing behavior.

View File

@@ -20,6 +20,21 @@ from erpnext.tests.utils import ERPNextTestSuite
class TestCustomer(ERPNextTestSuite):
def test_get_customer_name_dedupes_with_numeric_suffix(self):
# When a customer name already exists, get_customer_name appends "- <max trailing number + 1>".
# The Postgres branch extracts the trailing digits with regexp_replace/NULLIF/CAST (pypika's
# Substring cannot do regex extraction); this exercises that path on both engines.
base = "_Test PG Dedup Customer"
for nm in (base, f"{base} - 3"):
if not frappe.db.exists("Customer", nm):
frappe.get_doc(
{"doctype": "Customer", "customer_name": nm, "customer_type": "Individual"}
).insert()
self.addCleanup(frappe.delete_doc, "Customer", nm, force=1)
doc = frappe.get_doc({"doctype": "Customer", "customer_name": base, "customer_type": "Individual"})
self.assertEqual(doc.get_customer_name(), f"{base} - 4")
def test_get_customer_group_details(self):
doc = frappe.new_doc("Customer Group")
doc.customer_group_name = "_Testing Customer Group"