Merge pull request #28363 from frappe/expr-series

fix(NamingExpression): series should be separate for different format expressions instead of global
This commit is contained in:
Sumit Bhanushali 2024-12-09 12:13:28 +05:30 committed by GitHub
commit 91d553c9cf
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 90 additions and 12 deletions

View file

@ -1085,6 +1085,12 @@ def validate_series(dt, autoname=None, name=None):
df.unique = 1
break
if autoname and autoname.startswith("format:"):
from frappe.model.naming import BRACED_PARAMS_HASH_PATTERN
if len(BRACED_PARAMS_HASH_PATTERN.findall(autoname)) > 1:
frappe.throw(_("Only one set of {#} pattern is allowed in the format string"))
if (
autoname
and (not autoname.startswith("field:"))

View file

@ -23,7 +23,8 @@ if TYPE_CHECKING:
NAMING_SERIES_PATTERN = re.compile(r"^[\w\- \/.#{}]+$", re.UNICODE)
BRACED_PARAMS_PATTERN = re.compile(r"(\{[\w | #]+\})")
BRACED_PARAMS_WORD_PATTERN = re.compile(r"(\{[\w]+\})")
BRACED_PARAMS_HASH_PATTERN = re.compile(r"(\{[#]+\})")
# Types that can be using in naming series fields
@ -314,6 +315,7 @@ def parse_naming_series(
doctype=None,
doc: Optional["Document"] = None,
number_generator: Callable[[str, int], str] | None = None,
key: str | None = None,
) -> str:
"""Parse the naming series and get next name.
@ -341,7 +343,10 @@ def parse_naming_series(
if e.startswith("#"):
if not series_set:
digits = len(e)
part = number_generator(name, digits)
if key:
part = number_generator(key, digits)
else:
part = number_generator(name, digits)
series_set = True
elif e == "YY":
part = today.strftime("%y")
@ -575,11 +580,19 @@ def _format_autoname(autoname: str, doc):
first_colon_index = autoname.find(":")
autoname_value = autoname[first_colon_index + 1 :]
def get_param_value_for_match(match):
def get_param_value_for_word_match(match):
param = match.group()
return parse_naming_series([param[1:-1]], doc=doc)
# Replace braced params with their parsed value
name = BRACED_PARAMS_PATTERN.sub(get_param_value_for_match, autoname_value)
def get_param_value_for_hash_match(patterned_string: str):
def get_param_value(match):
param = match.group()
key = patterned_string[: patterned_string.find(param)]
return name
return parse_naming_series([param[1:-1]], doc=doc, key=key)
return get_param_value
# Replace braced params with their parsed value
autoname_value = BRACED_PARAMS_WORD_PATTERN.sub(get_param_value_for_word_match, autoname_value)
return BRACED_PARAMS_HASH_PATTERN.sub(get_param_value_for_hash_match(autoname_value), autoname_value)

View file

@ -242,3 +242,4 @@ execute:frappe.db.set_single_value("Workspace Settings", "workspace_setup_comple
frappe.patches.v16_0.add_app_launcher_in_navbar_settings
frappe.desk.doctype.workspace.patches.update_app
frappe.patches.v16_0.move_role_desk_settings_to_user
frappe.patches.v16_0.update_expression_series

View file

@ -0,0 +1,59 @@
import frappe
from frappe.model.naming import (
BRACED_PARAMS_WORD_PATTERN,
determine_consecutive_week_number,
has_custom_parser,
)
from frappe.query_builder import DocType
def execute():
Series = DocType("Series")
doctypes = frappe.get_all("DocType", filters={"naming_rule": "expression"}, fields=["name", "autoname"])
uniq_exprs = set()
def get_param_value_for_word_match(doc):
def get_param_value(match):
e = match.group()[1:-1]
creation = doc.creation
_sentinel = object()
part = ""
if e == "YY":
part = creation.strftime("%y")
elif e == "MM":
part = creation.strftime("%m")
elif e == "DD":
part = creation.strftime("%d")
elif e == "YYYY":
part = creation.strftime("%Y")
elif e == "WW":
part = determine_consecutive_week_number(creation)
elif e == "timestamp":
part = str(creation)
elif doc and (e.startswith("{") or doc.get(e, _sentinel) is not _sentinel):
e = e.replace("{", "").replace("}", "")
part = doc.get(e)
elif method := has_custom_parser(e):
part = frappe.get_attr(method[0])(doc, e)
else:
part = e
return part
return get_param_value
for doctype in doctypes:
if "#" in doctype.autoname:
docs = frappe.get_all(doctype.name)
if docs:
for doc in docs:
_doc = frappe.get_doc(doctype.name, doc.name)
expr = doctype.autoname[7 : doctype.autoname.find("{#")]
key = BRACED_PARAMS_WORD_PATTERN.sub(get_param_value_for_word_match(_doc), expr)
uniq_exprs.add(key)
current = (frappe.qb.from_(Series).select("*").where(Series.name == "")).run(as_dict=True)
if current:
current = current[0].get("current")
for uniq_expr in uniq_exprs:
(frappe.qb.into(Series).columns("name", "current").insert(uniq_expr, current + 1)).run()

View file

@ -103,7 +103,8 @@ class TestNaming(IntegrationTestCase):
doc.some_fieldname = description
doc.insert()
series = getseries("", 2)
series = getseries(f"TODO-{now_datetime().strftime('%m')}-{description}-", 2)
series = int(series) - 1
self.assertEqual(doc.name, f"TODO-{now_datetime().strftime('%m')}-{description}-{series:02}")
@ -117,7 +118,7 @@ class TestNaming(IntegrationTestCase):
doc.field = field
doc.insert()
series = getseries("", 2)
series = getseries(f"TODO-{field}-", 2)
series = int(series) - 1
self.assertEqual(doc.name, f"TODO-{field}-{series:02}")
@ -138,15 +139,13 @@ class TestNaming(IntegrationTestCase):
todo.description = description
todo.insert()
series = getseries("", 2)
week = determine_consecutive_week_number(now_datetime())
series = getseries(f"TODO-{week}-", 2)
series = str(int(series) - 1)
if len(series) < 2:
series = "0" + series
week = determine_consecutive_week_number(now_datetime())
self.assertEqual(todo.name, f"TODO-{week}-{series}")
def test_revert_series(self):