mirror of
https://github.com/frappe/erpnext.git
synced 2026-08-13 22:51:49 +00:00
fix(selling): make Customer name de-duplication work on Postgres
get_customer_name's Postgres branch used `Substring(Customer.name, r"\d+$")`, but pypika's Substring is a start/length function, not a regex extractor, so it raised `TypeError: Substring.__init__() missing 1 required positional argument: 'stop'` at query-build time. Creating a second Customer with an existing name therefore failed outright on Postgres. Extract the trailing digits with regexp_replace + NULLIF + CAST instead. A non-numeric trailing token strips to an empty string, which NULLIF turns into NULL so MAX() skips it and COALESCE floors to 0 -- matching MariaDB's CAST(... AS UNSIGNED) -> 0. MariaDB behaviour is unchanged (its branch is untouched). Drops the now-unused Substring import. Adds a test that creates "<name>" and "<name> - 3" and asserts the next de-duplicated name is "<name> - 4" on both engines. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -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.
|
||||
|
||||
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user