Não pode escolher mais do que 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.
 
 
 
 
 
 

441 linhas
13 KiB

  1. # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
  2. # License: MIT. See LICENSE
  3. import re
  4. from typing import TYPE_CHECKING, Optional, Union
  5. import frappe
  6. from frappe import _
  7. from frappe.database.sequence import get_next_val, set_next_val
  8. from frappe.model import log_types
  9. from frappe.query_builder import DocType
  10. from frappe.utils import cint, cstr, now_datetime
  11. if TYPE_CHECKING:
  12. from frappe.model.meta import Meta
  13. def set_new_name(doc):
  14. """
  15. Sets the `name` property for the document based on various rules.
  16. 1. If amended doc, set suffix.
  17. 2. If `autoname` method is declared, then call it.
  18. 3. If `autoname` property is set in the DocType (`meta`), then build it using the `autoname` property.
  19. 4. If no rule defined, use hash.
  20. :param doc: Document to be named.
  21. """
  22. doc.run_method("before_naming")
  23. meta = frappe.get_meta(doc.doctype)
  24. autoname = meta.autoname or ""
  25. if autoname.lower() != "prompt" and not frappe.flags.in_import:
  26. doc.name = None
  27. if is_autoincremented(doc.doctype, meta):
  28. doc.name = get_next_val(doc.doctype)
  29. return
  30. if getattr(doc, "amended_from", None):
  31. _set_amended_name(doc)
  32. return
  33. elif getattr(doc.meta, "issingle", False):
  34. doc.name = doc.doctype
  35. if not doc.name:
  36. set_naming_from_document_naming_rule(doc)
  37. if not doc.name:
  38. doc.run_method("autoname")
  39. if not doc.name and autoname:
  40. set_name_from_naming_options(autoname, doc)
  41. # if the autoname option is 'field:' and no name was derived, we need to
  42. # notify
  43. if not doc.name and autoname.startswith("field:"):
  44. fieldname = autoname[6:]
  45. frappe.throw(_("{0} is required").format(doc.meta.get_label(fieldname)))
  46. # at this point, we fall back to name generation with the hash option
  47. if not doc.name and autoname == "hash":
  48. doc.name = make_autoname("hash", doc.doctype)
  49. if not doc.name:
  50. doc.name = make_autoname("hash", doc.doctype)
  51. doc.name = validate_name(doc.doctype, doc.name, meta.get_field("name_case"))
  52. def is_autoincremented(doctype: str, meta: "Meta" = None):
  53. if doctype in log_types:
  54. if (
  55. frappe.local.autoincremented_status_map.get(frappe.local.site) is None
  56. or frappe.local.autoincremented_status_map[frappe.local.site] == -1
  57. ):
  58. if (
  59. frappe.db.sql(
  60. f"""select data_type FROM information_schema.columns
  61. where column_name = 'name' and table_name = 'tab{doctype}'"""
  62. )[0][0]
  63. == "bigint"
  64. ):
  65. frappe.local.autoincremented_status_map[frappe.local.site] = 1
  66. return True
  67. else:
  68. frappe.local.autoincremented_status_map[frappe.local.site] = 0
  69. elif frappe.local.autoincremented_status_map[frappe.local.site]:
  70. return True
  71. else:
  72. if not meta:
  73. meta = frappe.get_meta(doctype)
  74. if getattr(meta, "issingle", False):
  75. return False
  76. if meta.autoname == "autoincrement":
  77. return True
  78. return False
  79. def set_name_from_naming_options(autoname, doc):
  80. """
  81. Get a name based on the autoname field option
  82. """
  83. _autoname = autoname.lower()
  84. if _autoname.startswith("field:"):
  85. doc.name = _field_autoname(autoname, doc)
  86. elif _autoname.startswith("naming_series:"):
  87. set_name_by_naming_series(doc)
  88. elif _autoname.startswith("prompt"):
  89. _prompt_autoname(autoname, doc)
  90. elif _autoname.startswith("format:"):
  91. doc.name = _format_autoname(autoname, doc)
  92. elif "#" in autoname:
  93. doc.name = make_autoname(autoname, doc=doc)
  94. def set_naming_from_document_naming_rule(doc):
  95. """
  96. Evaluate rules based on "Document Naming Series" doctype
  97. """
  98. if doc.doctype in log_types:
  99. return
  100. # ignore_ddl if naming is not yet bootstrapped
  101. for d in frappe.get_all(
  102. "Document Naming Rule",
  103. dict(document_type=doc.doctype, disabled=0),
  104. order_by="priority desc",
  105. ignore_ddl=True,
  106. ):
  107. frappe.get_cached_doc("Document Naming Rule", d.name).apply(doc)
  108. if doc.name:
  109. break
  110. def set_name_by_naming_series(doc):
  111. """Sets name by the `naming_series` property"""
  112. if not doc.naming_series:
  113. doc.naming_series = get_default_naming_series(doc.doctype)
  114. if not doc.naming_series:
  115. frappe.throw(frappe._("Naming Series mandatory"))
  116. doc.name = make_autoname(doc.naming_series + ".#####", "", doc)
  117. def make_autoname(key="", doctype="", doc=""):
  118. """
  119. Creates an autoname from the given key:
  120. **Autoname rules:**
  121. * The key is separated by '.'
  122. * '####' represents a series. The string before this part becomes the prefix:
  123. Example: ABC.#### creates a series ABC0001, ABC0002 etc
  124. * 'MM' represents the current month
  125. * 'YY' and 'YYYY' represent the current year
  126. *Example:*
  127. * DE/./.YY./.MM./.##### will create a series like
  128. DE/09/01/0001 where 09 is the year, 01 is the month and 0001 is the series
  129. """
  130. if key == "hash":
  131. return frappe.generate_hash(doctype, 10)
  132. if "#" not in key:
  133. key = key + ".#####"
  134. elif "." not in key:
  135. error_message = _("Invalid naming series (. missing)")
  136. if doctype:
  137. error_message = _("Invalid naming series (. missing) for {0}").format(doctype)
  138. frappe.throw(error_message)
  139. parts = key.split(".")
  140. n = parse_naming_series(parts, doctype, doc)
  141. return n
  142. def parse_naming_series(parts, doctype="", doc=""):
  143. n = ""
  144. if isinstance(parts, str):
  145. parts = parts.split(".")
  146. series_set = False
  147. today = now_datetime()
  148. for e in parts:
  149. part = ""
  150. if e.startswith("#"):
  151. if not series_set:
  152. digits = len(e)
  153. part = getseries(n, digits)
  154. series_set = True
  155. elif e == "YY":
  156. part = today.strftime("%y")
  157. elif e == "MM":
  158. part = today.strftime("%m")
  159. elif e == "DD":
  160. part = today.strftime("%d")
  161. elif e == "YYYY":
  162. part = today.strftime("%Y")
  163. elif e == "WW":
  164. part = determine_consecutive_week_number(today)
  165. elif e == "timestamp":
  166. part = str(today)
  167. elif e == "FY":
  168. part = frappe.defaults.get_user_default("fiscal_year")
  169. elif e.startswith("{") and doc:
  170. e = e.replace("{", "").replace("}", "")
  171. part = doc.get(e)
  172. elif doc and doc.get(e):
  173. part = doc.get(e)
  174. else:
  175. part = e
  176. if isinstance(part, str):
  177. n += part
  178. return n
  179. def determine_consecutive_week_number(datetime):
  180. """Determines the consecutive calendar week"""
  181. m = datetime.month
  182. # ISO 8601 calandar week
  183. w = datetime.strftime("%V")
  184. # Ensure consecutiveness for the first and last days of a year
  185. if m == 1 and int(w) >= 52:
  186. w = "00"
  187. elif m == 12 and int(w) <= 1:
  188. w = "53"
  189. return w
  190. def getseries(key, digits):
  191. # series created ?
  192. # Using frappe.qb as frappe.get_values does not allow order_by=None
  193. series = DocType("Series")
  194. current = (frappe.qb.from_(series).where(series.name == key).for_update().select("current")).run()
  195. if current and current[0][0] is not None:
  196. current = current[0][0]
  197. # yes, update it
  198. frappe.db.sql("UPDATE `tabSeries` SET `current` = `current` + 1 WHERE `name`=%s", (key,))
  199. current = cint(current) + 1
  200. else:
  201. # no, create it
  202. frappe.db.sql("INSERT INTO `tabSeries` (`name`, `current`) VALUES (%s, 1)", (key,))
  203. current = 1
  204. return ("%0" + str(digits) + "d") % current
  205. def revert_series_if_last(key, name, doc=None):
  206. """
  207. Reverts the series for particular naming series:
  208. * key is naming series - SINV-.YYYY-.####
  209. * name is actual name - SINV-2021-0001
  210. 1. This function split the key into two parts prefix (SINV-YYYY) & hashes (####).
  211. 2. Use prefix to get the current index of that naming series from Series table
  212. 3. Then revert the current index.
  213. *For custom naming series:*
  214. 1. hash can exist anywhere, if it exist in hashes then it take normal flow.
  215. 2. If hash doesn't exit in hashes, we get the hash from prefix, then update name and prefix accordingly.
  216. *Example:*
  217. 1. key = SINV-.YYYY.-
  218. * If key doesn't have hash it will add hash at the end
  219. * prefix will be SINV-YYYY based on this will get current index from Series table.
  220. 2. key = SINV-.####.-2021
  221. * now prefix = SINV-#### and hashes = 2021 (hash doesn't exist)
  222. * will search hash in key then accordingly get prefix = SINV-
  223. 3. key = ####.-2021
  224. * prefix = #### and hashes = 2021 (hash doesn't exist)
  225. * will search hash in key then accordingly get prefix = ""
  226. """
  227. if ".#" in key:
  228. prefix, hashes = key.rsplit(".", 1)
  229. if "#" not in hashes:
  230. # get the hash part from the key
  231. hash = re.search("#+", key)
  232. if not hash:
  233. return
  234. name = name.replace(hashes, "")
  235. prefix = prefix.replace(hash.group(), "")
  236. else:
  237. prefix = key
  238. if "." in prefix:
  239. prefix = parse_naming_series(prefix.split("."), doc=doc)
  240. count = cint(name.replace(prefix, ""))
  241. series = DocType("Series")
  242. current = (
  243. frappe.qb.from_(series).where(series.name == prefix).for_update().select("current")
  244. ).run()
  245. if current and current[0][0] == count:
  246. frappe.db.sql("UPDATE `tabSeries` SET `current` = `current` - 1 WHERE `name`=%s", prefix)
  247. def get_default_naming_series(doctype):
  248. """get default value for `naming_series` property"""
  249. naming_series = frappe.get_meta(doctype).get_field("naming_series").options or ""
  250. if naming_series:
  251. naming_series = naming_series.split("\n")
  252. return naming_series[0] or naming_series[1]
  253. else:
  254. return None
  255. def validate_name(doctype: str, name: Union[int, str], case: Optional[str] = None):
  256. if not name:
  257. frappe.throw(_("No Name Specified for {0}").format(doctype))
  258. if isinstance(name, int):
  259. if is_autoincremented(doctype):
  260. # this will set the sequence val to be the provided name and set it to be used
  261. # so that the sequence will start from the next val of the setted val(name)
  262. set_next_val(doctype, name, is_val_used=True)
  263. return name
  264. frappe.throw(_("Invalid name type (integer) for varchar name column"), frappe.NameError)
  265. if name.startswith("New " + doctype):
  266. frappe.throw(
  267. _("There were some errors setting the name, please contact the administrator"), frappe.NameError
  268. )
  269. if case == "Title Case":
  270. name = name.title()
  271. if case == "UPPER CASE":
  272. name = name.upper()
  273. name = name.strip()
  274. if not frappe.get_meta(doctype).get("issingle") and (doctype == name) and (name != "DocType"):
  275. frappe.throw(_("Name of {0} cannot be {1}").format(doctype, name), frappe.NameError)
  276. special_characters = "<>"
  277. if re.findall("[{0}]+".format(special_characters), name):
  278. message = ", ".join("'{0}'".format(c) for c in special_characters)
  279. frappe.throw(
  280. _("Name cannot contain special characters like {0}").format(message), frappe.NameError
  281. )
  282. return name
  283. def append_number_if_name_exists(doctype, value, fieldname="name", separator="-", filters=None):
  284. if not filters:
  285. filters = dict()
  286. filters.update({fieldname: value})
  287. exists = frappe.db.exists(doctype, filters)
  288. regex = "^{value}{separator}\\d+$".format(value=re.escape(value), separator=separator)
  289. if exists:
  290. last = frappe.db.sql(
  291. """SELECT `{fieldname}` FROM `tab{doctype}`
  292. WHERE `{fieldname}` {regex_character} %s
  293. ORDER BY length({fieldname}) DESC,
  294. `{fieldname}` DESC LIMIT 1""".format(
  295. doctype=doctype, fieldname=fieldname, regex_character=frappe.db.REGEX_CHARACTER
  296. ),
  297. regex,
  298. )
  299. if last:
  300. count = str(cint(last[0][0].rsplit(separator, 1)[1]) + 1)
  301. else:
  302. count = "1"
  303. value = "{0}{1}{2}".format(value, separator, count)
  304. return value
  305. def _set_amended_name(doc):
  306. am_id = 1
  307. am_prefix = doc.amended_from
  308. if frappe.db.get_value(doc.doctype, doc.amended_from, "amended_from"):
  309. am_id = cint(doc.amended_from.split("-")[-1]) + 1
  310. am_prefix = "-".join(doc.amended_from.split("-")[:-1]) # except the last hyphen
  311. doc.name = am_prefix + "-" + str(am_id)
  312. return doc.name
  313. def _field_autoname(autoname, doc, skip_slicing=None):
  314. """
  315. Generate a name using `DocType` field. This is called when the doctype's
  316. `autoname` field starts with 'field:'
  317. """
  318. fieldname = autoname if skip_slicing else autoname[6:]
  319. name = (cstr(doc.get(fieldname)) or "").strip()
  320. return name
  321. def _prompt_autoname(autoname, doc):
  322. """
  323. Generate a name using Prompt option. This simply means the user will have to set the name manually.
  324. This is called when the doctype's `autoname` field starts with 'prompt'.
  325. """
  326. # set from __newname in save.py
  327. if not doc.name:
  328. frappe.throw(_("Please set the document name"))
  329. def _format_autoname(autoname, doc):
  330. """
  331. Generate autoname by replacing all instances of braced params (fields, date params ('DD', 'MM', 'YY'), series)
  332. Independent of remaining string or separators.
  333. Example pattern: 'format:LOG-{MM}-{fieldname1}-{fieldname2}-{#####}'
  334. """
  335. first_colon_index = autoname.find(":")
  336. autoname_value = autoname[first_colon_index + 1 :]
  337. def get_param_value_for_match(match):
  338. param = match.group()
  339. # trim braces
  340. trimmed_param = param[1:-1]
  341. return parse_naming_series([trimmed_param], doc=doc)
  342. # Replace braced params with their parsed value
  343. name = re.sub(r"(\{[\w | #]+\})", get_param_value_for_match, autoname_value)
  344. return name