Merge pull request #34116 from prateekkaramchandani/develop

fix: use max function to get default company address
diff --git a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.html b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.html
index 3920d4c..b9680df 100644
--- a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.html
+++ b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.html
@@ -15,7 +15,7 @@
 	</div>
 	<h2 class="text-center">{{ _("STATEMENTS OF ACCOUNTS") }}</h2>
 	<div>
-		<h5 style="float: left;">{{ _("Customer: ") }} <b>{{filters.party[0] }}</b></h5>
+		<h5 style="float: left;">{{ _("Customer: ") }} <b>{{filters.party_name[0] }}</b></h5>
 		<h5 style="float: right;">
 			{{ _("Date: ") }}
 			<b>{{ frappe.format(filters.from_date, 'Date')}}
diff --git a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py
index a48c027..a482931 100644
--- a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py
+++ b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py
@@ -24,7 +24,7 @@
 class ProcessStatementOfAccounts(Document):
 	def validate(self):
 		if not self.subject:
-			self.subject = "Statement Of Accounts for {{ customer.name }}"
+			self.subject = "Statement Of Accounts for {{ customer.customer_name }}"
 		if not self.body:
 			self.body = "Hello {{ customer.name }},<br>PFA your Statement Of Accounts from {{ doc.from_date }} to {{ doc.to_date }}."
 
@@ -87,6 +87,7 @@
 				"account": [doc.account] if doc.account else None,
 				"party_type": "Customer",
 				"party": [entry.customer],
+				"party_name": [entry.customer_name] if entry.customer_name else None,
 				"presentation_currency": presentation_currency,
 				"group_by": doc.group_by,
 				"currency": doc.currency,
@@ -156,7 +157,7 @@
 	]
 	return frappe.get_list(
 		"Customer",
-		fields=["name", "email_id"],
+		fields=["name", "customer_name", "email_id"],
 		filters=[[fields_dict[customer_collection], "IN", selected]],
 	)
 
@@ -179,7 +180,7 @@
 	if sales_person_records.get("Customer"):
 		return frappe.get_list(
 			"Customer",
-			fields=["name", "email_id"],
+			fields=["name", "customer_name", "email_id"],
 			filters=[["name", "in", list(sales_person_records["Customer"])]],
 		)
 	else:
@@ -228,7 +229,7 @@
 		if customer_collection == "Sales Partner":
 			customers = frappe.get_list(
 				"Customer",
-				fields=["name", "email_id"],
+				fields=["name", "customer_name", "email_id"],
 				filters=[["default_sales_partner", "=", collection_name]],
 			)
 		else:
@@ -245,7 +246,12 @@
 				continue
 
 		customer_list.append(
-			{"name": customer.name, "primary_email": primary_email, "billing_email": billing_email}
+			{
+				"name": customer.name,
+				"customer_name": customer.customer_name,
+				"primary_email": primary_email,
+				"billing_email": billing_email,
+			}
 		)
 	return customer_list
 
diff --git a/erpnext/accounts/doctype/process_statement_of_accounts_customer/process_statement_of_accounts_customer.json b/erpnext/accounts/doctype/process_statement_of_accounts_customer/process_statement_of_accounts_customer.json
index dd04dc1..8bffd6a 100644
--- a/erpnext/accounts/doctype/process_statement_of_accounts_customer/process_statement_of_accounts_customer.json
+++ b/erpnext/accounts/doctype/process_statement_of_accounts_customer/process_statement_of_accounts_customer.json
@@ -1,12 +1,12 @@
 {
  "actions": [],
- "allow_workflow": 1,
  "creation": "2020-08-03 16:35:21.852178",
  "doctype": "DocType",
  "editable_grid": 1,
  "engine": "InnoDB",
  "field_order": [
   "customer",
+  "customer_name",
   "billing_email",
   "primary_email"
  ],
@@ -30,11 +30,18 @@
    "fieldtype": "Read Only",
    "in_list_view": 1,
    "label": "Billing Email"
+  },
+  {
+   "fetch_from": "customer.customer_name",
+   "fieldname": "customer_name",
+   "fieldtype": "Data",
+   "label": "Customer Name",
+   "read_only": 1
   }
  ],
  "istable": 1,
  "links": [],
- "modified": "2020-08-03 22:55:38.875601",
+ "modified": "2023-03-13 00:12:34.508086",
  "modified_by": "Administrator",
  "module": "Accounts",
  "name": "Process Statement Of Accounts Customer",
@@ -43,5 +50,6 @@
  "quick_entry": 1,
  "sort_field": "modified",
  "sort_order": "DESC",
+ "states": [],
  "track_changes": 1
 }
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json
index 2f4e45e..2a8ff40 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json
@@ -32,9 +32,6 @@
   "cost_center",
   "dimension_col_break",
   "project",
-  "column_break_27",
-  "campaign",
-  "source",
   "currency_and_price_list",
   "currency",
   "conversion_rate",
@@ -203,7 +200,9 @@
   "more_information",
   "status",
   "inter_company_invoice_reference",
+  "campaign",
   "represents_company",
