Merge pull request #30419 from nextchamp-saqib/fix-item-tax-template-patch

fix: move item tax to item tax template patch
diff --git a/erpnext/accounts/report/cash_flow/custom_cash_flow.py b/erpnext/accounts/report/cash_flow/custom_cash_flow.py
index 20f7fcf..e81e0d7 100644
--- a/erpnext/accounts/report/cash_flow/custom_cash_flow.py
+++ b/erpnext/accounts/report/cash_flow/custom_cash_flow.py
@@ -5,7 +5,7 @@
 import frappe
 from frappe import _
 from frappe.query_builder.functions import Sum
-from frappe.utils import add_to_date, get_date_str
+from frappe.utils import add_to_date, flt, get_date_str
 
 from erpnext.accounts.report.financial_statements import get_columns, get_data, get_period_list
 from erpnext.accounts.report.profit_and_loss_statement.profit_and_loss_statement import (
@@ -442,8 +442,8 @@
 		else:
 			gl_sum = 0
 
-		total += gl_sum
-		data.setdefault(period["key"], gl_sum)
+		total += flt(gl_sum)
+		data.setdefault(period["key"], flt(gl_sum))
 
 	data["total"] = total
 	return data
diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js
index a8cec0a..23c2bd4 100644
--- a/erpnext/public/js/controllers/transaction.js
+++ b/erpnext/public/js/controllers/transaction.js
@@ -345,112 +345,8 @@
 	}
 
 	scan_barcode() {
-		let me = this;
-
-		if(this.frm.doc.scan_barcode) {
-			frappe.call({
-				method: "erpnext.selling.page.point_of_sale.point_of_sale.search_for_serial_or_batch_or_barcode_number",
-				args: {
-					search_value: this.frm.doc.scan_barcode
-				}
-			}).then(r => {
-				const data = r && r.message;
-				if (!data || Object.keys(data).length === 0) {
-					frappe.show_alert({
-						message: __('Cannot find Item with this Barcode'),
-						indicator: 'red'
-					});
-					return;
-				}
-
-				me.modify_table_after_scan(data);
-			});
-		}
-		return false;
-	}
-
-	modify_table_after_scan(data) {
-		let scan_barcode_field = this.frm.fields_dict["scan_barcode"];
-		let cur_grid = this.frm.fields_dict.items.grid;
-		let row_to_modify = null;
-
-		// Check if batch is scanned and table has batch no field
-		let batch_no_scan = Boolean(data.batch_no) && frappe.meta.has_field(cur_grid.doctype, "batch_no");
-
-		if (batch_no_scan) {
-			row_to_modify = this.get_batch_row_to_modify(data.batch_no);
-		} else {
-			// serial or barcode scan
-			row_to_modify = this.get_row_to_modify_on_scan(row_to_modify, data);
-		}
-
-		if (!row_to_modify) {
-			// add new row if new item/batch is scanned
-			row_to_modify = frappe.model.add_child(this.frm.doc, cur_grid.doctype, 'items');
-		}
-
-		this.show_scan_message(row_to_modify.idx, row_to_modify.item_code);
-		this.set_scanned_values(row_to_modify, data, scan_barcode_field);
-	}
-
-	set_scanned_values(row_to_modify, data, scan_barcode_field) {
-		// increase qty and set scanned value and item in row
-		this.frm.from_barcode = this.frm.from_barcode ? this.frm.from_barcode + 1 : 1;
-		frappe.model.set_value(row_to_modify.doctype, row_to_modify.name, {
-			item_code: data.item_code,
-			qty: (row_to_modify.qty || 0) + 1
-		});
-
-		['serial_no', 'batch_no', 'barcode'].forEach(field => {
-			if (data[field] && frappe.meta.has_field(row_to_modify.doctype, field)) {
-				let is_serial_no = row_to_modify[field] && field === "serial_no";
-				let value = data[field];
-
-				if (is_serial_no) {
-					value = row_to_modify[field] + '\n' + data[field];
-				}
-
-				frappe.model.set_value(row_to_modify.doctype, row_to_modify.name, field, value);
-			}
-		});
-
-		scan_barcode_field.set_value('');
-		refresh_field("items");
-	}
-
-	get_row_to_modify_on_scan(row_to_modify, data) {
-		// get an existing item row to increment or blank row to modify
-		const existing_item_row = this.frm.doc.items.find(d => d.item_code === data.item_code);
-		const blank_item_row = this.frm.doc.items.find(d => !d.item_code);
-
-		if (existing_item_row) {
-			row_to_modify = existing_item_row;
-		} else if (blank_item_row) {
-			row_to_modify = blank_item_row;
-		}
-
-		return row_to_modify;
-	}
-
-	get_batch_row_to_modify(batch_no) {
-		// get row if batch already exists in table
-		const existing_batch_row = this.frm.doc.items.find(d => d.batch_no === batch_no);
-		return existing_batch_row || null;
-	}
-
-	show_scan_message (idx, exist = null) {
-		// show new row or qty increase toast
-		if (exist) {
-			frappe.show_alert({
-				message: __('Row #{0}: Qty increased by 1', [idx]),
-				indicator: 'green'
-			});
-		} else {
-			frappe.show_alert({
-				message: __('Row #{0}: Item added', [idx]),
-				indicator: 'green'
-			});
-		}
+		const barcode_scanner = new erpnext.utils.BarcodeScanner({frm:this.frm});
+		barcode_scanner.process_scan();
 	}
 
 	apply_default_taxes() {
@@ -508,13 +404,13 @@
 	}
 
 	barcode(doc, cdt, cdn) {
-		var d = locals[cdt][cdn];
-		if(d.barcode=="" || d.barcode==null) {
+		const d = locals[cdt][cdn];
+		if (!d.barcode) {
 			// barcode cleared, remove item
 			d.item_code = "";
 		}
-
-		this.frm.from_barcode = this.frm.from_barcode ? this.frm.from_barcode + 1 : 1;
+		// flag required for circular triggers
+		d._triggerd_from_barcode = true;
 		this.item_code(doc, cdt, cdn);
 	}
 
@@ -535,11 +431,9 @@
 			this.frm.doc.doctype === 'Delivery Note') {
 			show_batch_dialog = 1;
 		}
