Merge pull request #31141 from ankush/naming_series_live_preview

feat: live preview of naming series on naming series tool
diff --git a/erpnext/setup/doctype/naming_series/naming_series.js b/erpnext/setup/doctype/naming_series/naming_series.js
index 861b2b3..0fb72ab 100644
--- a/erpnext/setup/doctype/naming_series/naming_series.js
+++ b/erpnext/setup/doctype/naming_series/naming_series.js
@@ -54,5 +54,35 @@
 				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
index f936dcf..c65a6f0 100644
--- a/erpnext/setup/doctype/naming_series/naming_series.json
+++ b/erpnext/setup/doctype/naming_series/naming_series.json
@@ -1,360 +1,132 @@
 {
- "allow_copy": 0, 
- "allow_guest_to_view": 0, 
- "allow_import": 0, 
- "allow_rename": 0, 
- "beta": 0, 
- "creation": "2013-01-25 11:35:08", 
- "custom": 0, 
- "description": "Set prefix for numbering series on your transactions", 
- "docstatus": 0, 
- "doctype": "DocType", 
- "editable_grid": 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": [
   {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "description": "Set prefix for numbering series on your transactions", 
-   "fieldname": "setup_series", 
-   "fieldtype": "Section Break", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 0, 
-   "in_standard_filter": 0, 
-   "label": "Setup Series", 
-   "length": 0, 
-   "no_copy": 0, 
-   "permlevel": 0, 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "unique": 0
-  }, 
+   "description": "Set prefix for numbering series on your transactions",
+   "fieldname": "setup_series",
+   "fieldtype": "Section Break",
+   "label": "Setup Series"
+  },
   {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "select_doc_for_series", 
-   "fieldtype": "Select", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 0, 
-   "in_standard_filter": 0, 
-   "label": "Select Transaction", 
-   "length": 0, 
-   "no_copy": 0, 
-   "permlevel": 0, 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "unique": 0
-  }, 
+   "fieldname": "select_doc_for_series",
+   "fieldtype": "Select",
+   "label": "Select Transaction"
+  },
   {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "depends_on": "select_doc_for_series", 
-   "fieldname": "help_html", 
-   "fieldtype": "HTML", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 0, 
-   "in_standard_filter": 0, 
-   "label": "Help HTML", 
-   "length": 0, 
-   "no_copy": 0, 
-   "options": "<div class=\"well\">\nEdit list of Series in the box below. Rules:\n<ul>\n<li>Each Series Prefix on a new line.</li>\n<li>Allowed special characters are \"/\" and \"-\"</li>\n<li>Optionally, set the number of digits in the series using dot (.) followed by hashes (#). For example, \".####\" means that the series will have four digits. Default is five digits.</li>\n</ul>\nExamples:<br>\nINV-<br>\nINV-10-<br>\nINVK-<br>\nINV-.####<br>\n</div>", 
-   "permlevel": 0, 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "unique": 0
-  }, 
+   "depends_on": "select_doc_for_series",
+   "fieldname": "help_html",
+   "fieldtype": "HTML",
+   "label": "Help HTML",
+   "options": "<div class=\"well\">\n    Edit list of Series in the box below. Rules:\n    <ul>\n        <li>Each Series Prefix on a new line.</li>\n        <li>Allowed special characters are \"/\" and \"-\"</li>\n        <li>\n            Optionally, set the number of digits in the series using dot (.)\n            followed by hashes (#). For example, \".####\" means that the series\n            will have four digits. Default is five digits.\n        </li>\n        <li>\n            You can also use variables in the series name by putting them\n            between (.) dots\n            <br>\n            Support Variables:\n            <ul>\n                <li><code>.YYYY.</code> - Year in 4 digits</li>\n                <li><code>.YY.</code> - Year in 2 digits</li>\n                <li><code>.MM.</code> - Month</li>\n                <li><code>.DD.</code> - Day of month</li>\n                <li><code>.WW.</code> - Week of the year</li>\n                <li><code>.FY.</code> - Fiscal Year</li>\n                <li>\n                    <code>.{fieldname}.</code> - fieldname on the document e.g.\n                    <code>branch</code>\n                </li>\n            </ul>\n        </li>\n    </ul>\n    Examples:\n    <ul>\n        <li>INV-</li>\n        <li>INV-10-</li>\n        <li>INVK-</li>\n        <li>INV-.YYYY.-.{branch}.-.MM.-.####</li>\n    </ul>\n</div>\n<br>\n"
+  },
   {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "depends_on": "select_doc_for_series", 
-   "fieldname": "set_options", 
-   "fieldtype": "Text", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 0, 
-   "in_standard_filter": 0, 
-   "label": "Series List for this Transaction", 
-   "length": 0, 
-   "no_copy": 0, 
-   "permlevel": 0, 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "unique": 0
-  }, 
+   "depends_on": "select_doc_for_series",
+   "fieldname": "set_options",
+   "fieldtype": "Text",
+   "label": "Series List for this Transaction"
+  },
   {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 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", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 0, 
-   "in_standard_filter": 0, 
-   "label": "User must always select", 
-   "length": 0, 
-   "no_copy": 0, 
-   "permlevel": 0, 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "unique": 0
-  }, 
+   "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"
+  },
   {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "depends_on": "select_doc_for_series", 
-   "fieldname": "update", 
-   "fieldtype": "Button", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 0, 
-   "in_standard_filter": 0, 
-   "label": "Update", 
-   "length": 0, 
-   "no_copy": 0, 
-   "options": "", 
-   "permlevel": 0, 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "unique": 0
-  }, 
+   "depends_on": "select_doc_for_series",
+   "fieldname": "update",
+   "fieldtype": "Button",
+   "label": "Update"
+  },
   {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "description": "Change the starting / current sequence number of an existing series.", 
-   "fieldname": "update_series", 
-   "fieldtype": "Section Break", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 0, 
-   "in_standard_filter": 0, 
-   "label": "Update Series", 
-   "length": 0, 
-   "no_copy": 0, 
-   "permlevel": 0, 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "unique": 0
-  }, 
+   "description": "Change the starting / current sequence number of an existing series.",
+   "fieldname": "update_series",
+   "fieldtype": "Section Break",
+   "label": "Update Series"
+  },
   {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "prefix", 
-   "fieldtype": "Select", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 0, 
-   "in_standard_filter": 0, 
-   "label": "Prefix", 
-   "length": 0, 
-   "no_copy": 0, 
-   "permlevel": 0, 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "unique": 0
-  }, 
+   "fieldname": "prefix",
+   "fieldtype": "Select",
+   "label": "Prefix"
+  },
   {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "description": "This is the number of the last created transaction with this prefix", 
-   "fieldname": "current_value", 
-   "fieldtype": "Int", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 0, 
-   "in_standard_filter": 0, 
-   "label": "Current Value", 
-   "length": 0, 
-   "no_copy": 0, 
-   "permlevel": 0, 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "unique": 0
-  }, 
+   "description": "This is the number of the last created transaction with this prefix",
+   "fieldname": "current_value",
+   "fieldtype": "Int",
+   "label": "Current Value"
+  },
   {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "update_series_start", 
-   "fieldtype": "Button", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 0, 
-   "in_standard_filter": 0, 
-   "label": "Update Series Number", 
-   "length": 0, 
-   "no_copy": 0, 
-   "options": "update_series_start", 
-   "permlevel": 0, 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "unique": 0
+   "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"
   }
