Merge pull request #33305 from barredterra/incoterm-named-place

feat: incoterm named place
diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py
index 9354e44..0a704ac 100644
--- a/erpnext/accounts/doctype/payment_entry/payment_entry.py
+++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py
@@ -684,35 +684,34 @@
 		)
 
 	def validate_payment_against_negative_invoice(self):
-		if (self.payment_type == "Pay" and self.party_type == "Customer") or (
-			self.payment_type == "Receive" and self.party_type == "Supplier"
+		if (self.payment_type != "Pay" or self.party_type != "Customer") and (
+			self.payment_type != "Receive" or self.party_type != "Supplier"
 		):
+			return
 
-			total_negative_outstanding = sum(
-				abs(flt(d.outstanding_amount)) for d in self.get("references") if flt(d.outstanding_amount) < 0
+		total_negative_outstanding = sum(
+			abs(flt(d.outstanding_amount)) for d in self.get("references") if flt(d.outstanding_amount) < 0
+		)
+
+		paid_amount = self.paid_amount if self.payment_type == "Receive" else self.received_amount
+		additional_charges = sum(flt(d.amount) for d in self.deductions)
+
+		if not total_negative_outstanding:
+			if self.party_type == "Customer":
+				msg = _("Cannot pay to Customer without any negative outstanding invoice")
+			else:
+				msg = _("Cannot receive from Supplier without any negative outstanding invoice")
+
+			frappe.throw(msg, InvalidPaymentEntry)
+
+		elif paid_amount - additional_charges > total_negative_outstanding:
+			frappe.throw(
+				_("Paid Amount cannot be greater than total negative outstanding amount {0}").format(
+					total_negative_outstanding
+				),
+				InvalidPaymentEntry,
 			)
 
-			paid_amount = self.paid_amount if self.payment_type == "Receive" else self.received_amount
-			additional_charges = sum([flt(d.amount) for d in self.deductions])
-
-			if not total_negative_outstanding:
-				frappe.throw(
-					_("Cannot {0} {1} {2} without any negative outstanding invoice").format(
-						_(self.payment_type),
-						(_("to") if self.party_type == "Customer" else _("from")),
-						self.party_type,
-					),
-					InvalidPaymentEntry,
-				)
-
-			elif paid_amount - additional_charges > total_negative_outstanding:
-				frappe.throw(
-					_("Paid Amount cannot be greater than total negative outstanding amount {0}").format(
-						total_negative_outstanding
-					),
-					InvalidPaymentEntry,
-				)
-
 	def set_title(self):
 		if frappe.flags.in_import and self.title:
 			# do not set title dynamically if title exists during data import.
diff --git a/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py b/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py
index e93fb61..d269e1f 100644
--- a/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py
+++ b/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py
@@ -533,12 +533,13 @@
 			],
 			filters={"company": company, "root_type": root_type},
 		):
-			if account.account_name not in added_accounts:
+			if account.account_number:
+				account_key = account.account_number + "-" + account.account_name
+			else:
+				account_key = account.account_name
+
+			if account_key not in added_accounts:
 				accounts.append(account)
-				if account.account_number:
-					account_key = account.account_number + "-" + account.account_name
-				else:
-					account_key = account.account_name
 				added_accounts.append(account_key)
 
 	return accounts
diff --git a/erpnext/accounts/report/tax_detail/tax_detail.py b/erpnext/accounts/report/tax_detail/tax_detail.py
index ba8d307..ba733c2 100644
--- a/erpnext/accounts/report/tax_detail/tax_detail.py
+++ b/erpnext/accounts/report/tax_detail/tax_detail.py
@@ -234,8 +234,11 @@
 		if field in ["item_tax_rate", "base_net_amount"]:
 			return None
 
-	if doctype == "GL Entry" and field in ["debit", "credit"]:
-		column.update({"label": _("Amount"), "fieldname": "amount"})
+	if doctype == "GL Entry":
+		if field in ["debit", "credit"]:
+			column.update({"label": _("Amount"), "fieldname": "amount"})
+		elif field == "voucher_type":
+			column.update({"fieldtype": "Data", "options": ""})
 
 	if field == "taxes_and_charges":
 		column.update({"label": _("Taxes and Charges Template")})
diff --git a/erpnext/controllers/status_updater.py b/erpnext/controllers/status_updater.py
index bf07728..d497297 100644
--- a/erpnext/controllers/status_updater.py
+++ b/erpnext/controllers/status_updater.py
@@ -347,16 +347,21 @@
 		)
 
 	def warn_about_bypassing_with_role(self, item, qty_or_amount, role):
