diff --git a/erpnext/buying/doctype/buying_settings/buying_settings.py b/erpnext/buying/doctype/buying_settings/buying_settings.py index c52b59e4c0c4..7b18cdbedcd3 100644 --- a/erpnext/buying/doctype/buying_settings/buying_settings.py +++ b/erpnext/buying/doctype/buying_settings/buying_settings.py @@ -18,7 +18,7 @@ def validate(self): for key in ["supplier_group", "supp_master_name", "maintain_same_rate", "buying_price_list"]: frappe.db.set_default(key, self.get(key, "")) - from erpnext.setup.doctype.naming_series.naming_series import set_by_naming_series + from erpnext.utilities.naming import set_by_naming_series set_by_naming_series( "Supplier", diff --git a/erpnext/e_commerce/doctype/website_item/website_item.py b/erpnext/e_commerce/doctype/website_item/website_item.py index 02ec3bf1f3fa..f6fea72f8a41 100644 --- a/erpnext/e_commerce/doctype/website_item/website_item.py +++ b/erpnext/e_commerce/doctype/website_item/website_item.py @@ -34,9 +34,7 @@ class WebsiteItem(WebsiteGenerator): def autoname(self): # use naming series to accomodate items with same name (different item code) - from frappe.model.naming import make_autoname - - from erpnext.setup.doctype.naming_series.naming_series import get_default_naming_series + from frappe.model.naming import get_default_naming_series, make_autoname naming_series = get_default_naming_series("Website Item") if not self.name and naming_series: diff --git a/erpnext/hr/doctype/hr_settings/hr_settings.py b/erpnext/hr/doctype/hr_settings/hr_settings.py index 72a49e285a0a..b56f3dbe0d7f 100644 --- a/erpnext/hr/doctype/hr_settings/hr_settings.py +++ b/erpnext/hr/doctype/hr_settings/hr_settings.py @@ -22,7 +22,7 @@ def validate(self): PROCEED_WITH_FREQUENCY_CHANGE = False def set_naming_series(self): - from erpnext.setup.doctype.naming_series.naming_series import set_by_naming_series + from erpnext.utilities.naming import set_by_naming_series set_by_naming_series( "Employee", diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 8c0ebe7a9043..785e2baa11bd 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -372,3 +372,4 @@ erpnext.patches.v14_0.discount_accounting_separation erpnext.patches.v14_0.delete_employee_transfer_property_doctype erpnext.patches.v13_0.create_accounting_dimensions_in_orders erpnext.patches.v13_0.set_per_billed_in_return_delivery_note +execute:frappe.delete_doc("DocType", "Naming Series") diff --git a/erpnext/patches/v13_0/item_naming_series_not_mandatory.py b/erpnext/patches/v13_0/item_naming_series_not_mandatory.py index 33fb8f963c53..0235a621ce04 100644 --- a/erpnext/patches/v13_0/item_naming_series_not_mandatory.py +++ b/erpnext/patches/v13_0/item_naming_series_not_mandatory.py @@ -1,6 +1,6 @@ import frappe -from erpnext.setup.doctype.naming_series.naming_series import set_by_naming_series +from erpnext.utilities.naming import set_by_naming_series def execute(): diff --git a/erpnext/selling/doctype/selling_settings/selling_settings.py b/erpnext/selling/doctype/selling_settings/selling_settings.py index 6c098942510c..d977807e7dc9 100644 --- a/erpnext/selling/doctype/selling_settings/selling_settings.py +++ b/erpnext/selling/doctype/selling_settings/selling_settings.py @@ -27,7 +27,7 @@ def validate(self): ]: frappe.db.set_default(key, self.get(key, "")) - from erpnext.setup.doctype.naming_series.naming_series import set_by_naming_series + from erpnext.utilities.naming import set_by_naming_series set_by_naming_series( "Customer", diff --git a/erpnext/setup/doctype/naming_series/README.md b/erpnext/setup/doctype/naming_series/README.md deleted file mode 100644 index 5a9b8ca8618e..000000000000 --- a/erpnext/setup/doctype/naming_series/README.md +++ /dev/null @@ -1 +0,0 @@ -Tool to set numbering (naming) series for various DocTypes. \ No newline at end of file diff --git a/erpnext/setup/doctype/naming_series/__init__.py b/erpnext/setup/doctype/naming_series/__init__.py deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/erpnext/setup/doctype/naming_series/naming_series.js b/erpnext/setup/doctype/naming_series/naming_series.js deleted file mode 100644 index 0fb72abba695..000000000000 --- a/erpnext/setup/doctype/naming_series/naming_series.js +++ /dev/null @@ -1,88 +0,0 @@ -// Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors -// For license information, please see license.txt - - -frappe.ui.form.on("Naming Series", { - onload: function(frm) { - frm.events.get_doc_and_prefix(frm); - }, - - refresh: function(frm) { - frm.disable_save(); - }, - - get_doc_and_prefix: function(frm) { - frappe.call({ - method: "get_transactions", - doc: frm.doc, - callback: function(r) { - frm.set_df_property("select_doc_for_series", "options", r.message.transactions); - frm.set_df_property("prefix", "options", r.message.prefixes); - } - }); - }, - - select_doc_for_series: function(frm) { - frm.set_value("user_must_always_select", 0); - frappe.call({ - method: "get_options", - doc: frm.doc, - callback: function(r) { - frm.set_value("set_options", r.message); - if(r.message && r.message.split('\n')[0]=='') - frm.set_value('user_must_always_select', 1); - frm.refresh(); - } - }); - }, - - prefix: function(frm) { - frappe.call({ - method: "get_current", - doc: frm.doc, - callback: function(r) { - frm.refresh_field("current_value"); - } - }); - }, - - update: function(frm) { - frappe.call({ - method: "update_series", - doc: frm.doc, - callback: function(r) { - frm.events.get_doc_and_prefix(frm); - } - }); - }, - - naming_series_to_check(frm) { - frappe.call({ - method: "preview_series", - doc: frm.doc, - callback: function(r) { - if (!r.exc) { - frm.set_value("preview", r.message); - } else { - frm.set_value("preview", __("Failed to generate preview of series")); - } - } - }); - }, - - add_series(frm) { - const series = frm.doc.naming_series_to_check; - - if (!series) { - frappe.show_alert(__("Please type a valid series.")); - return; - } - - if (!frm.doc.set_options.includes(series)) { - const current_series = frm.doc.set_options; - frm.set_value("set_options", `${current_series}\n${series}`); - } else { - frappe.show_alert(__("Series already added to transaction.")); - } - }, -}); diff --git a/erpnext/setup/doctype/naming_series/naming_series.json b/erpnext/setup/doctype/naming_series/naming_series.json deleted file mode 100644 index c65a6f0ae448..000000000000 --- a/erpnext/setup/doctype/naming_series/naming_series.json +++ /dev/null @@ -1,132 +0,0 @@ -{ - "actions": [], - "creation": "2022-05-26 03:12:49.087648", - "description": "Set prefix for numbering series on your transactions", - "doctype": "DocType", - "engine": "InnoDB", - "field_order": [ - "setup_series", - "select_doc_for_series", - "help_html", - "naming_series_to_check", - "preview", - "add_series", - "set_options", - "user_must_always_select", - "update", - "column_break_13", - "update_series", - "prefix", - "current_value", - "update_series_start" - ], - "fields": [ - { - "description": "Set prefix for numbering series on your transactions", - "fieldname": "setup_series", - "fieldtype": "Section Break", - "label": "Setup Series" - }, - { - "fieldname": "select_doc_for_series", - "fieldtype": "Select", - "label": "Select Transaction" - }, - { - "depends_on": "select_doc_for_series", - "fieldname": "help_html", - "fieldtype": "HTML", - "label": "Help HTML", - "options": "
\n Edit list of Series in the box below. Rules:\n \n Examples:\n \n
\n
\n" - }, - { - "depends_on": "select_doc_for_series", - "fieldname": "set_options", - "fieldtype": "Text", - "label": "Series List for this Transaction" - }, - { - "default": "0", - "depends_on": "select_doc_for_series", - "description": "Check this if you want to force the user to select a series before saving. There will be no default if you check this.", - "fieldname": "user_must_always_select", - "fieldtype": "Check", - "label": "User must always select" - }, - { - "depends_on": "select_doc_for_series", - "fieldname": "update", - "fieldtype": "Button", - "label": "Update" - }, - { - "description": "Change the starting / current sequence number of an existing series.", - "fieldname": "update_series", - "fieldtype": "Section Break", - "label": "Update Series" - }, - { - "fieldname": "prefix", - "fieldtype": "Select", - "label": "Prefix" - }, - { - "description": "This is the number of the last created transaction with this prefix", - "fieldname": "current_value", - "fieldtype": "Int", - "label": "Current Value" - }, - { - "fieldname": "update_series_start", - "fieldtype": "Button", - "label": "Update Series Number", - "options": "update_series_start" - }, - { - "fieldname": "naming_series_to_check", - "fieldtype": "Data", - "label": "Try a naming Series" - }, - { - "default": " ", - "fieldname": "preview", - "fieldtype": "Text", - "label": "Preview of generated names", - "read_only": 1 - }, - { - "fieldname": "column_break_13", - "fieldtype": "Column Break" - }, - { - "fieldname": "add_series", - "fieldtype": "Button", - "label": "Add this Series" - } - ], - "hide_toolbar": 1, - "icon": "fa fa-sort-by-order", - "idx": 1, - "issingle": 1, - "links": [], - "modified": "2022-05-26 06:06:42.109504", - "modified_by": "Administrator", - "module": "Setup", - "name": "Naming Series", - "owner": "Administrator", - "permissions": [ - { - "create": 1, - "email": 1, - "print": 1, - "read": 1, - "role": "System Manager", - "share": 1, - "write": 1 - } - ], - "read_only": 1, - "sort_field": "modified", - "sort_order": "DESC", - "states": [] -} \ No newline at end of file diff --git a/erpnext/setup/doctype/naming_series/naming_series.py b/erpnext/setup/doctype/naming_series/naming_series.py deleted file mode 100644 index eafc264f3081..000000000000 --- a/erpnext/setup/doctype/naming_series/naming_series.py +++ /dev/null @@ -1,303 +0,0 @@ -# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# License: GNU General Public License v3. See license.txt - - -import frappe -from frappe import _, msgprint, throw -from frappe.core.doctype.doctype.doctype import validate_series -from frappe.model.document import Document -from frappe.model.naming import make_autoname, parse_naming_series -from frappe.permissions import get_doctypes_with_read -from frappe.utils import cint, cstr - - -class NamingSeriesNotSetError(frappe.ValidationError): - pass - - -class NamingSeries(Document): - @frappe.whitelist() - def get_transactions(self, arg=None): - doctypes = list( - set( - frappe.db.sql_list( - """select parent - from `tabDocField` df where fieldname='naming_series'""" - ) - + frappe.db.sql_list( - """select dt from `tabCustom Field` - where fieldname='naming_series'""" - ) - ) - ) - - doctypes = list(set(get_doctypes_with_read()).intersection(set(doctypes))) - prefixes = "" - for d in doctypes: - options = "" - try: - options = self.get_options(d) - except frappe.DoesNotExistError: - frappe.msgprint(_("Unable to find DocType {0}").format(d)) - # frappe.pass_does_not_exist_error() - continue - - if options: - prefixes = prefixes + "\n" + options - prefixes.replace("\n\n", "\n") - prefixes = prefixes.split("\n") - - custom_prefixes = frappe.get_all( - "DocType", - fields=["autoname"], - filters={ - "name": ("not in", doctypes), - "autoname": ("like", "%.#%"), - "module": ("not in", ["Core"]), - }, - ) - if custom_prefixes: - prefixes = prefixes + [d.autoname.rsplit(".", 1)[0] for d in custom_prefixes] - - prefixes = "\n".join(sorted(prefixes)) - - return {"transactions": "\n".join([""] + sorted(doctypes)), "prefixes": prefixes} - - def scrub_options_list(self, ol): - options = list(filter(lambda x: x, [cstr(n).strip() for n in ol])) - return options - - @frappe.whitelist() - def update_series(self, arg=None): - """update series list""" - self.validate_series_set() - self.check_duplicate() - series_list = self.set_options.split("\n") - - # set in doctype - self.set_series_for(self.select_doc_for_series, series_list) - - # create series - map(self.insert_series, [d.split(".")[0] for d in series_list if d.strip()]) - - msgprint(_("Series Updated")) - - return self.get_transactions() - - def validate_series_set(self): - if self.select_doc_for_series and not self.set_options: - frappe.throw(_("Please set the series to be used.")) - - def set_series_for(self, doctype, ol): - options = self.scrub_options_list(ol) - - # validate names - for i in options: - self.validate_series_name(i) - - if options and self.user_must_always_select: - options = [""] + options - - default = options[0] if options else "" - - # update in property setter - prop_dict = {"options": "\n".join(options), "default": default} - - for prop in prop_dict: - ps_exists = frappe.db.get_value( - "Property Setter", {"field_name": "naming_series", "doc_type": doctype, "property": prop} - ) - - if ps_exists: - ps = frappe.get_doc("Property Setter", ps_exists) - ps.value = prop_dict[prop] - ps.save() - else: - ps = frappe.get_doc( - { - "doctype": "Property Setter", - "doctype_or_field": "DocField", - "doc_type": doctype, - "field_name": "naming_series", - "property": prop, - "value": prop_dict[prop], - "property_type": "Text", - "__islocal": 1, - } - ) - ps.save() - - self.set_options = "\n".join(options) - - frappe.clear_cache(doctype=doctype) - - def check_duplicate(self): - parent = list( - set( - frappe.db.sql_list( - """select dt.name - from `tabDocField` df, `tabDocType` dt - where dt.name = df.parent and df.fieldname='naming_series' and dt.name != %s""", - self.select_doc_for_series, - ) - + frappe.db.sql_list( - """select dt.name - from `tabCustom Field` df, `tabDocType` dt - where dt.name = df.dt and df.fieldname='naming_series' and dt.name != %s""", - self.select_doc_for_series, - ) - ) - ) - sr = [[frappe.get_meta(p).get_field("naming_series").options, p] for p in parent] - dt = frappe.get_doc("DocType", self.select_doc_for_series) - options = self.scrub_options_list(self.set_options.split("\n")) - for series in options: - validate_series(dt, series) - for i in sr: - if i[0]: - existing_series = [d.split(".")[0] for d in i[0].split("\n")] - if series.split(".")[0] in existing_series: - frappe.throw(_("Series {0} already used in {1}").format(series, i[1])) - - def validate_series_name(self, n): - import re - - if not re.match(r"^[\w\- \/.#{}]+$", n, re.UNICODE): - throw( - _('Special Characters except "-", "#", ".", "/", "{" and "}" not allowed in naming series') - ) - - @frappe.whitelist() - def get_options(self, arg=None): - if frappe.get_meta(arg or self.select_doc_for_series).get_field("naming_series"): - return frappe.get_meta(arg or self.select_doc_for_series).get_field("naming_series").options - - @frappe.whitelist() - def get_current(self, arg=None): - """get series current""" - if self.prefix: - prefix = self.parse_naming_series() - self.current_value = frappe.db.get_value("Series", prefix, "current", order_by="name") - - def insert_series(self, series): - """insert series if missing""" - if frappe.db.get_value("Series", series, "name", order_by="name") == None: - frappe.db.sql("insert into tabSeries (name, current) values (%s, 0)", (series)) - - @frappe.whitelist() - def update_series_start(self): - if self.prefix: - prefix = self.parse_naming_series() - self.insert_series(prefix) - frappe.db.sql( - "update `tabSeries` set current = %s where name = %s", (cint(self.current_value), prefix) - ) - msgprint(_("Series Updated Successfully")) - else: - msgprint(_("Please select prefix first")) - - def parse_naming_series(self): - parts = self.prefix.split(".") - - # Remove ### from the end of series - if parts[-1] == "#" * len(parts[-1]): - del parts[-1] - - prefix = parse_naming_series(parts) - return prefix - - @frappe.whitelist() - def preview_series(self) -> str: - """Preview what the naming series will generate.""" - - generated_names = [] - series = self.naming_series_to_check - if not series: - return "" - - try: - doc = self._fetch_last_doc_if_available() - for _count in range(3): - generated_names.append(make_autoname(series, doc=doc)) - except Exception as e: - if frappe.message_log: - frappe.message_log.pop() - return _("Failed to generate names from the series") + f"\n{str(e)}" - - # Explcitly rollback in case any changes were made to series table. - frappe.db.rollback() # nosemgrep - return "\n".join(generated_names) - - def _fetch_last_doc_if_available(self): - """Fetch last doc for evaluating naming series with fields.""" - try: - return frappe.get_last_doc(self.select_doc_for_series) - except Exception: - return None - - -def set_by_naming_series( - doctype, fieldname, naming_series, hide_name_field=True, make_mandatory=1 -): - from frappe.custom.doctype.property_setter.property_setter import make_property_setter - - if naming_series: - make_property_setter( - doctype, "naming_series", "hidden", 0, "Check", validate_fields_for_doctype=False - ) - make_property_setter( - doctype, "naming_series", "reqd", make_mandatory, "Check", validate_fields_for_doctype=False - ) - - # set values for mandatory - try: - frappe.db.sql( - """update `tab{doctype}` set naming_series={s} where - ifnull(naming_series, '')=''""".format( - doctype=doctype, s="%s" - ), - get_default_naming_series(doctype), - ) - except NamingSeriesNotSetError: - pass - - if hide_name_field: - make_property_setter(doctype, fieldname, "reqd", 0, "Check", validate_fields_for_doctype=False) - make_property_setter( - doctype, fieldname, "hidden", 1, "Check", validate_fields_for_doctype=False - ) - else: - make_property_setter( - doctype, "naming_series", "reqd", 0, "Check", validate_fields_for_doctype=False - ) - make_property_setter( - doctype, "naming_series", "hidden", 1, "Check", validate_fields_for_doctype=False - ) - - if hide_name_field: - make_property_setter( - doctype, fieldname, "hidden", 0, "Check", validate_fields_for_doctype=False - ) - make_property_setter(doctype, fieldname, "reqd", 1, "Check", validate_fields_for_doctype=False) - - # set values for mandatory - frappe.db.sql( - """update `tab{doctype}` set `{fieldname}`=`name` where - ifnull({fieldname}, '')=''""".format( - doctype=doctype, fieldname=fieldname - ) - ) - - -def get_default_naming_series(doctype): - naming_series = frappe.get_meta(doctype).get_field("naming_series").options or "" - naming_series = naming_series.split("\n") - out = naming_series[0] or (naming_series[1] if len(naming_series) > 1 else None) - - if not out: - frappe.throw( - _("Please set Naming Series for {0} via Setup > Settings > Naming Series").format(doctype), - NamingSeriesNotSetError, - ) - else: - return out diff --git a/erpnext/setup/doctype/naming_series/test_naming_series.py b/erpnext/setup/doctype/naming_series/test_naming_series.py deleted file mode 100644 index fce663e4c55e..000000000000 --- a/erpnext/setup/doctype/naming_series/test_naming_series.py +++ /dev/null @@ -1,35 +0,0 @@ -# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and Contributors -# See license.txt - -import frappe -from frappe.tests.utils import FrappeTestCase - -from erpnext.setup.doctype.naming_series.naming_series import NamingSeries - - -class TestNamingSeries(FrappeTestCase): - def setUp(self): - self.ns: NamingSeries = frappe.get_doc("Naming Series") - - def tearDown(self): - frappe.db.rollback() - - def test_naming_preview(self): - self.ns.select_doc_for_series = "Sales Invoice" - - self.ns.naming_series_to_check = "AXBZ.####" - serieses = self.ns.preview_series().split("\n") - self.assertEqual(["AXBZ0001", "AXBZ0002", "AXBZ0003"], serieses) - - self.ns.naming_series_to_check = "AXBZ-.{currency}.-" - serieses = self.ns.preview_series().split("\n") - - def test_get_transactions(self): - - naming_info = self.ns.get_transactions() - self.assertIn("Sales Invoice", naming_info["transactions"]) - - existing_naming_series = frappe.get_meta("Sales Invoice").get_field("naming_series").options - - for series in existing_naming_series.split("\n"): - self.assertIn(series, naming_info["prefixes"]) diff --git a/erpnext/stock/doctype/stock_settings/stock_settings.py b/erpnext/stock/doctype/stock_settings/stock_settings.py index e592a4be3c6b..50807a96abd9 100644 --- a/erpnext/stock/doctype/stock_settings/stock_settings.py +++ b/erpnext/stock/doctype/stock_settings/stock_settings.py @@ -26,7 +26,7 @@ def validate(self): ]: frappe.db.set_default(key, self.get(key, "")) - from erpnext.setup.doctype.naming_series.naming_series import set_by_naming_series + from erpnext.utilities.naming import set_by_naming_series set_by_naming_series( "Item", diff --git a/erpnext/utilities/naming.py b/erpnext/utilities/naming.py new file mode 100644 index 000000000000..52bbadef1486 --- /dev/null +++ b/erpnext/utilities/naming.py @@ -0,0 +1,60 @@ +import frappe +from frappe.model.naming import get_default_naming_series + + +class NamingSeriesNotSetError(frappe.ValidationError): + pass + + +def set_by_naming_series( + doctype, fieldname, naming_series, hide_name_field=True, make_mandatory=1 +): + """Change a doctype's naming to user naming series""" + from frappe.custom.doctype.property_setter.property_setter import make_property_setter + + if naming_series: + make_property_setter( + doctype, "naming_series", "hidden", 0, "Check", validate_fields_for_doctype=False + ) + make_property_setter( + doctype, "naming_series", "reqd", make_mandatory, "Check", validate_fields_for_doctype=False + ) + + # set values for mandatory + try: + frappe.db.sql( + """update `tab{doctype}` set naming_series={s} where + ifnull(naming_series, '')=''""".format( + doctype=doctype, s="%s" + ), + get_default_naming_series(doctype), + ) + except NamingSeriesNotSetError: + pass + + if hide_name_field: + make_property_setter(doctype, fieldname, "reqd", 0, "Check", validate_fields_for_doctype=False) + make_property_setter( + doctype, fieldname, "hidden", 1, "Check", validate_fields_for_doctype=False + ) + else: + make_property_setter( + doctype, "naming_series", "reqd", 0, "Check", validate_fields_for_doctype=False + ) + make_property_setter( + doctype, "naming_series", "hidden", 1, "Check", validate_fields_for_doctype=False + ) + + if hide_name_field: + make_property_setter( + doctype, fieldname, "hidden", 0, "Check", validate_fields_for_doctype=False + ) + make_property_setter(doctype, fieldname, "reqd", 1, "Check", validate_fields_for_doctype=False) + + # set values for mandatory + frappe.db.sql( + """update `tab{doctype}` set `{fieldname}`=`name` where + ifnull({fieldname}, '')=''""".format( + doctype=doctype, fieldname=fieldname + ) + )