+  "source",
   "customer_group",
   "col_break23",
   "is_internal_customer",
@@ -2084,10 +2083,6 @@
    "fieldtype": "Column Break"
   },
   {
-   "fieldname": "column_break_27",
-   "fieldtype": "Column Break"
-  },
-  {
    "fieldname": "column_break_52",
    "fieldtype": "Column Break"
   },
@@ -2143,11 +2138,10 @@
    "link_fieldname": "consolidated_invoice"
   }
  ],
- "modified": "2023-01-28 19:45:47.538163",
+ "modified": "2023-03-13 11:43:15.883055",
  "modified_by": "Administrator",
  "module": "Accounts",
  "name": "Sales Invoice",
- "name_case": "Title Case",
  "naming_rule": "By \"Naming Series\" field",
  "owner": "Administrator",
  "permissions": [
diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
index 0ffd946..6051c99 100644
--- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
+++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
@@ -266,16 +266,16 @@
 			"_Test Account Education Cess - _TC": [3, 1618, 0.06, 32.36],
 			"_Test Account S&H Education Cess - _TC": [1.5, 1619.5, 0.03, 32.39],
 			"_Test Account CST - _TC": [32.5, 1652, 0.65, 33.04],
-			"_Test Account VAT - _TC": [156.5, 1808.5, 3.13, 36.17],
-			"_Test Account Discount - _TC": [-181.0, 1627.5, -3.62, 32.55],
+			"_Test Account VAT - _TC": [156.0, 1808.0, 3.12, 36.16],
+			"_Test Account Discount - _TC": [-181.0, 1627.0, -3.62, 32.54],
 		}
 
 		for d in si.get("taxes"):
 			for i, k in enumerate(expected_values["keys"]):
 				self.assertEqual(d.get(k), expected_values[d.account_head][i])
 
-		self.assertEqual(si.base_grand_total, 1627.5)
-		self.assertEqual(si.grand_total, 32.55)
+		self.assertEqual(si.base_grand_total, 1627.0)
+		self.assertEqual(si.grand_total, 32.54)
 
 	def test_sales_invoice_with_discount_and_inclusive_tax(self):
 		si = create_sales_invoice(qty=100, rate=50, do_not_save=True)
@@ -401,10 +401,10 @@
 			"_Test Account S&H Education Cess - _TC": [1.4, 1.30, 1297.67],
 			"_Test Account CST - _TC": [27.88, 25.95, 1323.62],
 			"_Test Account VAT - _TC": [156.25, 145.43, 1469.05],
-			"_Test Account Customs Duty - _TC": [125, 116.35, 1585.40],
-			"_Test Account Shipping Charges - _TC": [100, 100, 1685.40],
-			"_Test Account Discount - _TC": [-180.33, -168.54, 1516.86],
-			"_Test Account Service Tax - _TC": [-18.03, -16.85, 1500.01],
+			"_Test Account Customs Duty - _TC": [125, 116.34, 1585.39],
+			"_Test Account Shipping Charges - _TC": [100, 100, 1685.39],
+			"_Test Account Discount - _TC": [-180.33, -168.54, 1516.85],
+			"_Test Account Service Tax - _TC": [-18.03, -16.85, 1500.00],
 		}
 
 		for d in si.get("taxes"):
@@ -413,7 +413,7 @@
 
 		self.assertEqual(si.base_grand_total, 1500)
 		self.assertEqual(si.grand_total, 1500)
-		self.assertEqual(si.rounding_adjustment, -0.01)
+		self.assertEqual(si.rounding_adjustment, 0.0)
 
 	def test_discount_amount_gl_entry(self):
 		frappe.db.set_value("Company", "_Test Company", "round_off_account", "Round Off - _TC")
@@ -454,7 +454,7 @@
 				[test_records[3]["taxes"][2]["account_head"], 0.0, 1.30],
 				[test_records[3]["taxes"][3]["account_head"], 0.0, 25.95],
 				[test_records[3]["taxes"][4]["account_head"], 0.0, 145.43],
-				[test_records[3]["taxes"][5]["account_head"], 0.0, 116.35],
+				[test_records[3]["taxes"][5]["account_head"], 0.0, 116.34],
 				[test_records[3]["taxes"][6]["account_head"], 0.0, 100],
 				[test_records[3]["taxes"][7]["account_head"], 168.54, 0.0],
 				["_Test Account Service Tax - _TC", 16.85, 0.0],
@@ -1614,7 +1614,7 @@
 			"_Test Account Education Cess - _TC": [1.4, 1.4, 1.4],
 			"_Test Account S&H Education Cess - _TC": [0.7, 0.7, 0.7],
 			"_Test Account CST - _TC": [17.19, 17.19, 17.19],
-			"_Test Account VAT - _TC": [78.13, 78.13, 78.13],
+			"_Test Account VAT - _TC": [78.12, 78.12, 78.12],
 			"_Test Account Discount - _TC": [-95.49, -95.49, -95.49],
 		}
 
@@ -1623,9 +1623,9 @@
 				if expected_values.get(d.account_head):
 					self.assertEqual(d.get(k), expected_values[d.account_head][i])
 