-		// clear barcode if setting item (else barcode will take priority)
-		if (this.frm.from_barcode == 0) {
+		if (!item._triggerd_from_barcode) {
 			item.barcode = null;
 		}
-		this.frm.from_barcode = this.frm.from_barcode - 1 >= 0 ? this.frm.from_barcode - 1 : 0;
 
 
 		if(item.item_code || item.barcode || item.serial_no) {
diff --git a/erpnext/public/js/erpnext.bundle.js b/erpnext/public/js/erpnext.bundle.js
index 8409e78..3baf667 100644
--- a/erpnext/public/js/erpnext.bundle.js
+++ b/erpnext/public/js/erpnext.bundle.js
@@ -19,6 +19,7 @@
 import "./education/assessment_result_tool.html";
 import "./call_popup/call_popup";
 import "./utils/dimension_tree_filter";
+import "./utils/barcode_scanner";
 import "./telephony";
 import "./templates/call_link.html";
 import "./bulk_transaction_processing";
diff --git a/erpnext/public/js/utils/barcode_scanner.js b/erpnext/public/js/utils/barcode_scanner.js
new file mode 100644
index 0000000..abea5fc
--- /dev/null
+++ b/erpnext/public/js/utils/barcode_scanner.js
@@ -0,0 +1,187 @@
+erpnext.utils.BarcodeScanner = class BarcodeScanner {
+	constructor(opts) {
+		this.frm = opts.frm;
+
+		// field from which to capture input of scanned data
+		this.scan_field_name = opts.scan_field_name || "scan_barcode";
+		this.scan_barcode_field = this.frm.fields_dict[this.scan_field_name];
+
+		this.barcode_field = opts.barcode_field || "barcode";
+		this.serial_no_field = opts.serial_no_field || "serial_no";
+		this.batch_no_field = opts.batch_no_field || "batch_no";
+		this.qty_field = opts.qty_field || "qty";
+
+		this.items_table_name = opts.items_table_name || "items";
+		this.items_table = this.frm.doc[this.items_table_name];
+
+		// any API that takes `search_value` as input and returns dictionary as follows
+		// {
+		//     item_code: "HORSESHOE", // present if any item was found
+		//     bar_code: "123456", // present if barcode was scanned
+		//     batch_no: "LOT12", // present if batch was scanned
+		//     serial_no: "987XYZ", // present if serial no was scanned
+		// }
+		this.scan_api =
+			opts.scan_api ||
+			"erpnext.selling.page.point_of_sale.point_of_sale.search_for_serial_or_batch_or_barcode_number";
+	}
+
+	process_scan() {
+		let me = this;
+
+		const input = this.scan_barcode_field.value;
+		if (!input) {
+			return;
+		}
+
+		frappe
+			.call({
+				method: this.scan_api,
+				args: {
+					search_value: input,
+				},
+			})
+			.then((r) => {
+				const data = r && r.message;
+				if (!data || Object.keys(data).length === 0) {
+					frappe.show_alert({
+						message: __("Cannot find Item with this Barcode"),
+						indicator: "red",
+					});
+					this.clean_up();
+					return;
+				}
+
+				me.update_table(data.item_code, data.barcode, data.batch_no, data.serial_no);
+			});
+	}
+
+	update_table(item_code, barcode, batch_no, serial_no) {
+		let cur_grid = this.frm.fields_dict[this.items_table_name].grid;
+		let row = null;
+
+		// Check if batch is scanned and table has batch no field
+		let batch_no_scan =
+			Boolean(batch_no) && frappe.meta.has_field(cur_grid.doctype, this.batch_no_field);
+
+		if (batch_no_scan) {
+			row = this.get_batch_row_to_modify(batch_no);
+		} else {
+			// serial or barcode scan
+			row = this.get_row_to_modify_on_scan(row, item_code);
+		}
+
+		if (!row) {
+			// add new row if new item/batch is scanned
+			row = frappe.model.add_child(this.frm.doc, cur_grid.doctype, this.items_table_name);
+			// trigger any row add triggers defined on child table.
+			this.frm.script_manager.trigger(`${this.items_table_name}_add`, row.doctype, row.name);
+		}
+
+		if (this.is_duplicate_serial_no(row, serial_no)) {
+			this.clean_up();
+			return;
+		}
+
+		this.show_scan_message(row.idx, row.item_code);
+		this.set_item(row, item_code);
+		this.set_serial_no(row, serial_no);
+		this.set_batch_no(row, batch_no);
+		this.set_barcode(row, barcode);
+		this.clean_up();
+	}
+
+	set_item(row, item_code) {
+		const item_data = { item_code: item_code };
+		item_data[this.qty_field] = (row[this.qty_field] || 0) + 1;
+		frappe.model.set_value(row.doctype, row.name, item_data);
+	}
+
+	set_serial_no(row, serial_no) {
+		if (serial_no && frappe.meta.has_field(row.doctype, this.serial_no_field)) {
+			const existing_serial_nos = row[this.serial_no_field];
+			let new_serial_nos = "";
+
+			if (!!existing_serial_nos) {
+				new_serial_nos = existing_serial_nos + "\n" + serial_no;
+			} else {
+				new_serial_nos = serial_no;
+			}
+			frappe.model.set_value(row.doctype, row.name, this.serial_no_field, new_serial_nos);
+		}
+	}
+
+	set_batch_no(row, batch_no) {
+		if (batch_no && frappe.meta.has_field(row.doctype, this.batch_no_field)) {
+			frappe.model.set_value(row.doctype, row.name, this.batch_no_field, batch_no);
+		}
+	}
+
+	set_barcode(row, barcode) {
+		if (barcode && frappe.meta.has_field(row.doctype, this.barcode_field)) {
+			frappe.model.set_value(row.doctype, row.name, this.barcode_field, barcode);
+		}
+	}
+
+	show_scan_message(idx, exist = null) {
+		// show new row or qty increase toast
+		if (exist) {
+			frappe.show_alert(
+				{
+					message: __("Row #{0}: Qty increased by 1", [idx]),
+					indicator: "green",
+				},
+				5
+			);
+		} else {
+			frappe.show_alert(
+				{
+					message: __("Row #{0}: Item added", [idx]),
+					indicator: "green",
+				},
+				5
+			);
+		}
+	}
+
+	is_duplicate_serial_no(row, serial_no) {
+		const is_duplicate = !!serial_no && !!row[this.serial_no_field]
+			&& row[this.serial_no_field].includes(serial_no);
+
+		if (is_duplicate) {
+			frappe.show_alert(
+				{
+					message: __("Serial No {0} is already added", [serial_no]),
+					indicator: "orange",
+				},
+				5
+			);
+		}
+		return is_duplicate;
+	}
+
+	get_batch_row_to_modify(batch_no) {
+		// get row if batch already exists in table
+		const existing_batch_row = this.items_table.find((d) => d.batch_no === batch_no);
+		return existing_batch_row || null;
+	}
+
+	get_row_to_modify_on_scan(row_to_modify, item_code) {
+		// get an existing item row to increment or blank row to modify
+		const existing_item_row = this.items_table.find((d) => d.item_code === item_code);
+		const blank_item_row = this.items_table.find((d) => !d.item_code);
+
+		if (existing_item_row) {
+			row_to_modify = existing_item_row;
+		} else if (blank_item_row) {
+			row_to_modify = blank_item_row;
+		}
+
+		return row_to_modify;
+	}
+
+	clean_up() {
+		this.scan_barcode_field.set_value("");
+		refresh_field(this.items_table_name);
+	}
+};
diff --git a/erpnext/setup/utils.py b/erpnext/setup/utils.py
index 6db1961..01884d9 100644
--- a/erpnext/setup/utils.py
+++ b/erpnext/setup/utils.py
@@ -1,7 +1,6 @@
 # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
 # License: GNU General Public License v3. See license.txt
 
-
 import frappe
 from frappe import _
 from frappe.utils import add_days, flt, get_datetime_str, nowdate
@@ -15,8 +14,9 @@
 	frappe.clear_cache()
 	# complete setup if missing
 	from frappe.desk.page.setup_wizard.setup_wizard import setup_complete
-	current_year = now_datetime().year
-	if not frappe.get_list("Company"):
+
+	if not frappe.db.a_row_exists("Company"):
+		current_year = now_datetime().year
 		setup_complete({
 			"currency"          :"USD",
 			"full_name"         :"Test User",
@@ -34,13 +34,15 @@
 			"chart_of_accounts" : "Standard",
 			"domains"           : ["Manufacturing"],
 		})
+		_enable_all_domains()
 
 	frappe.db.sql("delete from `tabLeave Allocation`")
 	frappe.db.sql("delete from `tabLeave Application`")
 	frappe.db.sql("delete from `tabSalary Slip`")
 	frappe.db.sql("delete from `tabItem Price`")
 
-	enable_all_roles_and_domains()
+	_enable_all_roles_for_admin()
+
 	set_defaults_for_tests()
 
 	frappe.db.commit()
@@ -119,38 +121,43 @@
 
 def enable_all_roles_and_domains():
 	""" enable all roles and domain for testing """
-	# add all roles to users
-	domains = frappe.get_all("Domain")
+	_enable_all_domains()
+	_enable_all_roles_for_admin()
+
+
+def _enable_all_domains():
+	domains = frappe.get_all("Domain", pluck="name")
 	if not domains:
 		return
+	frappe.get_single('Domain Settings').set_active_domains(domains)
 
+
+def _enable_all_roles_for_admin():
 	from frappe.desk.page.setup_wizard.setup_wizard import add_all_roles_to
-	frappe.get_single('Domain Settings').set_active_domains(\
-		[d.name for d in domains])
-	add_all_roles_to('Administrator')
+
+	all_roles = set(frappe.db.get_values("Role", pluck="name"))
+	admin_roles = set(frappe.db.get_values("Has Role",
+		{"parent": "Administrator"}, fieldname="role", pluck="role"))
+
+	if all_roles.difference(admin_roles):
+		add_all_roles_to('Administrator')
+
 
 def set_defaults_for_tests():
-	selling_settings = frappe.get_single("Selling Settings")
-	selling_settings.customer_group = get_root_of("Customer Group")
-	selling_settings.territory = get_root_of("Territory")
-	selling_settings.save()
-
+	defaults = {
+		"customer_group": get_root_of("Customer Group"),
+		"territory": get_root_of("Territory"),
+	}
+	frappe.db.set_single_value("Selling Settings", defaults)
+	for key, value in defaults.items():
+			frappe.db.set_default(key, value)
 	frappe.db.set_single_value("Stock Settings", "auto_insert_price_list_rate_if_missing", 0)
 
 
 def insert_record(records):
-	for r in records:
-		doc = frappe.new_doc(r.get("doctype"))
-		doc.update(r)
-		try:
-			doc.insert(ignore_permissions=True, ignore_if_duplicate=True)
-		except frappe.DuplicateEntryError as e:
-			# pass DuplicateEntryError and continue
-			if e.args and e.args[0]==doc.doctype and e.args[1]==doc.name:
-				# make sure DuplicateEntryError is for the exact same doc and not a related doc
-				pass
-			else:
-				raise
+	from frappe.desk.page.setup_wizard.setup_wizard import make_records
+
+	make_records(records)
 
 def welcome_email():
 	site_name = get_default_company() or "ERPNext"
diff --git a/erpnext/stock/doctype/pick_list/pick_list.py b/erpnext/stock/doctype/pick_list/pick_list.py
index 3a49686..35cbc2f 100644
--- a/erpnext/stock/doctype/pick_list/pick_list.py
+++ b/erpnext/stock/doctype/pick_list/pick_list.py
@@ -54,9 +54,9 @@
 
 	def before_cancel(self):
 		#update picked_qty in SO Item on cancel of PL
-		for location in self.get('locations'):
-			if location.sales_order_item:
-				self.update_so(location.sales_order_item,0,location.item_code)
+		for item in self.get('locations'):
+			if item.sales_order_item:
+				self.update_so(item.sales_order_item, -1 * item.picked_qty, item.item_code)
 
 	def update_so(self,so_item,picked_qty,item_code):
 		so_doc = frappe.get_doc("Sales Order",frappe.db.get_value("Sales Order Item",so_item,"parent"))
@@ -637,4 +637,4 @@
 	item.material_request = location.material_request
 	item.serial_no = location.serial_no
 	item.batch_no = location.batch_no
-	item.material_request_item = location.material_request_item
\ No newline at end of file
+	item.material_request_item = location.material_request_item
diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.js b/erpnext/stock/doctype/stock_entry/stock_entry.js
index 324ca7a..1aafcee 100644
--- a/erpnext/stock/doctype/stock_entry/stock_entry.js
+++ b/erpnext/stock/doctype/stock_entry/stock_entry.js
@@ -845,8 +845,8 @@
 	}
 
 	scan_barcode() {
-		let transaction_controller= new erpnext.TransactionController({frm:this.frm});
-		transaction_controller.scan_barcode();
+		const barcode_scanner = new erpnext.utils.BarcodeScanner({frm:this.frm});
+		barcode_scanner.process_scan();
 	}
 
 	on_submit() {