-		action = _("Over Receipt/Delivery") if qty_or_amount == "qty" else _("Overbilling")
+		if qty_or_amount == "qty":
+			msg = _("Over Receipt/Delivery of {0} {1} ignored for item {2} because you have {3} role.")
+		else:
+			msg = _("Overbilling of {0} {1} ignored for item {2} because you have {3} role.")
 
-		msg = _("{0} of {1} {2} ignored for item {3} because you have {4} role.").format(
-			action,
-			_(item["target_ref_field"].title()),
-			frappe.bold(item["reduce_by"]),
-			frappe.bold(item.get("item_code")),
-			role,
+		frappe.msgprint(
+			msg.format(
+				_(item["target_ref_field"].title()),
+				frappe.bold(item["reduce_by"]),
+				frappe.bold(item.get("item_code")),
+				role,
+			),
+			indicator="orange",
+			alert=True,
 		)
-		frappe.msgprint(msg, indicator="orange", alert=True)
 
 	def update_qty(self, update_modified=True):
 		"""Updates qty or amount at row level
diff --git a/erpnext/manufacturing/doctype/work_order/test_work_order.py b/erpnext/manufacturing/doctype/work_order/test_work_order.py
index 36466ff..f568264 100644
--- a/erpnext/manufacturing/doctype/work_order/test_work_order.py
+++ b/erpnext/manufacturing/doctype/work_order/test_work_order.py
@@ -1154,6 +1154,36 @@
 		except frappe.MandatoryError:
 			self.fail("Batch generation causing failing in Work Order")
 
+	@change_settings("Manufacturing Settings", {"make_serial_no_batch_from_work_order": 1})
+	def test_auto_serial_no_creation(self):
+		from erpnext.manufacturing.doctype.bom.test_bom import create_nested_bom
+
+		fg_item = frappe.generate_hash(length=20)
+		child_item = frappe.generate_hash(length=20)
+
+		bom_tree = {fg_item: {child_item: {}}}
+
+		create_nested_bom(bom_tree, prefix="")
+
+		item = frappe.get_doc("Item", fg_item)
+		item.has_serial_no = 1
+		item.serial_no_series = f"{item.name}.#####"
+		item.save()
+
+		try:
+			wo_order = make_wo_order_test_record(item=fg_item, qty=2, skip_transfer=True)
+			serial_nos = wo_order.serial_no
+			stock_entry = frappe.get_doc(make_stock_entry(wo_order.name, "Manufacture", 10))
+			stock_entry.set_work_order_details()
+			stock_entry.set_serial_no_batch_for_finished_good()
+			for row in stock_entry.items:
+				if row.item_code == fg_item:
+					self.assertTrue(row.serial_no)
+					self.assertEqual(sorted(get_serial_nos(row.serial_no)), sorted(get_serial_nos(serial_nos)))
+
+		except frappe.MandatoryError:
+			self.fail("Batch generation causing failing in Work Order")
+
 	@change_settings(
 		"Manufacturing Settings",
 		{"backflush_raw_materials_based_on": "Material Transferred for Manufacture"},
diff --git a/erpnext/manufacturing/report/bom_stock_report/bom_stock_report.py b/erpnext/manufacturing/report/bom_stock_report/bom_stock_report.py
index 1e1b435..cdf1541 100644
--- a/erpnext/manufacturing/report/bom_stock_report/bom_stock_report.py
+++ b/erpnext/manufacturing/report/bom_stock_report/bom_stock_report.py
@@ -4,7 +4,7 @@
 
 import frappe
 from frappe import _
-from frappe.query_builder.functions import Floor, Sum
+from frappe.query_builder.functions import Sum
 from pypika.terms import ExistsCriterion
 
 
@@ -58,9 +58,9 @@
 			bom_item.description,
 			bom_item.stock_qty,
 			bom_item.stock_uom,
-			bom_item.stock_qty * qty_to_produce / bom.quantity,
-			Sum(bin.actual_qty).as_("actual_qty"),
-			Sum(Floor(bin.actual_qty / (bom_item.stock_qty * qty_to_produce / bom.quantity))),
+			(bom_item.stock_qty / bom.quantity) * qty_to_produce,
+			Sum(bin.actual_qty),
+			Sum(bin.actual_qty) / (bom_item.stock_qty / bom.quantity),
 		)
 		.where((bom_item.parent == filters.get("bom")) & (bom_item.parenttype == "BOM"))
 		.groupby(bom_item.item_code)
diff --git a/erpnext/public/js/controllers/taxes_and_totals.js b/erpnext/public/js/controllers/taxes_and_totals.js
index 7481000..1f8a5e3 100644
--- a/erpnext/public/js/controllers/taxes_and_totals.js
+++ b/erpnext/public/js/controllers/taxes_and_totals.js
@@ -58,7 +58,7 @@
 
 		if (
 			in_list(["Sales Invoice", "POS Invoice"], this.frm.doc.doctype)
-			&& this.frm.doc.s_pos
+			&& this.frm.doc.is_pos
 			&& this.frm.doc.is_return
 		) {
 			this.set_total_amount_to_default_mop();
diff --git a/erpnext/selling/doctype/sales_order/sales_order_dashboard.py b/erpnext/selling/doctype/sales_order/sales_order_dashboard.py
index ace2e29..5c4b578 100644
--- a/erpnext/selling/doctype/sales_order/sales_order_dashboard.py
+++ b/erpnext/selling/doctype/sales_order/sales_order_dashboard.py
@@ -12,7 +12,10 @@
 			"Auto Repeat": "reference_document",
 			"Maintenance Visit": "prevdoc_docname",
 		},
-		"internal_links": {"Quotation": ["items", "prevdoc_docname"]},
+		"internal_links": {
+			"Quotation": ["items", "prevdoc_docname"],
+			"Material Request": ["items", "material_request"],
+		},
 		"transactions": [
 			{
 				"label": _("Fulfillment"),
diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py
index d9b9f12..d401f81 100644
--- a/erpnext/stock/doctype/stock_entry/stock_entry.py
+++ b/erpnext/stock/doctype/stock_entry/stock_entry.py
@@ -2236,16 +2236,16 @@
 			d.qty -= process_loss_dict[d.item_code][1]
 
 	def set_serial_no_batch_for_finished_good(self):
-		serial_nos = ""
+		serial_nos = []
 		if self.pro_doc.serial_no:
-			serial_nos = self.get_serial_nos_for_fg()
+			serial_nos = self.get_serial_nos_for_fg() or []
 
 		for row in self.items:
 			if row.is_finished_item and row.item_code == self.pro_doc.production_item:
 				if serial_nos:
 					row.serial_no = "\n".join(serial_nos[0 : cint(row.qty)])
 
-	def get_serial_nos_for_fg(self, args):
+	def get_serial_nos_for_fg(self):
 		fields = [
 			"`tabStock Entry`.`name`",
 			"`tabStock Entry Detail`.`qty`",
@@ -2261,9 +2261,7 @@
 		]
 
 		stock_entries = frappe.get_all("Stock Entry", fields=fields, filters=filters)
-
-		if self.pro_doc.serial_no:
-			return self.get_available_serial_nos(stock_entries)
+		return self.get_available_serial_nos(stock_entries)
 
 	def get_available_serial_nos(self, stock_entries):
 		used_serial_nos = []
diff --git a/erpnext/translations/de.csv b/erpnext/translations/de.csv
index f1d8302..1014e27 100644
--- a/erpnext/translations/de.csv
+++ b/erpnext/translations/de.csv
@@ -1849,6 +1849,8 @@
 Outstanding Cheques and Deposits to clear,Ausstehende Schecks und Anzahlungen zum verbuchen,
 Outstanding for {0} cannot be less than zero ({1}),Ausstände für {0} können nicht kleiner als Null sein ({1}),
 Outward taxable supplies(zero rated),Steuerpflichtige Lieferungen aus dem Ausland (null bewertet),
+Over Receipt/Delivery of {0} {1} ignored for item {2} because you have {3} role.,"Überhöhte Annahme bzw. Lieferung von Artikel {2} mit {0} {1} wurde ignoriert, weil Sie die Rolle {3} haben."
+Overbilling of {0} {1} ignored for item {2} because you have {3} role.,"Überhöhte Abrechnung von Artikel {2} mit {0} {1} wurde ignoriert, weil Sie die Rolle {3} haben."
 Overdue,Überfällig,
 Overlap in scoring between {0} and {1},Überlappung beim Scoring zwischen {0} und {1},
 Overlapping conditions found between:,Überlagernde Bedingungen gefunden zwischen:,
@@ -9914,4 +9916,3 @@
 Delivered at Place,Geliefert benannter Ort,
 Delivered at Place Unloaded,Geliefert benannter Ort entladen,
 Delivered Duty Paid,Geliefert verzollt,
-{0} of {1} {2} ignored for item {3} because you have {4} role,"{0} von Artikel {3} mit {1} {2} wurde ignoriert, weil Sie die Rolle {4} haben."