- ], 
- "has_web_view": 0, 
- "hide_heading": 0, 
- "hide_toolbar": 1, 
- "icon": "fa fa-sort-by-order", 
- "idx": 1, 
- "image_view": 0, 
- "in_create": 0, 
- "is_submittable": 0, 
- "issingle": 1, 
- "istable": 0, 
- "max_attachments": 0, 
- "modified": "2017-08-17 03:41:37.685910", 
- "modified_by": "Administrator", 
- "module": "Setup", 
- "name": "Naming Series", 
- "owner": "Administrator", 
+ ],
+ "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": [
   {
-   "amend": 0, 
-   "apply_user_permissions": 0, 
-   "cancel": 0, 
-   "create": 1, 
-   "delete": 0, 
-   "email": 1, 
-   "export": 0, 
-   "if_owner": 0, 
-   "import": 0, 
-   "permlevel": 0, 
-   "print": 1, 
-   "read": 1, 
-   "report": 0, 
-   "role": "System Manager", 
-   "set_user_permissions": 0, 
-   "share": 1, 
-   "submit": 0, 
+   "create": 1,
+   "email": 1,
+   "print": 1,
+   "read": 1,
+   "role": "System Manager",
+   "share": 1,
    "write": 1
   }
- ], 
- "quick_entry": 0, 
- "read_only": 1, 
- "read_only_onload": 0, 
- "show_name_in_global_search": 0, 
- "track_changes": 0, 
- "track_seen": 0
+ ],
+ "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
index 4fba776..eafc264 100644
--- a/erpnext/setup/doctype/naming_series/naming_series.py
+++ b/erpnext/setup/doctype/naming_series/naming_series.py
@@ -6,7 +6,7 @@
 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 parse_naming_series
+from frappe.model.naming import make_autoname, parse_naming_series
 from frappe.permissions import get_doctypes_with_read
 from frappe.utils import cint, cstr
 
@@ -206,6 +206,35 @@
 		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
diff --git a/erpnext/setup/doctype/naming_series/test_naming_series.py b/erpnext/setup/doctype/naming_series/test_naming_series.py
new file mode 100644
index 0000000..fce663e
--- /dev/null
+++ b/erpnext/setup/doctype/naming_series/test_naming_series.py
@@ -0,0 +1,35 @@
+# 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"])