mirror of
https://github.com/frappe/erpnext.git
synced 2026-08-12 06:01:46 +00:00
fix(selling): match MariaDB's customer-name suffix extraction on Postgres
get_customer_name's Postgres branch extracted the PURE TRAILING digits of the
name (regexp '^.*?(\d*)$'), while the MariaDB branch uses
CAST(SUBSTRING_INDEX(name, ' ', -1) AS UNSIGNED) — the LEADING digits of the last
whitespace token. For a scanned name like "<base> - 3a" MariaDB yields 3 but
Postgres yielded NULL→0, so the next de-duplicated number (and thus the generated
Customer name) diverged between engines.
Make the Postgres branch take the last whitespace token then its leading digits,
mirroring MariaDB exactly ("X - 3a"->3, "X - 1.5"->1, "X - Foo"->0). Add a
regression test with a "<base> - 3a" name asserting the next name is "<base> - 4"
on both engines (it produced "<base> - 1" on the old Postgres regex).
This commit is contained in:
@@ -128,15 +128,17 @@ class Customer(TransactionBase):
|
||||
Customer = frappe.qb.DocType("Customer")
|
||||
|
||||
if frappe.db.db_type == "postgres":
|
||||
# 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.)
|
||||
# Mirror MariaDB's CAST(SUBSTRING_INDEX(name, ' ', -1) AS UNSIGNED): take the last
|
||||
# whitespace-delimited token, then its LEADING digits, and cast to int. So "X - 3" -> 3,
|
||||
# "X - 3a" -> 3, "X - 1.5" -> 1, matching MariaDB exactly. A non-numeric token (e.g.
|
||||
# "X - Foo") strips to '' which NULLIF turns into NULL: MAX() skips it and COALESCE floors
|
||||
# to 0, matching MariaDB's CAST(... AS UNSIGNED) -> 0. (pypika's Substring is start/length,
|
||||
# not a regex; UNSIGNED 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"), "")
|
||||
last_token = regexp_replace(Customer.name, r"^.*\s", "")
|
||||
extracted_part = nullif(regexp_replace(last_token, r"^(\d*).*$", r"\1"), "")
|
||||
casted_part = Cast(extracted_part, "INTEGER")
|
||||
else:
|
||||
# MariaDB/MySQL: keep existing behavior.
|
||||
|
||||
@@ -21,9 +21,9 @@ 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.
|
||||
# When a customer name already exists, get_customer_name appends "- <max suffix + 1>". The
|
||||
# Postgres branch extracts the suffix 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):
|
||||
@@ -35,6 +35,22 @@ class TestCustomer(ERPNextTestSuite):
|
||||
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_name_dedupe_handles_mixed_suffix(self):
|
||||
# The suffix extractor must read the LEADING digits of the last whitespace-token, like MariaDB's
|
||||
# CAST(SUBSTRING_INDEX(name, ' ', -1) AS UNSIGNED): "<base> - 3a" -> 3, so the next name is
|
||||
# "<base> - 4". The earlier Postgres regex read pure-trailing digits, yielding 0 for "3a" and
|
||||
# diverging from MariaDB (which would have produced "<base> - 1"). Asserts engine parity.
|
||||
base = "_Test PG Dedup Mixed"
|
||||
for nm in (base, f"{base} - 3a"):
|
||||
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