-		self.assertEqual(si.total_taxes_and_charges, 234.43)
-		self.assertEqual(si.base_grand_total, 859.43)
-		self.assertEqual(si.grand_total, 859.43)
+		self.assertEqual(si.total_taxes_and_charges, 234.42)
+		self.assertEqual(si.base_grand_total, 859.42)
+		self.assertEqual(si.grand_total, 859.42)
 
 	def test_multi_currency_gle(self):
 		si = create_sales_invoice(
@@ -1985,17 +1985,17 @@
 			)
 		si.save()
 		si.submit()
-		self.assertEqual(si.net_total, 19453.13)
+		self.assertEqual(si.net_total, 19453.12)
 		self.assertEqual(si.grand_total, 24900)
 		self.assertEqual(si.total_taxes_and_charges, 5446.88)
-		self.assertEqual(si.rounding_adjustment, -0.01)
+		self.assertEqual(si.rounding_adjustment, 0.0)
 
 		expected_values = dict(
 			(d[0], d)
 			for d in [
 				[si.debit_to, 24900, 0.0],
 				["_Test Account Service Tax - _TC", 0.0, 5446.88],
-				["Sales - _TC", 0.0, 19453.13],
+				["Sales - _TC", 0.0, 19453.12],
 				["Round Off - _TC", 0.01, 0.0],
 			]
 		)
diff --git a/erpnext/accounts/party.py b/erpnext/accounts/party.py
index 01cfb58..b217f00 100644
--- a/erpnext/accounts/party.py
+++ b/erpnext/accounts/party.py
@@ -32,6 +32,16 @@
 from erpnext.accounts.utils import get_fiscal_year
 from erpnext.exceptions import InvalidAccountCurrency, PartyDisabled, PartyFrozen
 
+PURCHASE_TRANSACTION_TYPES = {"Purchase Order", "Purchase Receipt", "Purchase Invoice"}
+SALES_TRANSACTION_TYPES = {
+	"Quotation",
+	"Sales Order",
+	"Delivery Note",
+	"Sales Invoice",
+	"POS Invoice",
+}
+TRANSACTION_TYPES = PURCHASE_TRANSACTION_TYPES | SALES_TRANSACTION_TYPES
+
 
 class DuplicatePartyAccountError(frappe.ValidationError):
 	pass
@@ -124,12 +134,6 @@
 	set_other_values(party_details, party, party_type)
 	set_price_list(party_details, party, party_type, price_list, pos_profile)
 
-	party_details["tax_category"] = get_address_tax_category(
-		party.get("tax_category"),
-		party_address,
-		shipping_address if party_type != "Supplier" else party_address,
-	)
-
 	tax_template = set_taxes(
 		party.name,
 		party_type,
@@ -211,20 +215,10 @@
 	else:
 		party_details.update(get_company_address(company))
 
-	if doctype and doctype in [
-		"Delivery Note",
-		"Sales Invoice",
-		"Sales Order",
-		"Quotation",
-		"POS Invoice",
-	]:
-		if party_details.company_address:
-			party_details.update(
-				get_fetch_values(doctype, "company_address", party_details.company_address)
-			)
-		get_regional_address_details(party_details, doctype, company)
+	if doctype in SALES_TRANSACTION_TYPES and party_details.company_address:
+		party_details.update(get_fetch_values(doctype, "company_address", party_details.company_address))
 
-	elif doctype and doctype in ["Purchase Invoice", "Purchase Order", "Purchase Receipt"]:
+	if doctype in PURCHASE_TRANSACTION_TYPES:
 		if shipping_address:
 			party_details.update(
 				shipping_address=shipping_address,
@@ -250,9 +244,21 @@
 					**get_fetch_values(doctype, "shipping_address", party_details.billing_address)
 				)
 
+	party_address, shipping_address = (
+		party_details.get(billing_address_field),
+		party_details.shipping_address_name,
+	)
+
+	party_details["tax_category"] = get_address_tax_category(
+		party.get("tax_category"),
+		party_address,
+		shipping_address if party_type != "Supplier" else party_address,
+	)
+
+	if doctype in TRANSACTION_TYPES:
 		get_regional_address_details(party_details, doctype, company)
 
-	return party_details.get(billing_address_field), party_details.shipping_address_name
+	return party_address, shipping_address
 
 
 @erpnext.allow_regional
diff --git a/erpnext/accounts/report/trial_balance/trial_balance.py b/erpnext/accounts/report/trial_balance/trial_balance.py
index 3af01fd..bc334c7 100644
--- a/erpnext/accounts/report/trial_balance/trial_balance.py
+++ b/erpnext/accounts/report/trial_balance/trial_balance.py
@@ -78,7 +78,6 @@
 
 
 def get_data(filters):
-
 	accounts = frappe.db.sql(
 		"""select name, account_number, parent_account, account_name, root_type, report_type, lft, rgt
 
@@ -118,12 +117,10 @@
 		ignore_closing_entries=not flt(filters.with_period_closing_entry),
 	)
 
-	total_row = calculate_values(
-		accounts, gl_entries_by_account, opening_balances, filters, company_currency
-	)
+	calculate_values(accounts, gl_entries_by_account, opening_balances)
 	accumulate_values_into_parents(accounts, accounts_by_name)
 
-	data = prepare_data(accounts, filters, total_row, parent_children_map, company_currency)
+	data = prepare_data(accounts, filters, parent_children_map, company_currency)
 	data = filter_out_zero_value_rows(
 		data, parent_children_map, show_zero_values=filters.get("show_zero_values")
 	)
@@ -218,7 +215,7 @@
 	return opening
 
 
-def calculate_values(accounts, gl_entries_by_account, opening_balances, filters, company_currency):
+def calculate_values(accounts, gl_entries_by_account, opening_balances):
 	init = {
 		"opening_debit": 0.0,
 		"opening_credit": 0.0,
@@ -228,22 +225,6 @@
 		"closing_credit": 0.0,
 	}
 
-	total_row = {
-		"account": "'" + _("Total") + "'",
-		"account_name": "'" + _("Total") + "'",
-		"warn_if_negative": True,
-		"opening_debit": 0.0,
-		"opening_credit": 0.0,
-		"debit": 0.0,
-		"credit": 0.0,
-		"closing_debit": 0.0,
-		"closing_credit": 0.0,
-		"parent_account": None,
-		"indent": 0,
-		"has_value": True,
-		"currency": company_currency,
-	}
-
 	for d in accounts:
 		d.update(init.copy())
 
@@ -261,8 +242,28 @@
 
 		prepare_opening_closing(d)
 
-		for field in value_fields:
-			total_row[field] += d[field]
+
+def calculate_total_row(accounts, company_currency):
+	total_row = {
+		"account": "'" + _("Total") + "'",
+		"account_name": "'" + _("Total") + "'",
+		"warn_if_negative": True,
+		"opening_debit": 0.0,
+		"opening_credit": 0.0,
+		"debit": 0.0,
+		"credit": 0.0,
+		"closing_debit": 0.0,
+		"closing_credit": 0.0,
+		"parent_account": None,
+		"indent": 0,
+		"has_value": True,
+		"currency": company_currency,
+	}
+
+	for d in accounts:
+		if not d.parent_account:
+			for field in value_fields:
+				total_row[field] += d[field]
 
 	return total_row
 
@@ -274,7 +275,7 @@
 				accounts_by_name[d.parent_account][key] += d[key]
 
 
-def prepare_data(accounts, filters, total_row, parent_children_map, company_currency):
+def prepare_data(accounts, filters, parent_children_map, company_currency):
 	data = []
 
 	for d in accounts:
@@ -305,6 +306,7 @@
 		row["has_value"] = has_value
 		data.append(row)
 
+	total_row = calculate_total_row(accounts, company_currency)
 	data.extend([{}, total_row])
 
 	return data
diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py
index 8ab79e6..619a415 100644
--- a/erpnext/manufacturing/doctype/bom/bom.py
+++ b/erpnext/manufacturing/doctype/bom/bom.py
@@ -31,7 +31,7 @@
 
 	# specifying the attributes to save resources
 	# ref: https://docs.python.org/3/reference/datamodel.html#slots
-	__slots__ = ["name", "child_items", "is_bom", "item_code", "exploded_qty", "qty"]
+	__slots__ = ["name", "child_items", "is_bom", "item_code", "qty", "exploded_qty", "bom_qty"]
 
 	def __init__(
 		self, name: str, is_bom: bool = True, exploded_qty: float = 1.0, qty: float = 1
@@ -50,9 +50,10 @@
 	def __create_tree(self):
 		bom = frappe.get_cached_doc("BOM", self.name)
 		self.item_code = bom.item
+		self.bom_qty = bom.quantity
 
 		for item in bom.get("items", []):
-			qty = item.qty / bom.quantity  # quantity per unit
+			qty = item.stock_qty / bom.quantity  # quantity per unit
 			exploded_qty = self.exploded_qty * qty
 			if item.bom_no:
 				child = BOMTree(item.bom_no, exploded_qty=exploded_qty, qty=qty)
diff --git a/erpnext/manufacturing/doctype/bom/test_bom.py b/erpnext/manufacturing/doctype/bom/test_bom.py
index d60feb2..01bf2e4 100644
--- a/erpnext/manufacturing/doctype/bom/test_bom.py
+++ b/erpnext/manufacturing/doctype/bom/test_bom.py
@@ -6,7 +6,7 @@
 from functools import partial
 
 import frappe
-from frappe.tests.utils import FrappeTestCase
+from frappe.tests.utils import FrappeTestCase, timeout
 from frappe.utils import cstr, flt
 
 from erpnext.controllers.tests.test_subcontracting_controller import (
@@ -27,6 +27,7 @@
 
 
 class TestBOM(FrappeTestCase):
+	@timeout
 	def test_get_items(self):
 		from erpnext.manufacturing.doctype.bom.bom import get_bom_items_as_dict
 
@@ -37,6 +38,7 @@
 		self.assertTrue(test_records[2]["items"][1]["item_code"] in items_dict)
 		self.assertEqual(len(items_dict.values()), 2)
 
+	@timeout
 	def test_get_items_exploded(self):
 		from erpnext.manufacturing.doctype.bom.bom import get_bom_items_as_dict
 
@@ -49,11 +51,13 @@
 		self.assertTrue(test_records[0]["items"][1]["item_code"] in items_dict)
 		self.assertEqual(len(items_dict.values()), 3)
 
+	@timeout
 	def test_get_items_list(self):
 		from erpnext.manufacturing.doctype.bom.bom import get_bom_items
 
 		self.assertEqual(len(get_bom_items(bom=get_default_bom(), company="_Test Company")), 3)
 
+	@timeout
 	def test_default_bom(self):
 		def _get_default_bom_in_item():
 			return cstr(frappe.db.get_value("Item", "_Test FG Item 2", "default_bom"))
@@ -71,6 +75,7 @@
 
 		self.assertTrue(_get_default_bom_in_item(), bom.name)
 
+	@timeout
 	def test_update_bom_cost_in_all_boms(self):
 		# get current rate for '_Test Item 2'
 		bom_rates = frappe.db.get_values(
@@ -99,6 +104,7 @@
 		):
 			self.assertEqual(d.base_rate, rm_base_rate + 10)
 
+	@timeout
 	def test_bom_cost(self):
 		bom = frappe.copy_doc(test_records[2])
 		bom.insert()
@@ -127,6 +133,7 @@
 		self.assertAlmostEqual(bom.base_raw_material_cost, base_raw_material_cost)
 		self.assertAlmostEqual(bom.base_total_cost, base_raw_material_cost + base_op_cost)
 
+	@timeout
 	def test_bom_cost_with_batch_size(self):
 		bom = frappe.copy_doc(test_records[2])
 		bom.docstatus = 0
@@ -145,6 +152,7 @@
 		self.assertAlmostEqual(bom.operating_cost, op_cost / 2)
 		bom.delete()
 
+	@timeout
 	def test_bom_cost_multi_uom_multi_currency_based_on_price_list(self):
 		frappe.db.set_value("Price List", "_Test Price List", "price_not_uom_dependent", 1)
 		for item_code, rate in (("_Test Item", 3600), ("_Test Item Home Desktop Manufactured", 3000)):
@@ -181,6 +189,7 @@
 		self.assertEqual(bom.base_raw_material_cost, 27000)
 		self.assertEqual(bom.base_total_cost, 33000)
 
+	@timeout
 	def test_bom_cost_multi_uom_based_on_valuation_rate(self):
 		bom = frappe.copy_doc(test_records[2])
 		bom.set_rate_of_sub_assembly_item_based_on_bom = 0
@@ -202,6 +211,7 @@
 
 		self.assertEqual(bom.items[0].rate, 20)
 
+	@timeout
 	def test_bom_cost_with_fg_based_operating_cost(self):
 		bom = frappe.copy_doc(test_records[4])
 		bom.insert()
@@ -229,6 +239,7 @@
 		self.assertAlmostEqual(bom.base_raw_material_cost, base_raw_material_cost)
 		self.assertAlmostEqual(bom.base_total_cost, base_raw_material_cost + base_op_cost)
 
+	@timeout
 	def test_subcontractor_sourced_item(self):
 		item_code = "_Test Subcontracted FG Item 1"
 		set_backflush_based_on("Material Transferred for Subcontract")
@@ -310,6 +321,7 @@
 		supplied_items = sorted([d.rm_item_code for d in sco.supplied_items])
 		self.assertEqual(bom_items, supplied_items)
 
+	@timeout
 	def test_bom_tree_representation(self):
 		bom_tree = {
 			"Assembly": {
@@ -335,6 +347,7 @@
 		for reqd_item, created_item in zip(reqd_order, created_order):
 			self.assertEqual(reqd_item, created_item.item_code)
 
+	@timeout
 	def test_generated_variant_bom(self):
 		from erpnext.controllers.item_variant import create_variant
 
@@ -375,6 +388,7 @@
 			self.assertEqual(reqd_item.qty, created_item.qty)
 			self.assertEqual(reqd_item.exploded_qty, created_item.exploded_qty)
 
+	@timeout
 	def test_bom_recursion_1st_level(self):
 		"""BOM should not allow BOM item again in child"""
 		item_code = make_item(properties={"is_stock_item": 1}).name
@@ -387,6 +401,7 @@
 			bom.items[0].bom_no = bom.name
 			bom.save()
 
+	@timeout
 	def test_bom_recursion_transitive(self):
 		item1 = make_item(properties={"is_stock_item": 1}).name
 		item2 = make_item(properties={"is_stock_item": 1}).name
@@ -408,6 +423,7 @@
 			bom1.save()
 			bom2.save()
 
+	@timeout
 	def test_bom_with_process_loss_item(self):
 		fg_item_non_whole, fg_item_whole, bom_item = create_process_loss_bom_items()
 
@@ -421,6 +437,7 @@
 		#  Items with whole UOMs can't be PL Items
 		self.assertRaises(frappe.ValidationError, bom_doc.submit)
 
+	@timeout
 	def test_bom_item_query(self):
 		query = partial(
 			item_query,
@@ -440,6 +457,7 @@
 		)
 		self.assertTrue(0 < len(filtered) <= 3, msg="Item filtering showing excessive results")
 
+	@timeout
 	def test_exclude_exploded_items_from_bom(self):
 		bom_no = get_default_bom()
 		new_bom = frappe.copy_doc(frappe.get_doc("BOM", bom_no))
@@ -458,6 +476,7 @@
 
 		new_bom.delete()
 
+	@timeout
 	def test_valid_transfer_defaults(self):
 		bom_with_op = frappe.db.get_value(
 			"BOM", {"item": "_Test FG Item 2", "with_operations": 1, "is_active": 1}
@@ -489,11 +508,13 @@
 		self.assertEqual(bom.transfer_material_against, "Work Order")
 		bom.delete()
 
+	@timeout
 	def test_bom_name_length(self):
 		"""test >140 char names"""
 		bom_tree = {"x" * 140: {" ".join(["abc"] * 35): {}}}
 		create_nested_bom(bom_tree, prefix="")
 
+	@timeout
 	def test_version_index(self):
 
 		bom = frappe.new_doc("BOM")
@@ -515,6 +536,7 @@
 					msg=f"Incorrect index for {existing_boms}",
 				)
 
+	@timeout
 	def test_bom_versioning(self):
 		bom_tree = {frappe.generate_hash(length=10): {frappe.generate_hash(length=10): {}}}
 		bom = create_nested_bom(bom_tree, prefix="")
@@ -547,6 +569,7 @@
 		self.assertNotEqual(amendment.name, version.name)
 		self.assertEqual(int(version.name.split("-")[-1]), 2)
 
+	@timeout
 	def test_clear_inpection_quality(self):
 
 		bom = frappe.copy_doc(test_records[2], ignore_no_copy=True)
@@ -565,6 +588,7 @@
 
 		self.assertEqual(bom.quality_inspection_template, None)
 
+	@timeout
 	def test_bom_pricing_based_on_lpp(self):
 		from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt
 
@@ -585,6 +609,7 @@
 		bom.submit()
 		self.assertEqual(bom.items[0].rate, 42)
 
+	@timeout
 	def test_set_default_bom_for_item_having_single_bom(self):
 		from erpnext.stock.doctype.item.test_item import make_item
 
@@ -621,6 +646,7 @@
 		bom.reload()
 		self.assertEqual(frappe.get_value("Item", fg_item.item_code, "default_bom"), bom.name)
 
+	@timeout
 	def test_exploded_items_rate(self):
 		rm_item = make_item(
 			properties={"is_stock_item": 1, "valuation_rate": 99, "last_purchase_rate": 89}
@@ -649,6 +675,7 @@
 		bom.submit()
 		self.assertEqual(bom.exploded_items[0].rate, bom.items[0].base_rate)
 
+	@timeout
 	def test_bom_cost_update_flag(self):
 		rm_item = make_item(
 			properties={"is_stock_item": 1, "valuation_rate": 99, "last_purchase_rate": 89}
diff --git a/erpnext/manufacturing/doctype/bom_update_tool/test_bom_update_tool.py b/erpnext/manufacturing/doctype/bom_update_tool/test_bom_update_tool.py
index 5dd557f..2026f62 100644
--- a/erpnext/manufacturing/doctype/bom_update_tool/test_bom_update_tool.py
+++ b/erpnext/manufacturing/doctype/bom_update_tool/test_bom_update_tool.py
@@ -2,7 +2,7 @@
 # License: GNU General Public License v3. See license.txt
 
 import frappe
-from frappe.tests.utils import FrappeTestCase
+from frappe.tests.utils import FrappeTestCase, timeout
 
 from erpnext.manufacturing.doctype.bom_update_log.test_bom_update_log import (
 	update_cost_in_all_boms_in_test,
@@ -20,6 +20,7 @@
 	def tearDown(self):
 		frappe.db.rollback()
 
+	@timeout
 	def test_replace_bom(self):
 		current_bom = "BOM-_Test Item Home Desktop Manufactured-001"
 
@@ -33,6 +34,7 @@
 		self.assertFalse(frappe.db.exists("BOM Item", {"bom_no": current_bom, "docstatus": 1}))
 		self.assertTrue(frappe.db.exists("BOM Item", {"bom_no": bom_doc.name, "docstatus": 1}))
 
+	@timeout
 	def test_bom_cost(self):
 		for item in ["BOM Cost Test Item 1", "BOM Cost Test Item 2", "BOM Cost Test Item 3"]:
 			item_doc = create_item(item, valuation_rate=100)
diff --git a/erpnext/manufacturing/doctype/work_order/work_order.py b/erpnext/manufacturing/doctype/work_order/work_order.py
index ae9e9c6..66b871c 100644
--- a/erpnext/manufacturing/doctype/work_order/work_order.py
+++ b/erpnext/manufacturing/doctype/work_order/work_order.py
@@ -682,7 +682,7 @@
 
 			for node in bom_traversal:
 				if node.is_bom:
-					operations.extend(_get_operations(node.name, qty=node.exploded_qty))
+					operations.extend(_get_operations(node.name, qty=node.exploded_qty / node.bom_qty))
 
 		bom_qty = frappe.get_cached_value("BOM", self.bom_no, "quantity")
 		operations.extend(_get_operations(self.bom_no, qty=1.0 / bom_qty))
diff --git a/erpnext/patches.txt b/erpnext/patches.txt
index e4995f8..c1d4b82 100644
--- a/erpnext/patches.txt
+++ b/erpnext/patches.txt
@@ -323,5 +323,6 @@
 erpnext.patches.v14_0.change_autoname_for_tax_withheld_vouchers
 erpnext.patches.v14_0.set_pick_list_status
 erpnext.patches.v15_0.update_asset_value_for_manual_depr_entries
+erpnext.patches.v15_0.update_gpa_and_ndb_for_assdeprsch
 # below migration patches should always run last
-erpnext.patches.v14_0.migrate_gl_to_payment_ledger
+erpnext.patches.v14_0.migrate_gl_to_payment_ledger
\ No newline at end of file
diff --git a/erpnext/patches/v15_0/update_gpa_and_ndb_for_assdeprsch.py b/erpnext/patches/v15_0/update_gpa_and_ndb_for_assdeprsch.py
new file mode 100644
index 0000000..afb59e0
--- /dev/null
+++ b/erpnext/patches/v15_0/update_gpa_and_ndb_for_assdeprsch.py
@@ -0,0 +1,20 @@
+import frappe
+
+
+def execute():
+	# not using frappe.qb because https://github.com/frappe/frappe/issues/20292
+	frappe.db.sql(
+		"""UPDATE `tabAsset Depreciation Schedule`
+        JOIN `tabAsset`
+        ON `tabAsset Depreciation Schedule`.`asset`=`tabAsset`.`name`
+        SET
+            `tabAsset Depreciation Schedule`.`gross_purchase_amount`=`tabAsset`.`gross_purchase_amount`,
+            `tabAsset Depreciation Schedule`.`number_of_depreciations_booked`=`tabAsset`.`number_of_depreciations_booked`
+        WHERE
+        (
+            `tabAsset Depreciation Schedule`.`gross_purchase_amount`<>`tabAsset`.`gross_purchase_amount`
+            OR
+            `tabAsset Depreciation Schedule`.`number_of_depreciations_booked`<>`tabAsset`.`number_of_depreciations_booked`
+        )
+        AND `tabAsset Depreciation Schedule`.`docstatus`<2"""
+	)
diff --git a/erpnext/projects/doctype/timesheet/timesheet.js b/erpnext/projects/doctype/timesheet/timesheet.js
index a376bf4..d1d07a7 100644
--- a/erpnext/projects/doctype/timesheet/timesheet.js
+++ b/erpnext/projects/doctype/timesheet/timesheet.js
@@ -5,6 +5,8 @@
 	setup: function(frm) {
 		frappe.require("/assets/erpnext/js/projects/timer.js");
 
+		frm.ignore_doctypes_on_cancel_all = ['Sales Invoice'];
+
 		frm.fields_dict.employee.get_query = function() {
 			return {
 				filters:{
diff --git a/erpnext/projects/report/delayed_tasks_summary/delayed_tasks_summary.py b/erpnext/projects/report/delayed_tasks_summary/delayed_tasks_summary.py
index 17e3155..766e40e 100644
--- a/erpnext/projects/report/delayed_tasks_summary/delayed_tasks_summary.py
+++ b/erpnext/projects/report/delayed_tasks_summary/delayed_tasks_summary.py
@@ -46,6 +46,9 @@
 			# task has no end date, hence no delay
 			task.delay = 0
 
+		task.status = _(task.status)
+		task.priority = _(task.priority)
+
 	# Sort by descending order of delay
 	tasks.sort(key=lambda x: x["delay"], reverse=True)
 	return tasks
@@ -73,7 +76,7 @@
 			on_track = on_track + 1
 	charts = {
 		"data": {
-			"labels": ["On Track", "Delayed"],
+			"labels": [_("On Track"), _("Delayed")],
 			"datasets": [{"name": "Delayed", "values": [on_track, delay]}],
 		},
 		"type": "percentage",
diff --git a/erpnext/stock/doctype/item/item.py b/erpnext/stock/doctype/item/item.py
index c06700a..05a37ee 100644
--- a/erpnext/stock/doctype/item/item.py
+++ b/erpnext/stock/doctype/item/item.py
@@ -377,7 +377,9 @@
 						"" if item_barcode.barcode_type not in options else item_barcode.barcode_type
 					)
 					if item_barcode.barcode_type:
-						barcode_type = convert_erpnext_to_barcodenumber(item_barcode.barcode_type.upper())
+						barcode_type = convert_erpnext_to_barcodenumber(
+							item_barcode.barcode_type.upper(), item_barcode.barcode
+						)
 						if barcode_type in barcodenumber.barcodes():
 							if not barcodenumber.check_code(barcode_type, item_barcode.barcode):
 								frappe.throw(
@@ -982,20 +984,29 @@
 				)
 
 
-def convert_erpnext_to_barcodenumber(erpnext_number):
+def convert_erpnext_to_barcodenumber(erpnext_number, barcode):
+	if erpnext_number == "EAN":
+		ean_type = {
+			8: "EAN8",
+			13: "EAN13",
+		}
+		barcode_length = len(barcode)
+		if barcode_length in ean_type:
+			return ean_type[barcode_length]
+
+		return erpnext_number
+
 	convert = {
 		"UPC-A": "UPCA",
 		"CODE-39": "CODE39",
-		"EAN": "EAN13",
-		"EAN-12": "EAN",
-		"EAN-8": "EAN8",
 		"ISBN-10": "ISBN10",
 		"ISBN-13": "ISBN13",
 	}
+
 	if erpnext_number in convert:
 		return convert[erpnext_number]
-	else:
-		return erpnext_number
+
+	return erpnext_number
 
 
 def make_item_price(item, price_list_name, item_price):
diff --git a/erpnext/stock/doctype/item/test_item.py b/erpnext/stock/doctype/item/test_item.py
index 67ed90d..0c6dc77 100644
--- a/erpnext/stock/doctype/item/test_item.py
+++ b/erpnext/stock/doctype/item/test_item.py
@@ -581,8 +581,9 @@
 			},
 			{"barcode": "72527273070", "barcode_type": "UPC-A"},
 			{"barcode": "123456", "barcode_type": "CODE-39"},
-			{"barcode": "401268452363", "barcode_type": "EAN-12"},
-			{"barcode": "90311017", "barcode_type": "EAN-8"},
+			{"barcode": "401268452363", "barcode_type": "EAN"},
+			{"barcode": "90311017", "barcode_type": "EAN"},
+			{"barcode": "73513537", "barcode_type": "EAN"},
 			{"barcode": "0123456789012", "barcode_type": "GS1"},
 			{"barcode": "2211564566668", "barcode_type": "GTIN"},
 			{"barcode": "0256480249", "barcode_type": "ISBN"},
diff --git a/erpnext/stock/tests/test_valuation.py b/erpnext/stock/tests/test_valuation.py
index e60c1ca..05f153b 100644
--- a/erpnext/stock/tests/test_valuation.py
+++ b/erpnext/stock/tests/test_valuation.py
@@ -132,7 +132,7 @@
 		total_qty = 0
 
 		for qty, rate in stock_queue:
-			if qty == 0:
+			if round_off_if_near_zero(qty) == 0:
 				continue
 			if qty > 0:
 				self.queue.add_stock(qty, rate)
@@ -154,7 +154,7 @@
 
 		for qty, rate in stock_queue:
 			# don't allow negative stock
-			if qty == 0 or total_qty + qty < 0 or abs(qty) < 0.1:
+			if round_off_if_near_zero(qty) == 0 or total_qty + qty < 0 or abs(qty) < 0.1:
 				continue
 			if qty > 0:
 				self.queue.add_stock(qty, rate)
@@ -179,7 +179,7 @@
 
 		for qty, rate in stock_queue:
 			# don't allow negative stock
-			if qty == 0 or total_qty + qty < 0 or abs(qty) < 0.1:
+			if round_off_if_near_zero(qty) == 0 or total_qty + qty < 0 or abs(qty) < 0.1:
 				continue
 			if qty > 0:
 				self.queue.add_stock(qty, rate)
@@ -282,7 +282,7 @@
 		total_qty = 0
 
 		for qty, rate in stock_stack:
-			if qty == 0:
+			if round_off_if_near_zero(qty) == 0:
 				continue
 			if qty > 0:
 				self.stack.add_stock(qty, rate)
@@ -304,7 +304,7 @@
 
 		for qty, rate in stock_stack:
 			# don't allow negative stock
-			if qty == 0 or total_qty + qty < 0 or abs(qty) < 0.1:
+			if round_off_if_near_zero(qty) == 0 or total_qty + qty < 0 or abs(qty) < 0.1:
 				continue
 			if qty > 0:
 				self.stack.add_stock(qty, rate)
diff --git a/erpnext/translations/fr.csv b/erpnext/translations/fr.csv
index 8367afd..bace129 100644
--- a/erpnext/translations/fr.csv
+++ b/erpnext/translations/fr.csv
@@ -2801,7 +2801,7 @@
 Stock Levels,Niveaux du Stocks,
 Stock Liabilities,Passif du Stock,
 Stock Options,Options du Stock,
-Stock Qty,Qté en Stock,
+Stock Qty,Qté en unité de stock,
 Stock Received But Not Billed,Stock Reçus Mais Non Facturés,
 Stock Reports,Rapports de stock,
 Stock Summary,Résumé du Stock,