Merge pull request #39694 from ruthra-kumar/enforce_separate_account_for_each_bank_account

refactor: enforce unique GL Account for each 'Bank Account'
diff --git a/erpnext/accounts/doctype/account/account.py b/erpnext/accounts/doctype/account/account.py
index 651599d..3f11798 100644
--- a/erpnext/accounts/doctype/account/account.py
+++ b/erpnext/accounts/doctype/account/account.py
@@ -118,6 +118,7 @@
 		self.validate_balance_must_be_debit_or_credit()
 		self.validate_account_currency()
 		self.validate_root_company_and_sync_account_to_children()
+		self.validate_receivable_payable_account_type()
 
 	def validate_parent_child_account_type(self):
 		if self.parent_account:
@@ -188,6 +189,24 @@
 				"Balance Sheet" if self.root_type in ("Asset", "Liability", "Equity") else "Profit and Loss"
 			)
 
+	def validate_receivable_payable_account_type(self):
+		doc_before_save = self.get_doc_before_save()
+		receivable_payable_types = ["Receivable", "Payable"]
+		if (
+			doc_before_save
+			and doc_before_save.account_type in receivable_payable_types
+			and doc_before_save.account_type != self.account_type
+		):
+			# check for ledger entries
+			if frappe.db.get_all("GL Entry", filters={"account": self.name, "is_cancelled": 0}, limit=1):
+				msg = _(
+					"There are ledger entries against this account. Changing {0} to non-{1} in live system will cause incorrect output in 'Accounts {2}' report"
+				).format(
+					frappe.bold("Account Type"), doc_before_save.account_type, doc_before_save.account_type
+				)
+				frappe.msgprint(msg)
+				self.add_comment("Comment", msg)
+
 	def validate_root_details(self):
 		doc_before_save = self.get_doc_before_save()
 
diff --git a/erpnext/accounts/doctype/account/test_account.py b/erpnext/accounts/doctype/account/test_account.py
index eb3e00b..7d0869b 100644
--- a/erpnext/accounts/doctype/account/test_account.py
+++ b/erpnext/accounts/doctype/account/test_account.py
@@ -6,6 +6,7 @@
 
 import frappe
 from frappe.test_runner import make_test_records
+from frappe.utils import nowdate
 
 from erpnext.accounts.doctype.account.account import (
 	InvalidAccountMergeError,
@@ -324,6 +325,19 @@
 		acc.account_currency = "USD"
 		self.assertRaises(frappe.ValidationError, acc.save)
 
+	def test_account_balance(self):
+		from erpnext.accounts.utils import get_balance_on
+
+		if not frappe.db.exists("Account", "Test Percent Account %5 - _TC"):
+			acc = frappe.new_doc("Account")
+			acc.account_name = "Test Percent Account %5"
+			acc.parent_account = "Tax Assets - _TC"
+			acc.company = "_Test Company"
+			acc.insert()
+
+		balance = get_balance_on(account="Test Percent Account %5 - _TC", date=nowdate())
+		self.assertEqual(balance, 0)
+
 
 def _make_test_records(verbose=None):
 	from frappe.test_runner import make_test_objects
diff --git a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json
index b10924f..0e238e0 100644
--- a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json
+++ b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json
@@ -1,7 +1,6 @@
 {
  "actions": [],
  "creation": "2013-06-24 15:49:57",
- "description": "Settings for Accounts",
  "doctype": "DocType",
  "document_type": "Other",
  "editable_grid": 1,
@@ -462,7 +461,7 @@
  "index_web_pages_for_search": 1,
  "issingle": 1,
  "links": [],
- "modified": "2024-01-22 12:10:10.151819",
+ "modified": "2024-01-30 14:04:26.553554",
  "modified_by": "Administrator",
  "module": "Accounts",
  "name": "Accounts Settings",
diff --git a/erpnext/accounts/doctype/bank_clearance/bank_clearance.py b/erpnext/accounts/doctype/bank_clearance/bank_clearance.py
index 4b97619..8a505a8 100644
--- a/erpnext/accounts/doctype/bank_clearance/bank_clearance.py
+++ b/erpnext/accounts/doctype/bank_clearance/bank_clearance.py
@@ -5,7 +5,9 @@
 import frappe
 from frappe import _, msgprint
 from frappe.model.document import Document
+from frappe.query_builder.custom import ConstantColumn
 from frappe.utils import flt, fmt_money, getdate
+from pypika import Order
 
 import erpnext
 
@@ -179,39 +181,62 @@
 
 	pos_sales_invoices, pos_purchase_invoices = [], []
 	if include_pos_transactions:
-		pos_sales_invoices = frappe.db.sql(
-			"""
-				select
-					"Sales Invoice Payment" as payment_document, sip.name as payment_entry, sip.amount as debit,
-					si.posting_date, si.customer as against_account, sip.clearance_date,
-					account.account_currency, 0 as credit
-				from `tabSales Invoice Payment` sip, `tabSales Invoice` si, `tabAccount` account
-				where
-					sip.account=%(account)s and si.docstatus=1 and sip.parent = si.name
-					and account.name = sip.account and si.posting_date >= %(from)s and si.posting_date <= %(to)s
-				order by
-					si.posting_date ASC, si.name DESC
-			""",
-			{"account": account, "from": from_date, "to": to_date},
-			as_dict=1,
-		)
+		si_payment = frappe.qb.DocType("Sales Invoice Payment")
+		si = frappe.qb.DocType("Sales Invoice")
+		acc = frappe.qb.DocType("Account")
 
-		pos_purchase_invoices = frappe.db.sql(
-			"""
-				select
-					"Purchase Invoice" as payment_document, pi.name as payment_entry, pi.paid_amount as credit,
-					pi.posting_date, pi.supplier as against_account, pi.clearance_date,
-					account.account_currency, 0 as debit
-				from `tabPurchase Invoice` pi, `tabAccount` account
-				where
-					pi.cash_bank_account=%(account)s and pi.docstatus=1 and account.name = pi.cash_bank_account
-					and pi.posting_date >= %(from)s and pi.posting_date <= %(to)s
-				order by
-					pi.posting_date ASC, pi.name DESC
-			""",
-			{"account": account, "from": from_date, "to": to_date},
-			as_dict=1,
-		)
+		pos_sales_invoices = (
+			frappe.qb.from_(si_payment)
+			.inner_join(si)
+			.on(si_payment.parent == si.name)
+			.inner_join(acc)
+			.on(si_payment.account == acc.name)
+			.select(
+				ConstantColumn("Sales Invoice").as_("payment_document"),
+				si.name.as_("payment_entry"),
+				si_payment.reference_no.as_("cheque_number"),
+				si_payment.amount.as_("debit"),
+				si.posting_date,
+				si.customer.as_("against_account"),
+				si_payment.clearance_date,
+				acc.account_currency,
+				ConstantColumn(0).as_("credit"),
+			)
+			.where(
+				(si.docstatus == 1)
+				& (si_payment.account == account)
+				& (si.posting_date >= from_date)
+				& (si.posting_date <= to_date)
+			)
+			.orderby(si.posting_date)
+			.orderby(si.name, order=Order.desc)
+		).run(as_dict=True)
+
+		pi = frappe.qb.DocType("Purchase Invoice")
+
+		pos_purchase_invoices = (
+			frappe.qb.from_(pi)
+			.inner_join(acc)
+			.on(pi.cash_bank_account == acc.name)
+			.select(
+				ConstantColumn("Purchase Invoice").as_("payment_document"),
+				pi.name.as_("payment_entry"),
+				pi.paid_amount.as_("credit"),
+				pi.posting_date,
+				pi.supplier.as_("against_account"),
+				pi.clearance_date,
+				acc.account_currency,
+				ConstantColumn(0).as_("debit"),
+			)
+			.where(
+				(pi.docstatus == 1)
+				& (pi.cash_bank_account == account)
+				& (pi.posting_date >= from_date)
+				& (pi.posting_date <= to_date)
+			)
+			.orderby(pi.posting_date)
+			.orderby(pi.name, order=Order.desc)
+		).run(as_dict=True)
 
 	entries = (
 		list(payment_entries)
diff --git a/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.py b/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.py
index 1a4747c..30e564c 100644
--- a/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.py
+++ b/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.py
@@ -80,7 +80,8 @@
 		from frappe.utils.background_jobs import is_job_enqueued
 		from frappe.utils.scheduler import is_scheduler_inactive
 
-		if is_scheduler_inactive() and not frappe.flags.in_test:
+		run_now = frappe.flags.in_test or frappe.conf.developer_mode
+		if is_scheduler_inactive() and not run_now:
 			frappe.throw(_("Scheduler is inactive. Cannot import data."), title=_("Scheduler Inactive"))
 
 		job_id = f"bank_statement_import::{self.name}"
@@ -97,7 +98,7 @@
 				google_sheets_url=self.google_sheets_url,
 				bank=self.bank,
 				template_options=self.template_options,
-				now=frappe.conf.developer_mode or frappe.flags.in_test,
+				now=run_now,
 			)
 			return True
 
diff --git a/erpnext/accounts/doctype/fiscal_year/fiscal_year.json b/erpnext/accounts/doctype/fiscal_year/fiscal_year.json
index bd2bfbd..21091eb 100644
--- a/erpnext/accounts/doctype/fiscal_year/fiscal_year.json
+++ b/erpnext/accounts/doctype/fiscal_year/fiscal_year.json
@@ -3,7 +3,7 @@
  "allow_import": 1,
  "autoname": "field:year",
  "creation": "2013-01-22 16:50:25",
- "description": "**Fiscal Year** represents a Financial Year. All accounting entries and other major transactions are tracked against **Fiscal Year**.",
+ "description": "Represents a Financial Year. All accounting entries and other major transactions are tracked against the Fiscal Year.",
  "doctype": "DocType",
  "document_type": "Setup",
  "engine": "InnoDB",
@@ -82,11 +82,12 @@
  "icon": "fa fa-calendar",
  "idx": 1,
  "links": [],
- "modified": "2024-01-17 13:06:01.608953",
+ "modified": "2024-01-30 12:35:38.645968",
  "modified_by": "Administrator",
  "module": "Accounts",
  "name": "Fiscal Year",
-  "owner": "Administrator",
+ "naming_rule": "By fieldname",
+ "owner": "Administrator",
  "permissions": [
   {
    "create": 1,
@@ -130,5 +131,6 @@
  ],
  "show_name_in_global_search": 1,
  "sort_field": "name",
- "sort_order": "DESC"
+ "sort_order": "DESC",
+ "states": []
 }
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/gl_entry/gl_entry.py b/erpnext/accounts/doctype/gl_entry/gl_entry.py
index 777a5bb..def2838 100644
--- a/erpnext/accounts/doctype/gl_entry/gl_entry.py
+++ b/erpnext/accounts/doctype/gl_entry/gl_entry.py
@@ -13,16 +13,9 @@
 from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
 	get_checks_for_pl_and_bs_accounts,
 )
-from erpnext.accounts.doctype.accounting_dimension_filter.accounting_dimension_filter import (
-	get_dimension_filter_map,
-)
 from erpnext.accounts.party import validate_party_frozen_disabled, validate_party_gle_currency
 from erpnext.accounts.utils import get_account_currency, get_fiscal_year
-from erpnext.exceptions import (
-	InvalidAccountCurrency,
-	InvalidAccountDimensionError,
-	MandatoryAccountDimensionError,
-)
+from erpnext.exceptions import InvalidAccountCurrency
 
 exclude_from_linked_with = True
 
@@ -98,7 +91,6 @@
 		if not self.flags.from_repost and self.voucher_type != "Period Closing Voucher":
 			self.validate_account_details(adv_adj)
 			self.validate_dimensions_for_pl_and_bs()
-			self.validate_allowed_dimensions()
 			validate_balance_type(self.account, adv_adj)
 			validate_frozen_account(self.account, adv_adj)
 
@@ -208,42 +200,6 @@
 						)
 					)
 
-	def validate_allowed_dimensions(self):
-		dimension_filter_map = get_dimension_filter_map()
-		for key, value in dimension_filter_map.items():
-			dimension = key[0]
-			account = key[1]
-
-			if self.account == account:
-				if value["is_mandatory"] and not self.get(dimension):
-					frappe.throw(
-						_("{0} is mandatory for account {1}").format(
-							frappe.bold(frappe.unscrub(dimension)), frappe.bold(self.account)
-						),
-						MandatoryAccountDimensionError,
-					)
-
-				if value["allow_or_restrict"] == "Allow":
-					if self.get(dimension) and self.get(dimension) not in value["allowed_dimensions"]:
-						frappe.throw(
-							_("Invalid value {0} for {1} against account {2}").format(
-								frappe.bold(self.get(dimension)),
-								frappe.bold(frappe.unscrub(dimension)),
-								frappe.bold(self.account),
-							),
-							InvalidAccountDimensionError,
-						)
-				else:
-					if self.get(dimension) and self.get(dimension) in value["allowed_dimensions"]:
-						frappe.throw(
-							_("Invalid value {0} for {1} against account {2}").format(
-								frappe.bold(self.get(dimension)),
-								frappe.bold(frappe.unscrub(dimension)),
-								frappe.bold(self.account),
-							),
-							InvalidAccountDimensionError,
-						)
-
 	def check_pl_account(self):
 		if (
 			self.is_opening == "Yes"
diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.py b/erpnext/accounts/doctype/journal_entry/journal_entry.py
index 7bf5324..18cf4ed 100644
--- a/erpnext/accounts/doctype/journal_entry/journal_entry.py
+++ b/erpnext/accounts/doctype/journal_entry/journal_entry.py
@@ -1169,7 +1169,9 @@
 
 
 @frappe.whitelist()
-def get_default_bank_cash_account(company, account_type=None, mode_of_payment=None, account=None):
+def get_default_bank_cash_account(
+	company, account_type=None, mode_of_payment=None, account=None, ignore_permissions=False
+):
 	from erpnext.accounts.doctype.sales_invoice.sales_invoice import get_bank_cash_account
 
 	if mode_of_payment:
@@ -1207,7 +1209,7 @@
 		return frappe._dict(
 			{
 				"account": account,
-				"balance": get_balance_on(account),
+				"balance": get_balance_on(account, ignore_account_permission=ignore_permissions),
 				"account_currency": account_details.account_currency,
 				"account_type": account_details.account_type,
 			}
diff --git a/erpnext/accounts/doctype/monthly_distribution/monthly_distribution.json b/erpnext/accounts/doctype/monthly_distribution/monthly_distribution.json
index 14f2d80..488e8b2 100644
--- a/erpnext/accounts/doctype/monthly_distribution/monthly_distribution.json
+++ b/erpnext/accounts/doctype/monthly_distribution/monthly_distribution.json
@@ -1,173 +1,77 @@
 {
- "allow_copy": 0, 
- "allow_import": 0, 
- "allow_rename": 0, 
- "autoname": "field:distribution_id", 
- "beta": 0, 
- "creation": "2013-01-10 16:34:05", 
- "custom": 0, 
- "description": "**Monthly Distribution** helps you distribute the Budget/Target across months if you have seasonality in your business.", 
- "docstatus": 0, 
- "doctype": "DocType", 
- "editable_grid": 0, 
- "engine": "InnoDB", 
+ "actions": [],
+ "autoname": "field:distribution_id",
+ "creation": "2013-01-10 16:34:05",
+ "description": "Helps you distribute the Budget/Target across months if you have seasonality in your business.",
+ "doctype": "DocType",
+ "engine": "InnoDB",
+ "field_order": [
+  "distribution_id",
+  "fiscal_year",
+  "percentages"
+ ],
  "fields": [
   {
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "description": "Name of the Monthly Distribution", 
-   "fieldname": "distribution_id", 
-   "fieldtype": "Data", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_list_view": 1, 
-   "in_standard_filter": 0, 
-   "label": "Distribution Name", 
-   "length": 0, 
-   "no_copy": 0, 
-   "oldfieldname": "distribution_id", 
-   "oldfieldtype": "Data", 
-   "permlevel": 0, 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 1, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "unique": 0
-  }, 
+   "description": "Name of the Monthly Distribution",
+   "fieldname": "distribution_id",
+   "fieldtype": "Data",
+   "in_list_view": 1,
+   "label": "Distribution Name",
+   "oldfieldname": "distribution_id",
+   "oldfieldtype": "Data",
+   "reqd": 1,
+   "unique": 1
+  },
   {
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "fiscal_year", 
-   "fieldtype": "Link", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 1, 
-   "in_list_view": 1, 
-   "in_standard_filter": 1, 
-   "label": "Fiscal Year", 
-   "length": 0, 
-   "no_copy": 0, 
-   "oldfieldname": "fiscal_year", 
-   "oldfieldtype": "Select", 
-   "options": "Fiscal Year", 
-   "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": 1, 
-   "set_only_once": 0, 
-   "unique": 0
-  }, 
+   "fieldname": "fiscal_year",
+   "fieldtype": "Link",
+   "in_filter": 1,
+   "in_list_view": 1,
+   "in_standard_filter": 1,
+   "label": "Fiscal Year",
+   "oldfieldname": "fiscal_year",
+   "oldfieldtype": "Select",
+   "options": "Fiscal Year",
+   "search_index": 1
+  },
   {
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "percentages", 
-   "fieldtype": "Table", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_list_view": 0, 
-   "in_standard_filter": 0, 
-   "label": "Monthly Distribution Percentages", 
-   "length": 0, 
-   "no_copy": 0, 
-   "oldfieldname": "budget_distribution_details", 
-   "oldfieldtype": "Table", 
-   "options": "Monthly Distribution Percentage", 
-   "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": "percentages",
+   "fieldtype": "Table",
+   "label": "Monthly Distribution Percentages",
+   "oldfieldname": "budget_distribution_details",
+   "oldfieldtype": "Table",
+   "options": "Monthly Distribution Percentage"
   }
- ], 
- "hide_heading": 0, 
- "hide_toolbar": 0, 
- "icon": "fa fa-bar-chart", 
- "idx": 1, 
- "image_view": 0, 
- "in_create": 0, 
-
- "is_submittable": 0, 
- "issingle": 0, 
- "istable": 0, 
- "max_attachments": 0, 
- "modified": "2016-11-21 14:54:35.998761", 
- "modified_by": "Administrator", 
- "module": "Accounts", 
- "name": "Monthly Distribution", 
- "name_case": "Title Case", 
- "owner": "Administrator", 
+ ],
+ "icon": "fa fa-bar-chart",
+ "idx": 1,
+ "links": [],
+ "modified": "2024-01-30 13:57:55.802744",
+ "modified_by": "Administrator",
+ "module": "Accounts",
+ "name": "Monthly Distribution",
+ "naming_rule": "By fieldname",
+ "owner": "Administrator",
  "permissions": [
   {
-   "amend": 0, 
-   "apply_user_permissions": 0, 
-   "cancel": 0, 
-   "create": 1, 
-   "delete": 1, 
-   "email": 1, 
-   "export": 0, 
-   "if_owner": 0, 
-   "import": 0, 
-   "is_custom": 0, 
-   "permlevel": 0, 
-   "print": 1, 
-   "read": 1, 
-   "report": 1, 
-   "role": "Accounts Manager", 
-   "set_user_permissions": 0, 
-   "share": 1, 
-   "submit": 0, 
+   "create": 1,
+   "delete": 1,
+   "email": 1,
+   "print": 1,
+   "read": 1,
+   "report": 1,
+   "role": "Accounts Manager",
+   "share": 1,
    "write": 1
-  }, 
+  },
   {
-   "amend": 0, 
-   "apply_user_permissions": 0, 
-   "cancel": 0, 
-   "create": 0, 
-   "delete": 0, 
-   "email": 0, 
-   "export": 0, 
-   "if_owner": 0, 
-   "import": 0, 
-   "is_custom": 0, 
-   "permlevel": 2, 
-   "print": 0, 
-   "read": 1, 
-   "report": 1, 
-   "role": "Accounts Manager", 
-   "set_user_permissions": 0, 
-   "share": 0, 
-   "submit": 0, 
-   "write": 0
+   "permlevel": 2,
+   "read": 1,
+   "report": 1,
+   "role": "Accounts Manager"
   }
- ], 
- "quick_entry": 0, 
- "read_only": 0, 
- "read_only_onload": 0, 
- "sort_field": "modified", 
- "sort_order": "DESC", 
- "track_seen": 0
+ ],
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "states": []
 }
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py
index 7e88b6b..c55c820 100644
--- a/erpnext/accounts/doctype/payment_entry/payment_entry.py
+++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py
@@ -1032,19 +1032,19 @@
 		)
 
 		base_party_amount = flt(self.base_total_allocated_amount) + flt(base_unallocated_amount)
-
-		if self.payment_type == "Receive":
-			self.difference_amount = base_party_amount - self.base_received_amount
-		elif self.payment_type == "Pay":
-			self.difference_amount = self.base_paid_amount - base_party_amount
-		else:
-			self.difference_amount = self.base_paid_amount - flt(self.base_received_amount)
-
-		total_deductions = sum(flt(d.amount) for d in self.get("deductions"))
 		included_taxes = self.get_included_taxes()
 
+		if self.payment_type == "Receive":
+			self.difference_amount = base_party_amount - self.base_received_amount + included_taxes
+		elif self.payment_type == "Pay":
+			self.difference_amount = self.base_paid_amount - base_party_amount - included_taxes
+		else:
+			self.difference_amount = self.base_paid_amount - flt(self.base_received_amount) - included_taxes
+
+		total_deductions = sum(flt(d.amount) for d in self.get("deductions"))
+
 		self.difference_amount = flt(
-			self.difference_amount - total_deductions - included_taxes, self.precision("difference_amount")
+			self.difference_amount - total_deductions, self.precision("difference_amount")
 		)
 
 	def get_included_taxes(self):
@@ -2220,6 +2220,7 @@
 	party_type=None,
 	payment_type=None,
 	reference_date=None,
+	ignore_permissions=False,
 ):
 	doc = frappe.get_doc(dt, dn)
 	over_billing_allowance = frappe.db.get_single_value("Accounts Settings", "over_billing_allowance")
@@ -2242,14 +2243,14 @@
 	)
 
 	# bank or cash
-	bank = get_bank_cash_account(doc, bank_account)
+	bank = get_bank_cash_account(doc, bank_account, ignore_permissions=ignore_permissions)
 
 	# if default bank or cash account is not set in company master and party has default company bank account, fetch it
 	if party_type in ["Customer", "Supplier"] and not bank:
 		party_bank_account = get_party_bank_account(party_type, doc.get(scrub(party_type)))
 		if party_bank_account:
 			account = frappe.db.get_value("Bank Account", party_bank_account, "account")
-			bank = get_bank_cash_account(doc, account)
+			bank = get_bank_cash_account(doc, account, ignore_permissions=ignore_permissions)
 
 	paid_amount, received_amount = set_paid_amount_and_received_amount(
 		dt, party_account_currency, bank, outstanding_amount, payment_type, bank_amount, doc
@@ -2389,9 +2390,13 @@
 		pe.set(dimension, doc.get(dimension))
 
 
-def get_bank_cash_account(doc, bank_account):
+def get_bank_cash_account(doc, bank_account, ignore_permissions=False):
 	bank = get_default_bank_cash_account(
-		doc.company, "Bank", mode_of_payment=doc.get("mode_of_payment"), account=bank_account
+		doc.company,
+		"Bank",
+		mode_of_payment=doc.get("mode_of_payment"),
+		account=bank_account,
+		ignore_permissions=ignore_permissions,
 	)
 
 	if not bank:
diff --git a/erpnext/accounts/doctype/payment_request/payment_request.js b/erpnext/accounts/doctype/payment_request/payment_request.js
index e913912..c85cd42 100644
--- a/erpnext/accounts/doctype/payment_request/payment_request.js
+++ b/erpnext/accounts/doctype/payment_request/payment_request.js
@@ -25,6 +25,10 @@
 })
 
 frappe.ui.form.on("Payment Request", "refresh", function(frm) {
+	if(frm.doc.status == 'Failed'){
+		frm.set_intro(__("Failure: {0}", [frm.doc.failed_reason]), "red");
+	}
+
 	if(frm.doc.payment_request_type == 'Inward' && frm.doc.payment_channel !== "Phone" &&
 		!in_list(["Initiated", "Paid"], frm.doc.status) && !frm.doc.__islocal && frm.doc.docstatus==1){
 		frm.add_custom_button(__('Resend Payment Email'), function(){
diff --git a/erpnext/accounts/doctype/payment_request/payment_request.json b/erpnext/accounts/doctype/payment_request/payment_request.json
index 66b5c4b..f62b624 100644
--- a/erpnext/accounts/doctype/payment_request/payment_request.json
+++ b/erpnext/accounts/doctype/payment_request/payment_request.json
@@ -7,6 +7,7 @@
  "field_order": [
   "payment_request_type",
   "transaction_date",
+  "failed_reason",
   "column_break_2",
   "naming_series",
   "mode_of_payment",
@@ -389,13 +390,22 @@
    "options": "Payment Request",
    "print_hide": 1,
    "read_only": 1
+  },
+  {
+   "fieldname": "failed_reason",
+   "fieldtype": "Data",
+   "hidden": 1,
+   "label": "Reason for Failure",
+   "no_copy": 1,
+   "print_hide": 1,
+   "read_only": 1
   }
  ],
  "in_create": 1,
  "index_web_pages_for_search": 1,
  "is_submittable": 1,
  "links": [],
- "modified": "2023-09-27 09:51:42.277638",
+ "modified": "2024-01-20 00:37:06.988919",
  "modified_by": "Administrator",
  "module": "Accounts",
  "name": "Payment Request",
@@ -433,4 +443,4 @@
  "sort_field": "modified",
  "sort_order": "DESC",
  "states": []
-}
\ No newline at end of file
+}
diff --git a/erpnext/accounts/doctype/payment_request/payment_request_list.js b/erpnext/accounts/doctype/payment_request/payment_request_list.js
index 85d729c..43f7856 100644
--- a/erpnext/accounts/doctype/payment_request/payment_request_list.js
+++ b/erpnext/accounts/doctype/payment_request/payment_request_list.js
@@ -16,6 +16,9 @@
 		else if(doc.status == "Paid") {
 			return [__("Paid"), "blue", "status,=,Paid"];
 		}
+		else if(doc.status == "Failed") {
+			return [__("Failed"), "red", "status,=,Failed"];
+		}
 		else if(doc.status == "Cancelled") {
 			return [__("Cancelled"), "red", "status,=,Cancelled"];
 		}
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 c03b18a..083c8fc 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
@@ -120,18 +120,6 @@
 	statement_dict = {}
 	ageing = ""
 
-	err_journals = None
-	if doc.report == "General Ledger" and doc.ignore_exchange_rate_revaluation_journals:
-		err_journals = frappe.db.get_all(
-			"Journal Entry",
-			filters={
-				"company": doc.company,
-				"docstatus": 1,
-				"voucher_type": ("in", ["Exchange Rate Revaluation", "Exchange Gain Or Loss"]),
-			},
-			as_list=True,
-		)
-
 	for entry in doc.customers:
 		if doc.include_ageing:
 			ageing = set_ageing(doc, entry)
@@ -144,8 +132,8 @@
 		)
 
 		filters = get_common_filters(doc)
-		if err_journals:
-			filters.update({"voucher_no_not_in": [x[0] for x in err_journals]})
+		if doc.ignore_exchange_rate_revaluation_journals:
+			filters.update({"ignore_err": True})
 
 		if doc.report == "General Ledger":
 			filters.update(get_gl_filters(doc, entry, tax_id, presentation_currency))
diff --git a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py
index 992fbe6..5da6f8b 100644
--- a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py
+++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py
@@ -1995,6 +1995,21 @@
 
 		self.assertEqual(pi.items[0].cost_center, "_Test Cost Center Buying - _TC")
 
+	def test_debit_note_with_account_mismatch(self):
+		new_creditors = create_account(
+			parent_account="Accounts Payable - _TC",
+			account_name="Creditors 2",
+			company="_Test Company",
+			account_type="Payable",
+		)
+		pi = make_purchase_invoice(qty=1, rate=1000)
+		dr_note = make_purchase_invoice(
+			qty=-1, rate=1000, is_return=1, return_against=pi.name, do_not_save=True
+		)
+		dr_note.credit_to = new_creditors
+
+		self.assertRaises(frappe.ValidationError, dr_note.save)
+
 	def test_debit_note_without_item(self):
 		pi = make_purchase_invoice(item_name="_Test Item", qty=10, do_not_submit=True)
 		pi.items[0].item_code = ""
diff --git a/erpnext/accounts/doctype/purchase_taxes_and_charges_template/purchase_taxes_and_charges_template.json b/erpnext/accounts/doctype/purchase_taxes_and_charges_template/purchase_taxes_and_charges_template.json
index c36efb8..2ff6a45 100644
--- a/erpnext/accounts/doctype/purchase_taxes_and_charges_template/purchase_taxes_and_charges_template.json
+++ b/erpnext/accounts/doctype/purchase_taxes_and_charges_template/purchase_taxes_and_charges_template.json
@@ -3,7 +3,7 @@
  "allow_import": 1,
  "allow_rename": 1,
  "creation": "2013-01-10 16:34:08",
- "description": "Standard tax template that can be applied to all Purchase Transactions. This template can contain list of tax heads and also other expense heads like \"Shipping\", \"Insurance\", \"Handling\" etc.\n\n#### Note\n\nThe tax rate you define here will be the standard tax rate for all **Items**. If there are **Items** that have different rates, they must be added in the **Item Tax** table in the **Item** master.\n\n#### Description of Columns\n\n1. Calculation Type: \n    - This can be on **Net Total** (that is the sum of basic amount).\n    - **On Previous Row Total / Amount** (for cumulative taxes or charges). If you select this option, the tax will be applied as a percentage of the previous row (in the tax table) amount or total.\n    - **Actual** (as mentioned).\n2. Account Head: The Account ledger under which this tax will be booked\n3. Cost Center: If the tax / charge is an income (like shipping) or expense it needs to be booked against a Cost Center.\n4. Description: Description of the tax (that will be printed in invoices / quotes).\n5. Rate: Tax rate.\n6. Amount: Tax amount.\n7. Total: Cumulative total to this point.\n8. Enter Row: If based on \"Previous Row Total\" you can select the row number which will be taken as a base for this calculation (default is the previous row).\n9. Consider Tax or Charge for: In this section you can specify if the tax / charge is only for valuation (not a part of total) or only for total (does not add value to the item) or for both.\n10. Add or Deduct: Whether you want to add or deduct the tax.",
+ "description": "Standard tax template that can be applied to all Purchase Transactions. This template can contain a list of tax heads and also other expense heads like \"Shipping\", \"Insurance\", \"Handling\", etc.",
  "doctype": "DocType",
  "document_type": "Setup",
  "engine": "InnoDB",
@@ -77,7 +77,7 @@
  "icon": "fa fa-money",
  "idx": 1,
  "links": [],
- "modified": "2022-05-16 16:15:29.059370",
+ "modified": "2024-01-30 13:08:09.537242",
  "modified_by": "Administrator",
  "module": "Accounts",
  "name": "Purchase Taxes and Charges Template",
diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
index 672fec2..8c3aede 100644
--- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
+++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
@@ -1550,6 +1550,19 @@
 		self.assertEqual(frappe.db.get_value("Sales Invoice", si1.name, "outstanding_amount"), -1000)
 		self.assertEqual(frappe.db.get_value("Sales Invoice", si.name, "outstanding_amount"), 2500)
 
+	def test_return_invoice_with_account_mismatch(self):
+		debtors2 = create_account(
+			parent_account="Accounts Receivable - _TC",
+			account_name="Debtors 2",
+			company="_Test Company",
+			account_type="Receivable",
+		)
+		si = create_sales_invoice(qty=1, rate=1000)
+		cr_note = create_sales_invoice(
+			qty=-1, rate=1000, is_return=1, return_against=si.name, debit_to=debtors2, do_not_save=True
+		)
+		self.assertRaises(frappe.ValidationError, cr_note.save)
+
 	def test_gle_made_when_asset_is_returned(self):
 		create_asset_data()
 		asset = create_asset(item_code="Macbook Pro")
diff --git a/erpnext/accounts/doctype/sales_invoice_payment/sales_invoice_payment.json b/erpnext/accounts/doctype/sales_invoice_payment/sales_invoice_payment.json
index 5ab46b7..bd59f65 100644
--- a/erpnext/accounts/doctype/sales_invoice_payment/sales_invoice_payment.json
+++ b/erpnext/accounts/doctype/sales_invoice_payment/sales_invoice_payment.json
@@ -8,6 +8,7 @@
   "default",
   "mode_of_payment",
   "amount",
+  "reference_no",
   "column_break_3",
   "account",
   "type",
@@ -75,11 +76,16 @@
    "hidden": 1,
    "label": "Default",
    "read_only": 1
+  },
+  {
+   "fieldname": "reference_no",
+   "fieldtype": "Data",
+   "label": "Reference No"
   }
  ],
  "istable": 1,
  "links": [],
- "modified": "2020-08-03 12:45:39.986598",
+ "modified": "2024-01-23 16:20:06.436979",
  "modified_by": "Administrator",
  "module": "Accounts",
  "name": "Sales Invoice Payment",
@@ -87,5 +93,6 @@
  "permissions": [],
  "quick_entry": 1,
  "sort_field": "modified",
- "sort_order": "DESC"
+ "sort_order": "DESC",
+ "states": []
 }
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/sales_invoice_payment/sales_invoice_payment.py b/erpnext/accounts/doctype/sales_invoice_payment/sales_invoice_payment.py
index 57d0142..e460a01 100644
--- a/erpnext/accounts/doctype/sales_invoice_payment/sales_invoice_payment.py
+++ b/erpnext/accounts/doctype/sales_invoice_payment/sales_invoice_payment.py
@@ -23,6 +23,7 @@
 		parent: DF.Data
 		parentfield: DF.Data
 		parenttype: DF.Data
+		reference_no: DF.Data | None
 		type: DF.ReadOnly | None
 	# end: auto-generated types
 
diff --git a/erpnext/accounts/doctype/sales_taxes_and_charges_template/sales_taxes_and_charges_template.json b/erpnext/accounts/doctype/sales_taxes_and_charges_template/sales_taxes_and_charges_template.json
index 408ecbf..736d283 100644
--- a/erpnext/accounts/doctype/sales_taxes_and_charges_template/sales_taxes_and_charges_template.json
+++ b/erpnext/accounts/doctype/sales_taxes_and_charges_template/sales_taxes_and_charges_template.json
@@ -3,7 +3,7 @@
  "allow_import": 1,
  "allow_rename": 1,
  "creation": "2013-01-10 16:34:09",
- "description": "Standard tax template that can be applied to all Sales Transactions. This template can contain list of tax heads and also other expense / income heads like \"Shipping\", \"Insurance\", \"Handling\" etc.\n\n#### Note\n\nThe tax rate you define here will be the standard tax rate for all **Items**. If there are **Items** that have different rates, they must be added in the **Item Tax** table in the **Item** master.\n\n#### Description of Columns\n\n1. Calculation Type: \n    - This can be on **Net Total** (that is the sum of basic amount).\n    - **On Previous Row Total / Amount** (for cumulative taxes or charges). If you select this option, the tax will be applied as a percentage of the previous row (in the tax table) amount or total.\n    - **Actual** (as mentioned).\n2. Account Head: The Account ledger under which this tax will be booked\n3. Cost Center: If the tax / charge is an income (like shipping) or expense it needs to be booked against a Cost Center.\n4. Description: Description of the tax (that will be printed in invoices / quotes).\n5. Rate: Tax rate.\n6. Amount: Tax amount.\n7. Total: Cumulative total to this point.\n8. Enter Row: If based on \"Previous Row Total\" you can select the row number which will be taken as a base for this calculation (default is the previous row).\n9. Is this Tax included in Basic Rate?: If you check this, it means that this tax will not be shown below the item table, but will be included in the Basic Rate in your main item table. This is useful where you want give a flat price (inclusive of all taxes) price to customers.",
+ "description": "Standard tax template that can be applied to all Sales Transactions. This template can contain a list of tax heads and also other expense/income heads like \"Shipping\", \"Insurance\", \"Handling\" etc.",
  "doctype": "DocType",
  "document_type": "Setup",
  "engine": "InnoDB",
@@ -79,7 +79,7 @@
  "icon": "fa fa-money",
  "idx": 1,
  "links": [],
- "modified": "2022-05-16 16:14:52.061672",
+ "modified": "2024-01-30 13:07:28.801104",
  "modified_by": "Administrator",
  "module": "Accounts",
  "name": "Sales Taxes and Charges Template",
diff --git a/erpnext/accounts/general_ledger.py b/erpnext/accounts/general_ledger.py
index 1c8ac2f..2e82886 100644
--- a/erpnext/accounts/general_ledger.py
+++ b/erpnext/accounts/general_ledger.py
@@ -13,9 +13,13 @@
 from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
 	get_accounting_dimensions,
 )
+from erpnext.accounts.doctype.accounting_dimension_filter.accounting_dimension_filter import (
+	get_dimension_filter_map,
+)
 from erpnext.accounts.doctype.accounting_period.accounting_period import ClosedAccountingPeriod
 from erpnext.accounts.doctype.budget.budget import validate_expense_against_budget
 from erpnext.accounts.utils import create_payment_ledger_entry
+from erpnext.exceptions import InvalidAccountDimensionError, MandatoryAccountDimensionError
 
 
 def make_gl_entries(
@@ -355,6 +359,7 @@
 
 	process_debit_credit_difference(gl_map)
 
+	dimension_filter_map = get_dimension_filter_map()
 	if gl_map:
 		check_freezing_date(gl_map[0]["posting_date"], adv_adj)
 		is_opening = any(d.get("is_opening") == "Yes" for d in gl_map)
@@ -362,6 +367,7 @@
 			validate_against_pcv(is_opening, gl_map[0]["posting_date"], gl_map[0]["company"])
 
 	for entry in gl_map:
+		validate_allowed_dimensions(entry, dimension_filter_map)
 		make_entry(entry, adv_adj, update_outstanding, from_repost)
 
 
@@ -700,3 +706,39 @@
 		where voucher_type=%s and voucher_no=%s and is_cancelled = 0""",
 		(now(), frappe.session.user, voucher_type, voucher_no),
 	)
+
+
+def validate_allowed_dimensions(gl_entry, dimension_filter_map):
+	for key, value in dimension_filter_map.items():
+		dimension = key[0]
+		account = key[1]
+
+		if gl_entry.account == account:
+			if value["is_mandatory"] and not gl_entry.get(dimension):
+				frappe.throw(
+					_("{0} is mandatory for account {1}").format(
+						frappe.bold(frappe.unscrub(dimension)), frappe.bold(gl_entry.account)
+					),
+					MandatoryAccountDimensionError,
+				)
+
+			if value["allow_or_restrict"] == "Allow":
+				if gl_entry.get(dimension) and gl_entry.get(dimension) not in value["allowed_dimensions"]:
+					frappe.throw(
+						_("Invalid value {0} for {1} against account {2}").format(
+							frappe.bold(gl_entry.get(dimension)),
+							frappe.bold(frappe.unscrub(dimension)),
+							frappe.bold(gl_entry.account),
+						),
+						InvalidAccountDimensionError,
+					)
+			else:
+				if gl_entry.get(dimension) and gl_entry.get(dimension) in value["allowed_dimensions"]:
+					frappe.throw(
+						_("Invalid value {0} for {1} against account {2}").format(
+							frappe.bold(gl_entry.get(dimension)),
+							frappe.bold(frappe.unscrub(dimension)),
+							frappe.bold(gl_entry.account),
+						),
+						InvalidAccountDimensionError,
+					)
diff --git a/erpnext/accounts/notification/notification_for_new_fiscal_year/notification_for_new_fiscal_year.md b/erpnext/accounts/notification/notification_for_new_fiscal_year/notification_for_new_fiscal_year.html
similarity index 64%
rename from erpnext/accounts/notification/notification_for_new_fiscal_year/notification_for_new_fiscal_year.md
rename to erpnext/accounts/notification/notification_for_new_fiscal_year/notification_for_new_fiscal_year.html
index c674ab6..0c4a462 100644
--- a/erpnext/accounts/notification/notification_for_new_fiscal_year/notification_for_new_fiscal_year.md
+++ b/erpnext/accounts/notification/notification_for_new_fiscal_year/notification_for_new_fiscal_year.html
@@ -1,3 +1,3 @@
-<h3>{{_("Fiscal Year")}}</h3>
+<h3>{{ _("Fiscal Year") }}</h3>
 
-<p>{{ _("New fiscal year created :- ") }} {{ doc.name }}</p>
\ No newline at end of file
+<p>{{ _("New fiscal year created :- ") }} {{ doc.name }}</p>
diff --git a/erpnext/accounts/notification/notification_for_new_fiscal_year/notification_for_new_fiscal_year.json b/erpnext/accounts/notification/notification_for_new_fiscal_year/notification_for_new_fiscal_year.json
index 4c7faf4..f605ad3 100644
--- a/erpnext/accounts/notification/notification_for_new_fiscal_year/notification_for_new_fiscal_year.json
+++ b/erpnext/accounts/notification/notification_for_new_fiscal_year/notification_for_new_fiscal_year.json
@@ -11,19 +11,21 @@
  "event": "New",
  "idx": 0,
  "is_standard": 1,
- "message": "<h3>{{_(\"Fiscal Year\")}}</h3>\n\n<p>{{ _(\"New fiscal year created :- \") }} {{ doc.name }}</p>",
- "modified": "2018-04-25 14:30:38.588534",
+ "message_type": "HTML",
+ "modified": "2023-11-17 08:54:51.532104",
  "modified_by": "Administrator",
  "module": "Accounts",
  "name": "Notification for new fiscal year",
  "owner": "Administrator",
  "recipients": [
   {
-   "email_by_role": "Accounts User"
+   "receiver_by_role": "Accounts User"
   },
   {
-   "email_by_role": "Accounts Manager"
+   "receiver_by_role": "Accounts Manager"
   }
  ],
+ "send_system_notification": 0,
+ "send_to_all_assignees": 0,
  "subject": "Notification for new fiscal year {{ doc.name }}"
-}
\ No newline at end of file
+}
diff --git a/erpnext/accounts/report/general_ledger/general_ledger.js b/erpnext/accounts/report/general_ledger/general_ledger.js
index 79b5e4d..b7b9d34 100644
--- a/erpnext/accounts/report/general_ledger/general_ledger.js
+++ b/erpnext/accounts/report/general_ledger/general_ledger.js
@@ -203,8 +203,14 @@
 			"fieldname": "show_remarks",
 			"label": __("Show Remarks"),
 			"fieldtype": "Check"
+		},
+		{
+			"fieldname": "ignore_err",
+			"label": __("Ignore Exchange Rate Revaluation Journals"),
+			"fieldtype": "Check"
 		}
 
+
 	]
 }
 
diff --git a/erpnext/accounts/report/general_ledger/general_ledger.py b/erpnext/accounts/report/general_ledger/general_ledger.py
index 110ec75..cea3a7b 100644
--- a/erpnext/accounts/report/general_ledger/general_ledger.py
+++ b/erpnext/accounts/report/general_ledger/general_ledger.py
@@ -241,6 +241,19 @@
 	if filters.get("against_voucher_no"):
 		conditions.append("against_voucher=%(against_voucher_no)s")
 
+	if filters.get("ignore_err"):
+		err_journals = frappe.db.get_all(
+			"Journal Entry",
+			filters={
+				"company": filters.get("company"),
+				"docstatus": 1,
+				"voucher_type": ("in", ["Exchange Rate Revaluation", "Exchange Gain Or Loss"]),
+			},
+			as_list=True,
+		)
+		if err_journals:
+			filters.update({"voucher_no_not_in": [x[0] for x in err_journals]})
+
 	if filters.get("voucher_no_not_in"):
 		conditions.append("voucher_no not in %(voucher_no_not_in)s")
 
diff --git a/erpnext/accounts/report/general_ledger/test_general_ledger.py b/erpnext/accounts/report/general_ledger/test_general_ledger.py
index a8c362e..75f9430 100644
--- a/erpnext/accounts/report/general_ledger/test_general_ledger.py
+++ b/erpnext/accounts/report/general_ledger/test_general_ledger.py
@@ -3,7 +3,7 @@
 
 import frappe
 from frappe.tests.utils import FrappeTestCase
-from frappe.utils import today
+from frappe.utils import flt, today
 
 from erpnext.accounts.report.general_ledger.general_ledger import execute
 
@@ -148,3 +148,105 @@
 		self.assertEqual(data[2]["credit"], 900)
 		self.assertEqual(data[3]["debit"], 100)
 		self.assertEqual(data[3]["credit"], 100)
+
+	def test_ignore_exchange_rate_journals_filter(self):
+		# create a new account with USD currency
+		account_name = "Test Debtors USD"
+		company = "_Test Company"
+		account = frappe.get_doc(
+			{
+				"account_name": account_name,
+				"is_group": 0,
+				"company": company,
+				"root_type": "Asset",
+				"report_type": "Balance Sheet",
+				"account_currency": "USD",
+				"parent_account": "Accounts Receivable - _TC",
+				"account_type": "Receivable",
+				"doctype": "Account",
+			}
+		)
+		account.insert(ignore_if_duplicate=True)
+		# create a JV to debit 1000 USD at 75 exchange rate
+		jv = frappe.new_doc("Journal Entry")
+		jv.posting_date = today()
+		jv.company = company
+		jv.multi_currency = 1
+		jv.cost_center = "_Test Cost Center - _TC"
+		jv.set(
+			"accounts",
+			[
+				{
+					"account": account.name,
+					"party_type": "Customer",
+					"party": "_Test Customer USD",
+					"debit_in_account_currency": 1000,
+					"credit_in_account_currency": 0,
+					"exchange_rate": 75,
+					"cost_center": "_Test Cost Center - _TC",
+				},
+				{
+					"account": "Cash - _TC",
+					"debit_in_account_currency": 0,
+					"credit_in_account_currency": 75000,
+					"cost_center": "_Test Cost Center - _TC",
+				},
+			],
+		)
+		jv.save()
+		jv.submit()
+
+		revaluation = frappe.new_doc("Exchange Rate Revaluation")
+		revaluation.posting_date = today()
+		revaluation.company = company
+		accounts = revaluation.get_accounts_data()
+		revaluation.extend("accounts", accounts)
+		row = revaluation.accounts[0]
+		row.new_exchange_rate = 83
+		row.new_balance_in_base_currency = flt(
+			row.new_exchange_rate * flt(row.balance_in_account_currency)
+		)
+		row.gain_loss = row.new_balance_in_base_currency - flt(row.balance_in_base_currency)
+		revaluation.set_total_gain_loss()
+		revaluation = revaluation.save().submit()
+
+		# post journal entry for Revaluation doc
+		frappe.db.set_value(
+			"Company", company, "unrealized_exchange_gain_loss_account", "_Test Exchange Gain/Loss - _TC"
+		)
+		revaluation_jv = revaluation.make_jv_for_revaluation()
+		revaluation_jv.cost_center = "_Test Cost Center - _TC"
+		for acc in revaluation_jv.get("accounts"):
+			acc.cost_center = "_Test Cost Center - _TC"
+		revaluation_jv.save()
+		revaluation_jv.submit()
+
+		# With ignore_err enabled
+		columns, data = execute(
+			frappe._dict(
+				{
+					"company": company,
+					"from_date": today(),
+					"to_date": today(),
+					"account": [account.name],
+					"group_by": "Group by Voucher (Consolidated)",
+					"ignore_err": True,
+				}
+			)
+		)
+		self.assertNotIn(revaluation_jv.name, set([x.voucher_no for x in data]))
+
+		# Without ignore_err enabled
+		columns, data = execute(
+			frappe._dict(
+				{
+					"company": company,
+					"from_date": today(),
+					"to_date": today(),
+					"account": [account.name],
+					"group_by": "Group by Voucher (Consolidated)",
+					"ignore_err": False,
+				}
+			)
+		)
+		self.assertIn(revaluation_jv.name, set([x.voucher_no for x in data]))
diff --git a/erpnext/accounts/report/trial_balance/trial_balance.js b/erpnext/accounts/report/trial_balance/trial_balance.js
index 2c4c762..5374ac1 100644
--- a/erpnext/accounts/report/trial_balance/trial_balance.js
+++ b/erpnext/accounts/report/trial_balance/trial_balance.js
@@ -78,8 +78,14 @@
 			"options": erpnext.get_presentation_currency_list()
 		},
 		{
-			"fieldname": "with_period_closing_entry",
-			"label": __("Period Closing Entry"),
+			"fieldname": "with_period_closing_entry_for_opening",
+			"label": __("With Period Closing Entry For Opening Balances"),
+			"fieldtype": "Check",
+			"default": 1
+		},
+		{
+			"fieldname": "with_period_closing_entry_for_current_period",
+			"label": __("Period Closing Entry For Current Period"),
 			"fieldtype": "Check",
 			"default": 1
 		},
diff --git a/erpnext/accounts/report/trial_balance/trial_balance.py b/erpnext/accounts/report/trial_balance/trial_balance.py
index 8b7f0bb..2ff0eff 100644
--- a/erpnext/accounts/report/trial_balance/trial_balance.py
+++ b/erpnext/accounts/report/trial_balance/trial_balance.py
@@ -116,7 +116,7 @@
 		max_rgt,
 		filters,
 		gl_entries_by_account,
-		ignore_closing_entries=not flt(filters.with_period_closing_entry),
+		ignore_closing_entries=not flt(filters.with_period_closing_entry_for_current_period),
 		ignore_opening_entries=True,
 	)
 
@@ -249,7 +249,7 @@
 	):
 		opening_balance = opening_balance.where(closing_balance.posting_date >= filters.year_start_date)
 
-	if not flt(filters.with_period_closing_entry):
+	if not flt(filters.with_period_closing_entry_for_opening):
 		if doctype == "Account Closing Balance":
 			opening_balance = opening_balance.where(closing_balance.is_period_closing_voucher_entry == 0)
 		else:
diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py
index 30700d0..64bc39a 100644
--- a/erpnext/accounts/utils.py
+++ b/erpnext/accounts/utils.py
@@ -237,7 +237,7 @@
 			)
 
 		else:
-			cond.append("""gle.cost_center = %s """ % (frappe.db.escape(cost_center, percent=False),))
+			cond.append("""gle.cost_center = %s """ % (frappe.db.escape(cost_center),))
 
 	if account:
 		if not (frappe.flags.ignore_account_permission or ignore_account_permission):
@@ -258,7 +258,7 @@
 			if acc.account_currency == frappe.get_cached_value("Company", acc.company, "default_currency"):
 				in_account_currency = False
 		else:
-			cond.append("""gle.account = %s """ % (frappe.db.escape(account, percent=False),))
+			cond.append("""gle.account = %s """ % (frappe.db.escape(account),))
 
 	if account_type:
 		accounts = frappe.db.get_all(
@@ -278,11 +278,11 @@
 	if party_type and party:
 		cond.append(
 			"""gle.party_type = %s and gle.party = %s """
-			% (frappe.db.escape(party_type), frappe.db.escape(party, percent=False))
+			% (frappe.db.escape(party_type), frappe.db.escape(party))
 		)
 
 	if company:
-		cond.append("""gle.company = %s """ % (frappe.db.escape(company, percent=False)))
+		cond.append("""gle.company = %s """ % (frappe.db.escape(company)))
 
 	if account or (party_type and party) or account_type:
 		precision = get_currency_precision()
@@ -348,7 +348,7 @@
 				% (acc.lft, acc.rgt)
 			)
 		else:
-			cond.append("""gle.account = %s """ % (frappe.db.escape(account, percent=False),))
+			cond.append("""gle.account = %s """ % (frappe.db.escape(account),))
 
 		entries = frappe.db.sql(
 			"""
diff --git a/erpnext/buying/doctype/buying_settings/buying_settings.json b/erpnext/buying/doctype/buying_settings/buying_settings.json
index ddcbd55..d98a00f 100644
--- a/erpnext/buying/doctype/buying_settings/buying_settings.json
+++ b/erpnext/buying/doctype/buying_settings/buying_settings.json
@@ -1,7 +1,6 @@
 {
  "actions": [],
  "creation": "2013-06-25 11:04:03",
- "description": "Settings for Buying Module",
  "doctype": "DocType",
  "document_type": "Other",
  "engine": "InnoDB",
@@ -152,6 +151,7 @@
   },
   {
    "default": "1",
+   "depends_on": "eval: frappe.boot.versions && frappe.boot.versions.payments",
    "fieldname": "show_pay_button",
    "fieldtype": "Check",
    "label": "Show Pay Button in Purchase Order Portal"
@@ -214,7 +214,7 @@
  "index_web_pages_for_search": 1,
  "issingle": 1,
  "links": [],
- "modified": "2024-01-12 16:42:01.894346",
+ "modified": "2024-01-31 13:34:18.101256",
  "modified_by": "Administrator",
  "module": "Buying",
  "name": "Buying Settings",
diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.py b/erpnext/buying/doctype/purchase_order/purchase_order.py
index 4efbb27..4d94868 100644
--- a/erpnext/buying/doctype/purchase_order/purchase_order.py
+++ b/erpnext/buying/doctype/purchase_order/purchase_order.py
@@ -457,6 +457,7 @@
 		self.update_ordered_qty()
 		self.update_reserved_qty_for_subcontract()
 		self.update_subcontracting_order_status()
+		self.update_blanket_order()
 		self.notify_update()
 		clear_doctype_notifications(self)
 
@@ -644,6 +645,7 @@
 				update_sco_status(sco, "Closed" if self.status == "Closed" else None)
 
 
+@frappe.request_cache
 def item_last_purchase_rate(name, conversion_rate, item_code, conversion_factor=1.0):
 	"""get last purchase rate for an item"""
 
diff --git a/erpnext/buying/doctype/purchase_order/test_purchase_order.py b/erpnext/buying/doctype/purchase_order/test_purchase_order.py
index 5405799..a30de68 100644
--- a/erpnext/buying/doctype/purchase_order/test_purchase_order.py
+++ b/erpnext/buying/doctype/purchase_order/test_purchase_order.py
@@ -822,6 +822,30 @@
 		# To test if the PO does NOT have a Blanket Order
 		self.assertEqual(po_doc.items[0].blanket_order, None)
 
+	def test_blanket_order_on_po_close_and_open(self):
+		# Step - 1: Create Blanket Order
+		bo = make_blanket_order(blanket_order_type="Purchasing", quantity=10, rate=10)
+
+		# Step - 2: Create Purchase Order
+		po = create_purchase_order(
+			item_code="_Test Item", qty=5, against_blanket_order=1, against_blanket=bo.name
+		)
+
+		bo.load_from_db()
+		self.assertEqual(bo.items[0].ordered_qty, 5)
+
+		# Step - 3: Close Purchase Order
+		po.update_status("Closed")
+
+		bo.load_from_db()
+		self.assertEqual(bo.items[0].ordered_qty, 0)
+
+		# Step - 4: Re-Open Purchase Order
+		po.update_status("Re-open")
+
+		bo.load_from_db()
+		self.assertEqual(bo.items[0].ordered_qty, 5)
+
 	def test_payment_terms_are_fetched_when_creating_purchase_invoice(self):
 		from erpnext.accounts.doctype.payment_entry.test_payment_entry import (
 			create_payment_terms_template,
@@ -1148,6 +1172,7 @@
 				"schedule_date": add_days(nowdate(), 1),
 				"include_exploded_items": args.get("include_exploded_items", 1),
 				"against_blanket_order": args.against_blanket_order,
+				"against_blanket": args.against_blanket,
 				"material_request": args.material_request,
 				"material_request_item": args.material_request_item,
 			},
diff --git a/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json b/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json
index 5a24cc2..e3e8def 100644
--- a/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json
+++ b/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json
@@ -545,7 +545,6 @@
    "fieldname": "blanket_order",
    "fieldtype": "Link",
    "label": "Blanket Order",
-   "no_copy": 1,
    "options": "Blanket Order"
   },
   {
@@ -553,7 +552,6 @@
    "fieldname": "blanket_order_rate",
    "fieldtype": "Currency",
    "label": "Blanket Order Rate",
-   "no_copy": 1,
    "print_hide": 1,
    "read_only": 1
   },
@@ -917,7 +915,7 @@
  "index_web_pages_for_search": 1,
  "istable": 1,
  "links": [],
- "modified": "2023-11-24 13:24:41.298416",
+ "modified": "2024-02-05 11:23:24.859435",
  "modified_by": "Administrator",
  "module": "Buying",
  "name": "Purchase Order Item",
diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py
index 1ed719d..e2b0ee5 100644
--- a/erpnext/controllers/accounts_controller.py
+++ b/erpnext/controllers/accounts_controller.py
@@ -202,6 +202,7 @@
 		self.validate_party()
 		self.validate_currency()
 		self.validate_party_account_currency()
+		self.validate_return_against_account()
 
 		if self.doctype in ["Purchase Invoice", "Sales Invoice"]:
 			if invalid_advances := [
@@ -350,6 +351,20 @@
 		for bundle in bundles:
 			frappe.delete_doc("Serial and Batch Bundle", bundle.name)
 
+	def validate_return_against_account(self):
+		if (
+			self.doctype in ["Sales Invoice", "Purchase Invoice"] and self.is_return and self.return_against
+		):
+			cr_dr_account_field = "debit_to" if self.doctype == "Sales Invoice" else "credit_to"
+			cr_dr_account_label = "Debit To" if self.doctype == "Sales Invoice" else "Credit To"
+			cr_dr_account = self.get(cr_dr_account_field)
+			if frappe.get_value(self.doctype, self.return_against, cr_dr_account_field) != cr_dr_account:
+				frappe.throw(
+					_("'{0}' account: '{1}' should match the Return Against Invoice").format(
+						frappe.bold(cr_dr_account_label), frappe.bold(cr_dr_account)
+					)
+				)
+
 	def validate_deferred_income_expense_account(self):
 		field_map = {
 			"Sales Invoice": "deferred_revenue_account",
@@ -678,7 +693,7 @@
 					if self.get("is_subcontracted"):
 						args["is_subcontracted"] = self.is_subcontracted
 
-					ret = get_item_details(args, self, for_validate=True, overwrite_warehouse=False)
+					ret = get_item_details(args, self, for_validate=for_validate, overwrite_warehouse=False)
 
 					for fieldname, value in ret.items():
 						if item.meta.get_field(fieldname) and value is not None:
diff --git a/erpnext/controllers/selling_controller.py b/erpnext/controllers/selling_controller.py
index 8c43842..dc49023 100644
--- a/erpnext/controllers/selling_controller.py
+++ b/erpnext/controllers/selling_controller.py
@@ -599,7 +599,7 @@
 		if self.doctype in ["Sales Order", "Quotation"]:
 			for item in self.items:
 				item.gross_profit = flt(
-					((item.base_rate - item.valuation_rate) * item.stock_qty), self.precision("amount", item)
+					((item.base_rate - flt(item.valuation_rate)) * item.stock_qty), self.precision("amount", item)
 				)
 
 	def set_customer_address(self):
diff --git a/erpnext/controllers/taxes_and_totals.py b/erpnext/controllers/taxes_and_totals.py
index 8cb1a0e..3d7a947 100644
--- a/erpnext/controllers/taxes_and_totals.py
+++ b/erpnext/controllers/taxes_and_totals.py
@@ -98,6 +98,7 @@
 				item_doc = frappe.get_cached_doc("Item", item.item_code)
 				args = {
 					"net_rate": item.net_rate or item.rate,
+					"base_net_rate": item.base_net_rate or item.base_rate,
 					"tax_category": self.doc.get("tax_category"),
 					"posting_date": self.doc.get("posting_date"),
 					"bill_date": self.doc.get("bill_date"),
diff --git a/erpnext/manufacturing/doctype/job_card/job_card.py b/erpnext/manufacturing/doctype/job_card/job_card.py
index 23650b6..079350b 100644
--- a/erpnext/manufacturing/doctype/job_card/job_card.py
+++ b/erpnext/manufacturing/doctype/job_card/job_card.py
@@ -955,6 +955,14 @@
 		if update_status:
 			self.db_set("status", self.status)
 
+		if self.status in ["Completed", "Work In Progress"]:
+			status = {
+				"Completed": "Off",
+				"Work In Progress": "Production",
+			}.get(self.status)
+
+			self.update_status_in_workstation(status)
+
 	def set_wip_warehouse(self):
 		if not self.wip_warehouse:
 			self.wip_warehouse = frappe.db.get_single_value(
@@ -1035,6 +1043,12 @@
 
 		return False
 
+	def update_status_in_workstation(self, status):
+		if not self.workstation:
+			return
+
+		frappe.db.set_value("Workstation", self.workstation, "status", status)
+
 
 @frappe.whitelist()
 def make_time_log(args):
diff --git a/erpnext/manufacturing/doctype/plant_floor/__init__.py b/erpnext/manufacturing/doctype/plant_floor/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/manufacturing/doctype/plant_floor/__init__.py
diff --git a/erpnext/manufacturing/doctype/plant_floor/plant_floor.js b/erpnext/manufacturing/doctype/plant_floor/plant_floor.js
new file mode 100644
index 0000000..67e5acd
--- /dev/null
+++ b/erpnext/manufacturing/doctype/plant_floor/plant_floor.js
@@ -0,0 +1,256 @@
+// Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+
+frappe.ui.form.on("Plant Floor", {
+	setup(frm) {
+		frm.trigger("setup_queries");
+	},
+
+	setup_queries(frm) {
+		frm.set_query("warehouse", (doc) => {
+			if (!doc.company) {
+				frappe.throw(__("Please select Company first"));
+			}
+
+			return {
+				filters: {
+					"is_group": 0,
+					"company": doc.company
+				}
+			}
+		});
+	},
+
+	refresh(frm) {
+		frm.trigger('prepare_stock_dashboard')
+		frm.trigger('prepare_workstation_dashboard')
+	},
+
+	prepare_workstation_dashboard(frm) {
+		let wrapper = $(frm.fields_dict["plant_dashboard"].wrapper);
+		wrapper.empty();
+
+		frappe.visual_plant_floor = new frappe.ui.VisualPlantFloor({
+			wrapper: wrapper,
+			skip_filters: true,
+			plant_floor: frm.doc.name,
+		});
+	},
+
+	prepare_stock_dashboard(frm) {
+		if (!frm.doc.warehouse) {
+			return;
+		}
+
+		let wrapper = $(frm.fields_dict["stock_summary"].wrapper);
+		wrapper.empty();
+
+		frappe.visual_stock = new VisualStock({
+			wrapper: wrapper,
+			frm: frm,
+		});
+	},
+});
+
+
+class VisualStock {
+	constructor(opts) {
+		Object.assign(this, opts);
+		this.make();
+	}
+
+	make() {
+		this.prepare_filters();
+		this.prepare_stock_summary({
+			start:0
+		});
+	}
+
+	prepare_filters() {
+		this.wrapper.append(`
+			<div class="row">
+				<div class="col-sm-12 filter-section section-body">
+
+				</div>
+			</div>
+		`);
+
+		this.item_filter = frappe.ui.form.make_control({
+			df: {
+				fieldtype: "Link",
+				fieldname: "item_code",
+				placeholder: __("Item"),
+				options: "Item",
+				onchange: () => this.prepare_stock_summary({
+					start:0,
+					item_code: this.item_filter.value
+				})
+			},
+			parent: this.wrapper.find('.filter-section'),
+			render_input: true,
+		});
+
+		this.item_filter.$wrapper.addClass('form-column col-sm-3');
+		this.item_filter.$wrapper.find('.clearfix').hide();
+
+		this.item_group_filter = frappe.ui.form.make_control({
+			df: {
+				fieldtype: "Link",
+				fieldname: "item_group",
+				placeholder: __("Item Group"),
+				options: "Item Group",
+				change: () => this.prepare_stock_summary({
+					start:0,
+					item_group: this.item_group_filter.value
+				})
+			},
+			parent: this.wrapper.find('.filter-section'),
+			render_input: true,
+		});
+
+		this.item_group_filter.$wrapper.addClass('form-column col-sm-3');
+		this.item_group_filter.$wrapper.find('.clearfix').hide();
+	}
+
+	prepare_stock_summary(args) {
+		let {start, item_code, item_group} = args;
+
+		this.get_stock_summary(start, item_code, item_group).then(stock_summary => {
+			this.wrapper.find('.stock-summary-container').remove();
+			this.wrapper.append(`<div class="col-sm-12 stock-summary-container" style="margin-bottom:20px"></div>`);
+			this.stock_summary = stock_summary.message;
+			this.render_stock_summary();
+			this.bind_events();
+		});
+	}
+
+	async get_stock_summary(start, item_code, item_group) {
+		let stock_summary = await frappe.call({
+			method: "erpnext.manufacturing.doctype.plant_floor.plant_floor.get_stock_summary",
+			args: {
+				warehouse: this.frm.doc.warehouse,
+				start: start,
+				item_code: item_code,
+				item_group: item_group
+			}
+		});
+
+		return stock_summary;
+	}
+
+	render_stock_summary() {
+		let template = frappe.render_template("stock_summary_template", {
+			stock_summary: this.stock_summary
+		});
+
+		this.wrapper.find('.stock-summary-container').append(template);
+	}
+
+	bind_events() {
+		this.wrapper.find('.btn-add').click((e) => {
+			this.item_code = decodeURI($(e.currentTarget).attr('data-item-code'));
+
+			this.make_stock_entry([
+				{
+					label: __("For Item"),
+					fieldname: "item_code",
+					fieldtype: "Data",
+					read_only: 1,
+					default: this.item_code
+				},
+				{
+					label: __("Quantity"),
+					fieldname: "qty",
+					fieldtype: "Float",
+					reqd: 1
+				}
+			], __("Add Stock"), "Material Receipt")
+		});
+
+		this.wrapper.find('.btn-move').click((e) => {
+			this.item_code = decodeURI($(e.currentTarget).attr('data-item-code'));
+
+			this.make_stock_entry([
+				{
+					label: __("For Item"),
+					fieldname: "item_code",
+					fieldtype: "Data",
+					read_only: 1,
+					default: this.item_code
+				},
+				{
+					label: __("Quantity"),
+					fieldname: "qty",
+					fieldtype: "Float",
+					reqd: 1
+				},
+				{
+					label: __("To Warehouse"),
+					fieldname: "to_warehouse",
+					fieldtype: "Link",
+					options: "Warehouse",
+					reqd: 1,
+					get_query: () => {
+						return {
+							filters: {
+								"is_group": 0,
+								"company": this.frm.doc.company
+							}
+						}
+					}
+				}
+			], __("Move Stock"), "Material Transfer")
+		});
+	}
+
+	make_stock_entry(fields, title, stock_entry_type) {
+		frappe.prompt(fields,
+			(values) => {
+				this.values = values;
+				this.stock_entry_type = stock_entry_type;
+				this.update_values();
+
+				this.frm.call({
+					method: "make_stock_entry",
+					doc: this.frm.doc,
+					args: {
+						kwargs: this.values,
+					},
+					callback: (r) => {
+						if (!r.exc) {
+							var doc = frappe.model.sync(r.message);
+							frappe.set_route("Form", r.message.doctype, r.message.name);
+						}
+					}
+				})
+			}, __(title), __("Create")
+		);
+	}
+
+	update_values() {
+		if (!this.values.qty) {
+			frappe.throw(__("Quantity is required"));
+		}
+
+		let from_warehouse = "";
+		let to_warehouse = "";
+
+		if (this.stock_entry_type == "Material Receipt") {
+			to_warehouse = this.frm.doc.warehouse;
+		} else {
+			from_warehouse = this.frm.doc.warehouse;
+			to_warehouse = this.values.to_warehouse;
+		}
+
+		this.values = {
+			...this.values,
+			...{
+				"company": this.frm.doc.company,
+				"item_code": this.item_code,
+				"from_warehouse": from_warehouse,
+				"to_warehouse": to_warehouse,
+				"purpose": this.stock_entry_type,
+			}
+		}
+	}
+}
\ No newline at end of file
diff --git a/erpnext/manufacturing/doctype/plant_floor/plant_floor.json b/erpnext/manufacturing/doctype/plant_floor/plant_floor.json
new file mode 100644
index 0000000..be0052c
--- /dev/null
+++ b/erpnext/manufacturing/doctype/plant_floor/plant_floor.json
@@ -0,0 +1,97 @@
+{
+ "actions": [],
+ "allow_rename": 1,
+ "autoname": "field:floor_name",
+ "creation": "2023-10-06 15:06:07.976066",
+ "default_view": "List",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+  "workstations_tab",
+  "plant_dashboard",
+  "stock_summary_tab",
+  "stock_summary",
+  "details_tab",
+  "column_break_mvbx",
+  "floor_name",
+  "company",
+  "warehouse"
+ ],
+ "fields": [
+  {
+   "fieldname": "floor_name",
+   "fieldtype": "Data",
+   "label": "Floor Name",
+   "unique": 1
+  },
+  {
+   "depends_on": "eval:!doc.__islocal",
+   "fieldname": "workstations_tab",
+   "fieldtype": "Tab Break",
+   "label": "Workstations"
+  },
+  {
+   "fieldname": "plant_dashboard",
+   "fieldtype": "HTML",
+   "label": "Plant Dashboard"
+  },
+  {
+   "fieldname": "details_tab",
+   "fieldtype": "Tab Break",
+   "label": "Floor"
+  },
+  {
+   "fieldname": "column_break_mvbx",
+   "fieldtype": "Column Break"
+  },
+  {
+   "fieldname": "warehouse",
+   "fieldtype": "Link",
+   "label": "Warehouse",
+   "options": "Warehouse"
+  },
+  {
+   "depends_on": "eval:!doc.__islocal && doc.warehouse",
+   "fieldname": "stock_summary_tab",
+   "fieldtype": "Tab Break",
+   "label": "Stock Summary"
+  },
+  {
+   "fieldname": "stock_summary",
+   "fieldtype": "HTML",
+   "label": "Stock Summary"
+  },
+  {
+   "fieldname": "company",
+   "fieldtype": "Link",
+   "label": "Company",
+   "options": "Company"
+  }
+ ],
+ "index_web_pages_for_search": 1,
+ "links": [],
+ "modified": "2024-01-30 11:59:07.508535",
+ "modified_by": "Administrator",
+ "module": "Manufacturing",
+ "name": "Plant Floor",
+ "naming_rule": "By fieldname",
+ "owner": "Administrator",
+ "permissions": [
+  {
+   "create": 1,
+   "delete": 1,
+   "email": 1,
+   "export": 1,
+   "print": 1,
+   "read": 1,
+   "report": 1,
+   "role": "System Manager",
+   "share": 1,
+   "write": 1
+  }
+ ],
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "states": []
+}
\ No newline at end of file
diff --git a/erpnext/manufacturing/doctype/plant_floor/plant_floor.py b/erpnext/manufacturing/doctype/plant_floor/plant_floor.py
new file mode 100644
index 0000000..d30b7d1
--- /dev/null
+++ b/erpnext/manufacturing/doctype/plant_floor/plant_floor.py
@@ -0,0 +1,129 @@
+# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+import frappe
+from frappe.model.document import Document
+from frappe.query_builder import Order
+from frappe.utils import get_link_to_form, nowdate, nowtime
+
+
+class PlantFloor(Document):
+	# begin: auto-generated types
+	# This code is auto-generated. Do not modify anything in this block.
+
+	from typing import TYPE_CHECKING
+
+	if TYPE_CHECKING:
+		from frappe.types import DF
+
+		company: DF.Link | None
+		floor_name: DF.Data | None
+		warehouse: DF.Link | None
+	# end: auto-generated types
+
+	@frappe.whitelist()
+	def make_stock_entry(self, kwargs):
+		if isinstance(kwargs, str):
+			kwargs = frappe.parse_json(kwargs)
+
+		if isinstance(kwargs, dict):
+			kwargs = frappe._dict(kwargs)
+
+		stock_entry = frappe.new_doc("Stock Entry")
+		stock_entry.update(
+			{
+				"company": kwargs.company,
+				"from_warehouse": kwargs.from_warehouse,
+				"to_warehouse": kwargs.to_warehouse,
+				"purpose": kwargs.purpose,
+				"stock_entry_type": kwargs.purpose,
+				"posting_date": nowdate(),
+				"posting_time": nowtime(),
+				"items": self.get_item_details(kwargs),
+			}
+		)
+
+		stock_entry.set_missing_values()
+
+		return stock_entry
+
+	def get_item_details(self, kwargs) -> list[dict]:
+		item_details = frappe.db.get_value(
+			"Item", kwargs.item_code, ["item_name", "stock_uom", "item_group", "description"], as_dict=True
+		)
+		item_details.update(
+			{
+				"qty": kwargs.qty,
+				"uom": item_details.stock_uom,
+				"item_code": kwargs.item_code,
+				"conversion_factor": 1,
+				"s_warehouse": kwargs.from_warehouse,
+				"t_warehouse": kwargs.to_warehouse,
+			}
+		)
+
+		return [item_details]
+
+
+@frappe.whitelist()
+def get_stock_summary(warehouse, start=0, item_code=None, item_group=None):
+	stock_details = get_stock_details(
+		warehouse, start=start, item_code=item_code, item_group=item_group
+	)
+
+	max_count = 0.0
+	for d in stock_details:
+		d.actual_or_pending = (
+			d.projected_qty
+			+ d.reserved_qty
+			+ d.reserved_qty_for_production
+			+ d.reserved_qty_for_sub_contract
+		)
+		d.pending_qty = 0
+		d.total_reserved = (
+			d.reserved_qty + d.reserved_qty_for_production + d.reserved_qty_for_sub_contract
+		)
+		if d.actual_or_pending > d.actual_qty:
+			d.pending_qty = d.actual_or_pending - d.actual_qty
+
+		d.max_count = max(d.actual_or_pending, d.actual_qty, d.total_reserved, max_count)
+		max_count = d.max_count
+		d.item_link = get_link_to_form("Item", d.item_code)
+
+	return stock_details
+
+
+def get_stock_details(warehouse, start=0, item_code=None, item_group=None):
+	item_table = frappe.qb.DocType("Item")
+	bin_table = frappe.qb.DocType("Bin")
+
+	query = (
+		frappe.qb.from_(bin_table)
+		.inner_join(item_table)
+		.on(bin_table.item_code == item_table.name)
+		.select(
+			bin_table.item_code,
+			bin_table.actual_qty,
+			bin_table.projected_qty,
+			bin_table.reserved_qty,
+			bin_table.reserved_qty_for_production,
+			bin_table.reserved_qty_for_sub_contract,
+			bin_table.reserved_qty_for_production_plan,
+			bin_table.reserved_stock,
+			item_table.item_name,
+			item_table.item_group,
+			item_table.image,
+		)
+		.where(bin_table.warehouse == warehouse)
+		.limit(20)
+		.offset(start)
+		.orderby(bin_table.actual_qty, order=Order.desc)
+	)
+
+	if item_code:
+		query = query.where(bin_table.item_code == item_code)
+
+	if item_group:
+		query = query.where(item_table.item_group == item_group)
+
+	return query.run(as_dict=True)
diff --git a/erpnext/manufacturing/doctype/plant_floor/stock_summary_template.html b/erpnext/manufacturing/doctype/plant_floor/stock_summary_template.html
new file mode 100644
index 0000000..8824c98
--- /dev/null
+++ b/erpnext/manufacturing/doctype/plant_floor/stock_summary_template.html
@@ -0,0 +1,61 @@
+{% $.each(stock_summary, (idx, row) => { %}
+<div class="row" style="border-bottom:1px solid var(--border-color); padding:4px 5px; margin-top: 3px;margin-bottom: 3px;">
+	<div class="col-sm-1">
+		{% if(row.image) { %}
+			<img style="width:50px;height:50px;" src="{{row.image}}">
+		{% } else { %}
+			<div style="width:50px;height:50px;background-color:var(--control-bg);text-align:center;padding-top:15px">{{frappe.get_abbr(row.item_code, 2)}}</div>
+		{% } %}
+	</div>
+	<div class="col-sm-3">
+		{% if (row.item_code === row.item_name) { %}
+			{{row.item_link}}
+		{% } else { %}
+			{{row.item_link}}
+			<p>
+				{{row.item_name}}
+			</p>
+		{% } %}
+
+	</div>
+	<div class="col-sm-1" title="{{ __('Actual Qty') }}">
+		{{ frappe.format(row.actual_qty, { fieldtype: "Float"})}}
+	</div>
+	<div class="col-sm-1" title="{{ __('Reserved Stock') }}">
+		{{ frappe.format(row.reserved_stock, { fieldtype: "Float"})}}
+	</div>
+	<div class="col-sm-4 small">
+		<span class="inline-graph">
+			<span class="inline-graph-half" title="{{ __("Reserved Qty") }}">
+				<span class="inline-graph-count">{{ row.total_reserved }}</span>
+				<span class="inline-graph-bar">
+					<span class="inline-graph-bar-inner"
+						style="width: {{ cint(Math.abs(row.total_reserved)/row.max_count * 100) || 5 }}%">
+					</span>
+				</span>
+			</span>
+			<span class="inline-graph-half" title="{{ __("Actual Qty {0} / Waiting Qty {1}", [row.actual_qty, row.pending_qty]) }}">
+				<span class="inline-graph-count">
+					{{ row.actual_qty }} {{ (row.pending_qty > 0) ? ("(" + row.pending_qty+ ")") : "" }}
+				</span>
+				<span class="inline-graph-bar">
+					<span class="inline-graph-bar-inner dark"
+						style="width: {{ cint(row.actual_qty/row.max_count * 100) }}%">
+					</span>
+					{% if row.pending_qty > 0 %}
+					<span class="inline-graph-bar-inner" title="{{ __("Projected Qty") }}"
+						style="width: {{ cint(row.pending_qty/row.max_count * 100) }}%">
+					</span>
+					{% endif %}
+				</span>
+			</span>
+		</span>
+	</div>
+	<div class="col-sm-1">
+		<button style="margin-left: 7px;" class="btn btn-default btn-xs btn-add" data-item-code="{{ escape(row.item_code) }}">Add</button>
+	</div>
+	<div class="col-sm-1">
+		<button style="margin-left: 7px;" class="btn btn-default btn-xs btn-move" data-item-code="{{ escape(row.item_code) }}">Move</button>
+	</div>
+</div>
+{% }); %}
\ No newline at end of file
diff --git a/erpnext/manufacturing/doctype/plant_floor/test_plant_floor.py b/erpnext/manufacturing/doctype/plant_floor/test_plant_floor.py
new file mode 100644
index 0000000..2fac211
--- /dev/null
+++ b/erpnext/manufacturing/doctype/plant_floor/test_plant_floor.py
@@ -0,0 +1,9 @@
+# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and Contributors
+# See license.txt
+
+# import frappe
+from frappe.tests.utils import FrappeTestCase
+
+
+class TestPlantFloor(FrappeTestCase):
+	pass
diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.py b/erpnext/manufacturing/doctype/production_plan/production_plan.py
index 5dc5c38..f0392be 100644
--- a/erpnext/manufacturing/doctype/production_plan/production_plan.py
+++ b/erpnext/manufacturing/doctype/production_plan/production_plan.py
@@ -1334,10 +1334,10 @@
 	)
 
 	date_field_mapper = {
-		"from_date": self.from_date >= so.transaction_date,
-		"to_date": self.to_date <= so.transaction_date,
-		"from_delivery_date": self.from_delivery_date >= so_item.delivery_date,
-		"to_delivery_date": self.to_delivery_date <= so_item.delivery_date,
+		"from_date": so.transaction_date >= self.from_date,
+		"to_date": so.transaction_date <= self.to_date,
+		"from_delivery_date": so_item.delivery_date >= self.from_delivery_date,
+		"to_delivery_date": so_item.delivery_date <= self.to_delivery_date,
 	}
 
 	for field, value in date_field_mapper.items():
diff --git a/erpnext/manufacturing/doctype/work_order/work_order.py b/erpnext/manufacturing/doctype/work_order/work_order.py
index aa7bc5b..39beb36 100644
--- a/erpnext/manufacturing/doctype/work_order/work_order.py
+++ b/erpnext/manufacturing/doctype/work_order/work_order.py
@@ -1511,14 +1511,14 @@
 
 
 def validate_operation_data(row):
-	if row.get("qty") <= 0:
+	if flt(row.get("qty")) <= 0:
 		frappe.throw(
 			_("Quantity to Manufacture can not be zero for the operation {0}").format(
 				frappe.bold(row.get("operation"))
 			)
 		)
 
-	if row.get("qty") > row.get("pending_qty"):
+	if flt(row.get("qty")) > flt(row.get("pending_qty")):
 		frappe.throw(
 			_("For operation {0}: Quantity ({1}) can not be greater than pending quantity({2})").format(
 				frappe.bold(row.get("operation")),
diff --git a/erpnext/manufacturing/doctype/workstation/workstation.js b/erpnext/manufacturing/doctype/workstation/workstation.js
index f830b17..e3ad3fe 100644
--- a/erpnext/manufacturing/doctype/workstation/workstation.js
+++ b/erpnext/manufacturing/doctype/workstation/workstation.js
@@ -2,6 +2,28 @@
 // License: GNU General Public License v3. See license.txt
 
 frappe.ui.form.on("Workstation", {
+	set_illustration_image(frm) {
+		let status_image_field = frm.doc.status == "Production" ? frm.doc.on_status_image : frm.doc.off_status_image;
+		if (status_image_field) {
+			frm.sidebar.image_wrapper.find(".sidebar-image").attr("src", status_image_field);
+		}
+	},
+
+	refresh(frm) {
+		frm.trigger("set_illustration_image");
+		frm.trigger("prepapre_dashboard");
+	},
+
+	prepapre_dashboard(frm) {
+		let $parent = $(frm.fields_dict["workstation_dashboard"].wrapper);
+		$parent.empty();
+
+		let workstation_dashboard = new WorkstationDashboard({
+			wrapper: $parent,
+			frm: frm
+		});
+	},
+
 	onload(frm) {
 		if(frm.is_new())
 		{
@@ -54,3 +76,243 @@
 
 
 ];
+
+
+class WorkstationDashboard {
+	constructor({ wrapper, frm }) {
+		this.$wrapper = $(wrapper);
+		this.frm = frm;
+
+		this.prepapre_dashboard();
+	}
+
+	prepapre_dashboard() {
+		frappe.call({
+			method: "erpnext.manufacturing.doctype.workstation.workstation.get_job_cards",
+			args: {
+				workstation: this.frm.doc.name
+			},
+			callback: (r) => {
+				if (r.message) {
+					this.job_cards = r.message;
+					this.render_job_cards();
+				}
+			}
+		});
+	}
+
+	render_job_cards() {
+		let template  = frappe.render_template("workstation_job_card", {
+			data: this.job_cards
+		});
+
+		this.$wrapper.html(template);
+		this.prepare_timer();
+		this.toggle_job_card();
+		this.bind_events();
+	}
+
+	toggle_job_card() {
+		this.$wrapper.find(".collapse-indicator-job").on("click", (e) => {
+			$(e.currentTarget).closest(".form-dashboard-section").find(".section-body-job-card").toggleClass("hide")
+			if ($(e.currentTarget).closest(".form-dashboard-section").find(".section-body-job-card").hasClass("hide"))
+				$(e.currentTarget).html(frappe.utils.icon("es-line-down", "sm", "mb-1"))
+			else
+				$(e.currentTarget).html(frappe.utils.icon("es-line-up", "sm", "mb-1"))
+		});
+	}
+
+	bind_events() {
+		this.$wrapper.find(".make-material-request").on("click", (e) => {
+			let job_card = $(e.currentTarget).attr("job-card");
+			this.make_material_request(job_card);
+		});
+
+		this.$wrapper.find(".btn-start").on("click", (e) => {
+			let job_card = $(e.currentTarget).attr("job-card");
+			this.start_job(job_card);
+		});
+
+		this.$wrapper.find(".btn-complete").on("click", (e) => {
+			let job_card = $(e.currentTarget).attr("job-card");
+			let pending_qty = flt($(e.currentTarget).attr("pending-qty"));
+			this.complete_job(job_card, pending_qty);
+		});
+	}
+
+	start_job(job_card) {
+		let me = this;
+		frappe.prompt([
+			{
+				fieldtype: 'Datetime',
+				label: __('Start Time'),
+				fieldname: 'start_time',
+				reqd: 1,
+				default: frappe.datetime.now_datetime()
+			},
+			{
+				label: __('Operator'),
+				fieldname: 'employee',
+				fieldtype: 'Link',
+				options: 'Employee',
+			}
+		], data => {
+			this.frm.call({
+				method: "start_job",
+				doc: this.frm.doc,
+				args: {
+					job_card: job_card,
+					from_time: data.start_time,
+					employee: data.employee,
+				},
+				callback(r) {
+					if (r.message) {
+						me.job_cards = [r.message];
+						me.prepare_timer()
+						me.update_job_card_details();
+					}
+				}
+			});
+		}, __("Enter Value"), __("Start Job"));
+	}
+
+	complete_job(job_card, qty_to_manufacture) {
+		let me = this;
+		let fields = [
+			{
+				fieldtype: 'Float',
+				label: __('Completed Quantity'),
+				fieldname: 'qty',
+				reqd: 1,
+				default: flt(qty_to_manufacture || 0)
+			},
+			{
+				fieldtype: 'Datetime',
+				label: __('End Time'),
+				fieldname: 'end_time',
+				default: frappe.datetime.now_datetime()
+			},
+		];
+
+		frappe.prompt(fields, data => {
+			if (data.qty <= 0) {
+				frappe.throw(__("Quantity should be greater than 0"));
+			}
+
+			this.frm.call({
+				method: "complete_job",
+				doc: this.frm.doc,
+				args: {
+					job_card: job_card,
+					qty: data.qty,
+					to_time: data.end_time,
+				},
+				callback: function(r) {
+					if (r.message) {
+						me.job_cards = [r.message];
+						me.prepare_timer()
+						me.update_job_card_details();
+					}
+				}
+			});
+		}, __("Enter Value"), __("Submit"));
+	}
+
+	make_material_request(job_card) {
+		frappe.call({
+			method: "erpnext.manufacturing.doctype.job_card.job_card.make_material_request",
+			args: {
+				source_name: job_card,
+			},
+			callback: (r) => {
+				if (r.message) {
+					var doc = frappe.model.sync(r.message)[0];
+					frappe.set_route("Form", doc.doctype, doc.name);
+				}
+			}
+		});
+	}
+
+	prepare_timer() {
+		this.job_cards.forEach((data) => {
+			if (data.time_logs?.length) {
+				data._current_time = this.get_current_time(data);
+				if (data.time_logs[cint(data.time_logs.length) - 1].to_time) {
+					this.updateStopwatch(data);
+				} else {
+					this.initialiseTimer(data);
+				}
+			}
+		});
+	}
+
+	update_job_card_details() {
+		let color_map = {
+			"Pending": "var(--bg-blue)",
+			"In Process": "var(--bg-yellow)",
+			"Submitted": "var(--bg-blue)",
+			"Open": "var(--bg-gray)",
+			"Closed": "var(--bg-green)",
+			"Work In Progress": "var(--bg-orange)",
+		}
+
+		this.job_cards.forEach((data) => {
+			let job_card_selector =  this.$wrapper.find(`
+				[data-name='${data.name}']`
+			);
+
+			$(job_card_selector).find(".job-card-status").text(data.status);
+			$(job_card_selector).find(".job-card-status").css("backgroundColor", color_map[data.status]);
+
+			if (data.status === "Work In Progress") {
+				$(job_card_selector).find(".btn-start").addClass("hide");
+				$(job_card_selector).find(".btn-complete").removeClass("hide");
+			} else if (data.status === "Completed") {
+				$(job_card_selector).find(".btn-start").addClass("hide");
+				$(job_card_selector).find(".btn-complete").addClass("hide");
+			}
+		});
+	}
+
+	initialiseTimer(data) {
+		setInterval(() => {
+			data._current_time += 1;
+			this.updateStopwatch(data);
+		}, 1000);
+	}
+
+	updateStopwatch(data) {
+		let increment = data._current_time;
+		let hours = Math.floor(increment / 3600);
+		let minutes = Math.floor((increment - (hours * 3600)) / 60);
+		let seconds = cint(increment - (hours * 3600) - (minutes * 60));
+
+		let job_card_selector = `[data-job-card='${data.name}']`
+		let timer_selector =  this.$wrapper.find(job_card_selector)
+
+		$(timer_selector).find(".hours").text(hours < 10 ? ("0" + hours.toString()) : hours.toString());
+		$(timer_selector).find(".minutes").text(minutes < 10 ? ("0" + minutes.toString()) : minutes.toString());
+		$(timer_selector).find(".seconds").text(seconds < 10 ? ("0" + seconds.toString()) : seconds.toString());
+	}
+
+	get_current_time(data) {
+		let current_time = 0.0;
+		data.time_logs.forEach(d => {
+			if (d.to_time) {
+				if (d.time_in_mins) {
+					current_time += flt(d.time_in_mins, 2) * 60;
+				} else {
+					current_time += this.get_seconds_diff(d.to_time, d.from_time);
+				}
+			} else {
+				current_time += this.get_seconds_diff(frappe.datetime.now_datetime(), d.from_time);
+			}
+		});
+
+		return current_time;
+	}
+
+	get_seconds_diff(d1, d2) {
+		return moment(d1).diff(d2, "seconds");
+	}
+}
\ No newline at end of file
diff --git a/erpnext/manufacturing/doctype/workstation/workstation.json b/erpnext/manufacturing/doctype/workstation/workstation.json
index 881cba0..5912714 100644
--- a/erpnext/manufacturing/doctype/workstation/workstation.json
+++ b/erpnext/manufacturing/doctype/workstation/workstation.json
@@ -8,10 +8,24 @@
  "document_type": "Setup",
  "engine": "InnoDB",
  "field_order": [
+  "dashboard_tab",
+  "workstation_dashboard",
+  "details_tab",
   "workstation_name",
-  "production_capacity",
-  "column_break_3",
   "workstation_type",
+  "plant_floor",
+  "column_break_3",
+  "production_capacity",
+  "warehouse",
+  "production_capacity_section",
+  "parts_per_hour",
+  "workstation_status_tab",
+  "status",
+  "column_break_glcv",
+  "illustration_section",
+  "on_status_image",
+  "column_break_etmc",
+  "off_status_image",
   "over_heads",
   "hour_rate_electricity",
   "hour_rate_consumable",
@@ -24,7 +38,9 @@
   "description",
   "working_hours_section",
   "holiday_list",
-  "working_hours"
+  "working_hours",
+  "total_working_hours",
+  "connections_tab"
  ],
  "fields": [
   {
@@ -120,9 +136,10 @@
   },
   {
    "default": "1",
+   "description": "Run parallel job cards in a workstation",
    "fieldname": "production_capacity",
    "fieldtype": "Int",
-   "label": "Production Capacity",
+   "label": "Job Capacity",
    "reqd": 1
   },
   {
@@ -145,12 +162,97 @@
   {
    "fieldname": "section_break_11",
    "fieldtype": "Section Break"
+  },
+  {
+   "fieldname": "plant_floor",
+   "fieldtype": "Link",
+   "label": "Plant Floor",
+   "options": "Plant Floor"
+  },
+  {
+   "fieldname": "workstation_status_tab",
+   "fieldtype": "Tab Break",
+   "label": "Workstation Status"
+  },
+  {
+   "fieldname": "illustration_section",
+   "fieldtype": "Section Break",
+   "label": "Status Illustration"
+  },
+  {
+   "fieldname": "column_break_etmc",
+   "fieldtype": "Column Break"
+  },
+  {
+   "fieldname": "status",
+   "fieldtype": "Select",
+   "in_list_view": 1,
+   "label": "Status",
+   "options": "Production\nOff\nIdle\nProblem\nMaintenance\nSetup"
+  },
+  {
+   "fieldname": "column_break_glcv",
+   "fieldtype": "Column Break"
+  },
+  {
+   "fieldname": "on_status_image",
+   "fieldtype": "Attach Image",
+   "label": "Active Status"
+  },
+  {
+   "fieldname": "off_status_image",
+   "fieldtype": "Attach Image",
+   "label": "Inactive Status"
+  },
+  {
+   "fieldname": "warehouse",
+   "fieldtype": "Link",
+   "label": "Warehouse",
+   "options": "Warehouse"
+  },
+  {
+   "fieldname": "production_capacity_section",
+   "fieldtype": "Section Break",
+   "label": "Production Capacity"
+  },
+  {
+   "fieldname": "parts_per_hour",
+   "fieldtype": "Float",
+   "label": "Parts Per Hour"
+  },
+  {
+   "fieldname": "total_working_hours",
+   "fieldtype": "Float",
+   "label": "Total Working Hours"
+  },
+  {
+   "depends_on": "eval:!doc.__islocal",
+   "fieldname": "dashboard_tab",
+   "fieldtype": "Tab Break",
+   "label": "Job Cards"
+  },
+  {
+   "fieldname": "details_tab",
+   "fieldtype": "Tab Break",
+   "label": "Details"
+  },
+  {
+   "fieldname": "connections_tab",
+   "fieldtype": "Tab Break",
+   "label": "Connections",
+   "show_dashboard": 1
+  },
+  {
+   "fieldname": "workstation_dashboard",
+   "fieldtype": "HTML",
+   "label": "Workstation Dashboard"
   }
  ],
  "icon": "icon-wrench",
  "idx": 1,
+ "image_field": "on_status_image",
  "links": [],
- "modified": "2022-11-04 17:39:01.549346",
+ "modified": "2023-11-30 12:43:35.808845",
  "modified_by": "Administrator",
  "module": "Manufacturing",
  "name": "Workstation",
diff --git a/erpnext/manufacturing/doctype/workstation/workstation.py b/erpnext/manufacturing/doctype/workstation/workstation.py
index 0f05eaa..3d40a2d 100644
--- a/erpnext/manufacturing/doctype/workstation/workstation.py
+++ b/erpnext/manufacturing/doctype/workstation/workstation.py
@@ -11,7 +11,11 @@
 	comma_and,
 	flt,
 	formatdate,
+	get_link_to_form,
+	get_time,
+	get_url_to_form,
 	getdate,
+	time_diff_in_hours,
 	time_diff_in_seconds,
 	to_timedelta,
 )
@@ -60,6 +64,23 @@
 	def before_save(self):
 		self.set_data_based_on_workstation_type()
 		self.set_hour_rate()
+		self.set_total_working_hours()
+
+	def set_total_working_hours(self):
+		self.total_working_hours = 0.0
+		for row in self.working_hours:
+			self.validate_working_hours(row)
+
+			if row.start_time and row.end_time:
+				row.hours = flt(time_diff_in_hours(row.end_time, row.start_time), row.precision("hours"))
+				self.total_working_hours += row.hours
+
+	def validate_working_hours(self, row):
+		if not (row.start_time and row.end_time):
+			frappe.throw(_("Row #{0}: Start Time and End Time are required").format(row.idx))
+
+		if get_time(row.start_time) >= get_time(row.end_time):
+			frappe.throw(_("Row #{0}: Start Time must be before End Time").format(row.idx))
 
 	def set_hour_rate(self):
 		self.hour_rate = (
@@ -143,6 +164,141 @@
 
 		return schedule_date
 
+	@frappe.whitelist()
+	def start_job(self, job_card, from_time, employee):
+		doc = frappe.get_doc("Job Card", job_card)
+		doc.append("time_logs", {"from_time": from_time, "employee": employee})
+		doc.save(ignore_permissions=True)
+
+		return doc
+
+	@frappe.whitelist()
+	def complete_job(self, job_card, qty, to_time):
+		doc = frappe.get_doc("Job Card", job_card)
+		for row in doc.time_logs:
+			if not row.to_time:
+				row.to_time = to_time
+				row.time_in_mins = time_diff_in_hours(row.to_time, row.from_time) / 60
+				row.completed_qty = qty
+
+		doc.save(ignore_permissions=True)
+		doc.submit()
+
+		return doc
+
+
+@frappe.whitelist()
+def get_job_cards(workstation):
+	if frappe.has_permission("Job Card", "read"):
+		jc_data = frappe.get_all(
+			"Job Card",
+			fields=[
+				"name",
+				"production_item",
+				"work_order",
+				"operation",
+				"total_completed_qty",
+				"for_quantity",
+				"transferred_qty",
+				"status",
+				"expected_start_date",
+				"expected_end_date",
+				"time_required",
+				"wip_warehouse",
+			],
+			filters={
+				"workstation": workstation,
+				"docstatus": ("<", 2),
+				"status": ["not in", ["Completed", "Stopped"]],
+			},
+			order_by="expected_start_date, expected_end_date",
+		)
+
+		job_cards = [row.name for row in jc_data]
+		raw_materials = get_raw_materials(job_cards)
+		time_logs = get_time_logs(job_cards)
+
+		allow_excess_transfer = frappe.db.get_single_value(
+			"Manufacturing Settings", "job_card_excess_transfer"
+		)
+
+		for row in jc_data:
+			row.progress_percent = (
+				flt(row.total_completed_qty / row.for_quantity * 100, 2) if row.for_quantity else 0
+			)
+			row.progress_title = _("Total completed quantity: {0}").format(row.total_completed_qty)
+			row.status_color = get_status_color(row.status)
+			row.job_card_link = get_link_to_form("Job Card", row.name)
+			row.work_order_link = get_link_to_form("Work Order", row.work_order)
+
+			row.raw_materials = raw_materials.get(row.name, [])
+			row.time_logs = time_logs.get(row.name, [])
+			row.make_material_request = False
+			if row.for_quantity > row.transferred_qty or allow_excess_transfer:
+				row.make_material_request = True
+
+		return jc_data
+
+
+def get_status_color(status):
+	color_map = {
+		"Pending": "var(--bg-blue)",
+		"In Process": "var(--bg-yellow)",
+		"Submitted": "var(--bg-blue)",
+		"Open": "var(--bg-gray)",
+		"Closed": "var(--bg-green)",
+		"Work In Progress": "var(--bg-orange)",
+	}
+
+	return color_map.get(status, "var(--bg-blue)")
+
+
+def get_raw_materials(job_cards):
+	raw_materials = {}
+
+	data = frappe.get_all(
+		"Job Card Item",
+		fields=[
+			"parent",
+			"item_code",
+			"item_group",
+			"uom",
+			"item_name",
+			"source_warehouse",
+			"required_qty",
+			"transferred_qty",
+		],
+		filters={"parent": ["in", job_cards]},
+	)
+
+	for row in data:
+		raw_materials.setdefault(row.parent, []).append(row)
+
+	return raw_materials
+
+
+def get_time_logs(job_cards):
+	time_logs = {}
+
+	data = frappe.get_all(
+		"Job Card Time Log",
+		fields=[
+			"parent",
+			"name",
+			"employee",
+			"from_time",
+			"to_time",
+			"time_in_mins",
+		],
+		filters={"parent": ["in", job_cards], "parentfield": "time_logs"},
+		order_by="parent, idx",
+	)
+
+	for row in data:
+		time_logs.setdefault(row.parent, []).append(row)
+
+	return time_logs
+
 
 @frappe.whitelist()
 def get_default_holiday_list():
@@ -201,3 +357,52 @@
 				+ "\n".join(applicable_holidays),
 				WorkstationHolidayError,
 			)
+
+
+@frappe.whitelist()
+def get_workstations(**kwargs):
+	kwargs = frappe._dict(kwargs)
+	_workstation = frappe.qb.DocType("Workstation")
+
+	query = (
+		frappe.qb.from_(_workstation)
+		.select(
+			_workstation.name,
+			_workstation.description,
+			_workstation.status,
+			_workstation.on_status_image,
+			_workstation.off_status_image,
+		)
+		.orderby(_workstation.workstation_type, _workstation.name)
+		.where(_workstation.plant_floor == kwargs.plant_floor)
+	)
+
+	if kwargs.workstation:
+		query = query.where(_workstation.name == kwargs.workstation)
+
+	if kwargs.workstation_type:
+		query = query.where(_workstation.workstation_type == kwargs.workstation_type)
+
+	if kwargs.workstation_status:
+		query = query.where(_workstation.status == kwargs.workstation_status)
+
+	data = query.run(as_dict=True)
+
+	color_map = {
+		"Production": "var(--green-600)",
+		"Off": "var(--gray-600)",
+		"Idle": "var(--gray-600)",
+		"Problem": "var(--red-600)",
+		"Maintenance": "var(--yellow-600)",
+		"Setup": "var(--blue-600)",
+	}
+
+	for d in data:
+		d.workstation_name = get_link_to_form("Workstation", d.name)
+		d.status_image = d.on_status_image
+		d.background_color = color_map.get(d.status, "var(--red-600)")
+		d.workstation_link = get_url_to_form("Workstation", d.name)
+		if d.status != "Production":
+			d.status_image = d.off_status_image
+
+	return data
diff --git a/erpnext/manufacturing/doctype/workstation/workstation_job_card.html b/erpnext/manufacturing/doctype/workstation/workstation_job_card.html
new file mode 100644
index 0000000..9770785
--- /dev/null
+++ b/erpnext/manufacturing/doctype/workstation/workstation_job_card.html
@@ -0,0 +1,125 @@
+<style>
+	.job-card-link {
+		min-height: 100px;
+	}
+
+	.section-head-job-card {
+		margin-bottom: 0px;
+		padding-bottom: 0px;
+	}
+</style>
+
+<div style = "max-height: 400px; overflow-y: auto;">
+{% $.each(data, (idx, d) => { %}
+	<div class="row form-dashboard-section job-card-link form-links border-gray-200" data-name="{{d.name}}">
+		<div class="section-head section-head-job-card">
+			{{ d.operation }} - {{ d.production_item }}
+			<span class="ml-2 collapse-indicator-job mb-1" style="">
+				{{frappe.utils.icon("es-line-down", "sm", "mb-1")}}
+			</span>
+		</div>
+		<div class="row form-section" style="width:100%;margin-bottom:10px">
+			<div class="form-column col-sm-3">
+				<div class="frappe-control" title="{{__('Job Card')}}" style="text-decoration:underline">
+					{{ d.job_card_link }}
+				</div>
+				<div class="frappe-control" title="{{__('Work Order')}}" style="text-decoration:underline">
+					{{ d.work_order_link }}
+				</div>
+			</div>
+			<div class="form-column col-sm-2">
+				<div class="frappe-control timer" title="{{__('Timer')}}" style="text-align:center;font-size:14px;" data-job-card = {{escape(d.name)}}>
+					<span class="hours">00</span>
+					<span class="colon">:</span>
+					<span class="minutes">00</span>
+					<span class="colon">:</span>
+					<span class="seconds">00</span>
+				</div>
+
+				{% if(d.status === "Open") { %}
+					<div class="frappe-control" title="{{__('Expected Start Date')}}" style="text-align:center;font-size:11px;padding-top: 4px;">
+						{{ frappe.format(d.expected_start_date, { fieldtype: 'Datetime' }) }}
+					</div>
+				{% } else { %}
+					<div class="frappe-control" title="{{__('Expected End Date')}}" style="text-align:center;font-size:11px;padding-top: 4px;">
+						{{ frappe.format(d.expected_end_date, { fieldtype: 'Datetime' }) }}
+					</div>
+				{% } %}
+
+			</div>
+			<div class="form-column col-sm-2">
+				<div class="frappe-control job-card-status" title="{{__('Status')}}" style="background:{{d.status_color}};text-align:center;border-radius:var(--border-radius-full)">
+					{{ d.status }}
+				</div>
+			</div>
+			<div class="form-column col-sm-2">
+				<div class="frappe-control" title="{{__('Qty to Manufacture')}}">
+					<div class="progress" title = "{{d.progress_title}}">
+						<div class="progress-bar progress-bar-success" style="width: {{d.progress_percent}}%">
+						</div>
+					</div>
+				</div>
+				<div class="frappe-control" style="text-align: center; font-size: 10px;">
+					{{ d.for_quantity }} / {{ d.total_completed_qty }}
+				</div>
+			</div>
+			<div class="form-column col-sm-2 text-center">
+				<button style="width: 85px;" class="btn btn-default btn-start {% if(d.status !== "Open") { %} hide {% } %}" job-card="{{d.name}}"> {{__("Start")}} </button>
+				<button style="width: 85px;" class="btn btn-default btn-complete {% if(d.status === "Open") { %} hide {% } %}" job-card="{{d.name}}" pending-qty="{{d.for_quantity - d.transferred_qty}}"> {{__("Complete")}} </button>
+			</div>
+		</div>
+
+		<div class="section-body section-body-job-card form-section hide">
+			<hr>
+			<div class="row">
+				<div class="form-column col-sm-2">
+					{{ __("Raw Materials") }}
+				</div>
+				{% if(d.make_material_request) { %}
+					<div class="form-column col-sm-10 text-right">
+						<button class="btn btn-default btn-xs make-material-request" job-card="{{d.name}}">{{ __("Material Request") }}</button>
+					</div>
+				{% } %}
+			</div>
+
+			{% if(d.raw_materials) { %}
+			<table class="table table-bordered table-condensed">
+				<thead>
+					<tr>
+						<th style="width: 5%" class="table-sr">Sr</th>
+
+						<th style="width: 15%">{{ __("Item") }}</th>
+						<th style="width: 15%">{{ __("Warehouse") }}</th>
+						<th style="width: 10%">{{__("UOM")}}</th>
+						<th style="width: 15%">{{__("Item Group")}}</th>
+						<th style="width: 20%" >{{__("Required Qty")}}</th>
+						<th style="width: 20%" >{{__("Transferred Qty")}}</th>
+					</tr>
+				</thead>
+				<tbody>
+
+				{% $.each(d.raw_materials, (row_index, child_row) => { %}
+					<tr>
+						<td class="table-sr">{{ row_index+1 }}</td>
+						{% if(child_row.item_code === child_row.item_name) { %}
+							<td>{{ child_row.item_code }}</td>
+						{% } else { %}
+							<td>{{ child_row.item_code }}: {{child_row.item_name}}</td>
+						{% } %}
+						<td>{{ child_row.source_warehouse }}</td>
+						<td>{{ child_row.uom }}</td>
+						<td>{{ child_row.item_group }}</td>
+						<td>{{ child_row.required_qty }}</td>
+						<td>{{ child_row.transferred_qty }}</td>
+					</tr>
+				{% }); %}
+
+				</tbody>
+			{% } %}
+
+			</table>
+		</div>
+
+	</div>
+{% }); %}
+</div>
\ No newline at end of file
diff --git a/erpnext/manufacturing/doctype/workstation/workstation_list.js b/erpnext/manufacturing/doctype/workstation/workstation_list.js
index 61f2062..86928ca 100644
--- a/erpnext/manufacturing/doctype/workstation/workstation_list.js
+++ b/erpnext/manufacturing/doctype/workstation/workstation_list.js
@@ -1,5 +1,16 @@
 
 frappe.listview_settings['Workstation'] = {
-	// add_fields: ["status"],
-	// filters:[["status","=", "Open"]]
+	add_fields: ["status"],
+	get_indicator: function(doc) {
+		let color_map = {
+			"Production": "green",
+			"Off": "gray",
+			"Idle": "gray",
+			"Problem": "red",
+			"Maintenance": "yellow",
+			"Setup": "blue",
+		}
+
+		return [__(doc.status), color_map[doc.status], true];
+	}
 };
diff --git a/erpnext/manufacturing/doctype/workstation_working_hour/workstation_working_hour.json b/erpnext/manufacturing/doctype/workstation_working_hour/workstation_working_hour.json
index a79182f..b185f7d 100644
--- a/erpnext/manufacturing/doctype/workstation_working_hour/workstation_working_hour.json
+++ b/erpnext/manufacturing/doctype/workstation_working_hour/workstation_working_hour.json
@@ -1,150 +1,58 @@
 {
- "allow_copy": 0, 
- "allow_import": 0, 
- "allow_rename": 0, 
- "beta": 0, 
- "creation": "2014-12-24 14:46:40.678236", 
- "custom": 0, 
- "docstatus": 0, 
- "doctype": "DocType", 
- "document_type": "", 
- "editable_grid": 1, 
- "engine": "InnoDB", 
+ "actions": [],
+ "creation": "2014-12-24 14:46:40.678236",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+  "start_time",
+  "hours",
+  "column_break_2",
+  "end_time",
+  "enabled"
+ ],
  "fields": [
   {
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "start_time", 
-   "fieldtype": "Time", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_list_view": 1, 
-   "in_standard_filter": 0, 
-   "label": "Start Time", 
-   "length": 0, 
-   "no_copy": 0, 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 1, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "unique": 0
-  }, 
+   "fieldname": "start_time",
+   "fieldtype": "Time",
+   "in_list_view": 1,
+   "label": "Start Time",
+   "reqd": 1
+  },
   {
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "column_break_2", 
-   "fieldtype": "Column Break", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_list_view": 0, 
-   "in_standard_filter": 0, 
-   "length": 0, 
-   "no_copy": 0, 
-   "permlevel": 0, 
-   "precision": "", 
-   "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": "column_break_2",
+   "fieldtype": "Column Break"
+  },
   {
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "end_time", 
-   "fieldtype": "Time", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_list_view": 1, 
-   "in_standard_filter": 0, 
-   "label": "End Time", 
-   "length": 0, 
-   "no_copy": 0, 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 1, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "unique": 0
-  }, 
+   "fieldname": "end_time",
+   "fieldtype": "Time",
+   "in_list_view": 1,
+   "label": "End Time",
+   "reqd": 1
+  },
   {
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "default": "1", 
-   "fieldname": "enabled", 
-   "fieldtype": "Check", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_list_view": 1, 
-   "in_standard_filter": 0, 
-   "label": "Enabled", 
-   "length": 0, 
-   "no_copy": 0, 
-   "permlevel": 0, 
-   "precision": "", 
-   "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": "1",
+   "fieldname": "enabled",
+   "fieldtype": "Check",
+   "in_list_view": 1,
+   "label": "Enabled"
+  },
+  {
+   "fieldname": "hours",
+   "fieldtype": "Float",
+   "label": "Hours",
+   "read_only": 1
   }
- ], 
- "hide_heading": 0, 
- "hide_toolbar": 0, 
- "idx": 0, 
- "image_view": 0, 
- "in_create": 0, 
-
- "is_submittable": 0, 
- "issingle": 0, 
- "istable": 1, 
- "max_attachments": 0, 
- "modified": "2016-12-13 05:02:36.754145", 
- "modified_by": "Administrator", 
- "module": "Manufacturing", 
- "name": "Workstation Working Hour", 
- "name_case": "", 
- "owner": "Administrator", 
- "permissions": [], 
- "quick_entry": 0, 
- "read_only": 0, 
- "read_only_onload": 0, 
- "sort_field": "modified", 
- "sort_order": "DESC", 
- "track_seen": 0
+ ],
+ "istable": 1,
+ "links": [],
+ "modified": "2023-10-25 14:48:29.697498",
+ "modified_by": "Administrator",
+ "module": "Manufacturing",
+ "name": "Workstation Working Hour",
+ "owner": "Administrator",
+ "permissions": [],
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "states": []
 }
\ No newline at end of file
diff --git a/erpnext/manufacturing/notification/material_request_receipt_notification/material_request_receipt_notification.html b/erpnext/manufacturing/notification/material_request_receipt_notification/material_request_receipt_notification.html
new file mode 100644
index 0000000..ca97516
--- /dev/null
+++ b/erpnext/manufacturing/notification/material_request_receipt_notification/material_request_receipt_notification.html
@@ -0,0 +1,19 @@
+<p><b>{{ _("Material Request Type") }}</b>: {{ doc.material_request_type }}<br>
+<b>{{ _("Company") }}</b>: {{ doc.company }}</p>
+
+<h3>{{ _("Order Summary") }}</h3>
+
+<table border=2 >
+    <tr align="center">
+        <th>{{ _("Item Name") }}</th>
+        <th>{{ _("Received Quantity") }}</th>
+    </tr>
+    {% for item in doc.items %}
+        {% if frappe.utils.flt(item.received_qty, 2) > 0.0 %}
+            <tr align="center">
+                <td>{{ item.item_code }}</td>
+                <td>{{ frappe.utils.flt(item.received_qty, 2) }}</td>
+            </tr>
+        {% endif %}
+    {% endfor %}
+</table>
diff --git a/erpnext/manufacturing/notification/material_request_receipt_notification/material_request_receipt_notification.json b/erpnext/manufacturing/notification/material_request_receipt_notification/material_request_receipt_notification.json
index 5391a31..6ef2ea3 100644
--- a/erpnext/manufacturing/notification/material_request_receipt_notification/material_request_receipt_notification.json
+++ b/erpnext/manufacturing/notification/material_request_receipt_notification/material_request_receipt_notification.json
@@ -11,19 +11,21 @@
  "event": "Value Change",
  "idx": 0,
  "is_standard": 1,
- "message": "<b>Material Request Type</b>: {{ doc.material_request_type }}<br>\n<b>Company</b>: {{ doc.company }}\n\n<h3>Order Summary</h3>\n\n<table border=2 >\n    <tr align=\"center\">\n        <th>Item Name</th>\n        <th>Received Quantity</th>\n    </tr>\n    {% for item in doc.items %}\n        {% if frappe.utils.flt(item.received_qty, 2) > 0.0 %}\n            <tr align=\"center\">\n                <td>{{ item.item_code }}</td>\n                <td>{{ frappe.utils.flt(item.received_qty, 2) }}</td>\n            </tr>\n        {% endif %}\n    {% endfor %}\n</table>",
+ "message_type": "HTML",
  "method": "",
- "modified": "2019-05-01 18:02:51.090037",
+ "modified": "2023-11-17 08:53:29.525296",
  "modified_by": "Administrator",
  "module": "Manufacturing",
  "name": "Material Request Receipt Notification",
  "owner": "Administrator",
  "recipients": [
   {
-   "email_by_document_field": "requested_by"
+   "receiver_by_document_field": "requested_by"
   }
  ],
+ "send_system_notification": 0,
+ "send_to_all_assignees": 0,
  "sender_email": "",
  "subject": "{{ doc.name }} has been received",
  "value_changed": "status"
-}
\ No newline at end of file
+}
diff --git a/erpnext/manufacturing/notification/material_request_receipt_notification/material_request_receipt_notification.md b/erpnext/manufacturing/notification/material_request_receipt_notification/material_request_receipt_notification.md
deleted file mode 100644
index e6feee9..0000000
--- a/erpnext/manufacturing/notification/material_request_receipt_notification/material_request_receipt_notification.md
+++ /dev/null
@@ -1,19 +0,0 @@
-<b>Material Request Type</b>: {{ doc.material_request_type }}<br>
-<b>Company</b>: {{ doc.company }}
-
-<h3>Order Summary</h3>
-
-<table border=2 >
-    <tr align="center">
-        <th>Item Name</th>
-        <th>Received Quantity</th>
-    </tr>
-    {% for item in doc.items %}
-        {% if frappe.utils.flt(item.received_qty, 2) > 0.0 %}
-            <tr align="center">
-                <td>{{ item.item_code }}</td>
-                <td>{{ frappe.utils.flt(item.received_qty, 2) }}</td>
-            </tr>
-        {% endif %}
-    {% endfor %}
-</table>
\ No newline at end of file
diff --git a/erpnext/manufacturing/page/visual_plant_floor/__init__.py b/erpnext/manufacturing/page/visual_plant_floor/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/manufacturing/page/visual_plant_floor/__init__.py
diff --git a/erpnext/manufacturing/page/visual_plant_floor/visual_plant_floor.js b/erpnext/manufacturing/page/visual_plant_floor/visual_plant_floor.js
new file mode 100644
index 0000000..38667e8
--- /dev/null
+++ b/erpnext/manufacturing/page/visual_plant_floor/visual_plant_floor.js
@@ -0,0 +1,13 @@
+
+
+frappe.pages['visual-plant-floor'].on_page_load = function(wrapper) {
+	var page = frappe.ui.make_app_page({
+		parent: wrapper,
+		title: 'Visual Plant Floor',
+		single_column: true
+	});
+
+	frappe.visual_plant_floor = new frappe.ui.VisualPlantFloor(
+		{wrapper: $(wrapper).find('.layout-main-section')}, wrapper.page
+	);
+}
\ No newline at end of file
diff --git a/erpnext/manufacturing/page/visual_plant_floor/visual_plant_floor.json b/erpnext/manufacturing/page/visual_plant_floor/visual_plant_floor.json
new file mode 100644
index 0000000..a907e97
--- /dev/null
+++ b/erpnext/manufacturing/page/visual_plant_floor/visual_plant_floor.json
@@ -0,0 +1,29 @@
+{
+ "content": null,
+ "creation": "2023-10-06 15:17:39.215300",
+ "docstatus": 0,
+ "doctype": "Page",
+ "idx": 0,
+ "modified": "2023-10-06 15:18:00.622073",
+ "modified_by": "Administrator",
+ "module": "Manufacturing",
+ "name": "visual-plant-floor",
+ "owner": "Administrator",
+ "page_name": "visual-plant-floor",
+ "roles": [
+  {
+   "role": "Manufacturing User"
+  },
+  {
+   "role": "Manufacturing Manager"
+  },
+  {
+   "role": "Operator"
+  }
+ ],
+ "script": null,
+ "standard": "Yes",
+ "style": null,
+ "system_page": 0,
+ "title": "Visual Plant Floor"
+}
\ No newline at end of file
diff --git a/erpnext/manufacturing/workspace/manufacturing/manufacturing.json b/erpnext/manufacturing/workspace/manufacturing/manufacturing.json
index 8e07850..d2520d6 100644
--- a/erpnext/manufacturing/workspace/manufacturing/manufacturing.json
+++ b/erpnext/manufacturing/workspace/manufacturing/manufacturing.json
@@ -1,6 +1,6 @@
 {
  "charts": [],
- "content": "[{\"id\":\"csBCiDglCE\",\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Your Shortcuts</b></span>\",\"col\":12}},{\"id\":\"YHCQG3wAGv\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"BOM Creator\",\"col\":3}},{\"id\":\"xit0dg7KvY\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"BOM\",\"col\":3}},{\"id\":\"LRhGV9GAov\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Production Plan\",\"col\":3}},{\"id\":\"69KKosI6Hg\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Work Order\",\"col\":3}},{\"id\":\"PwndxuIpB3\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Job Card\",\"col\":3}},{\"id\":\"OaiDqTT03Y\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Forecasting\",\"col\":3}},{\"id\":\"OtMcArFRa5\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"BOM Stock Report\",\"col\":3}},{\"id\":\"76yYsI5imF\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Production Planning Report\",\"col\":3}},{\"id\":\"PIQJYZOMnD\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Learn Manufacturing\",\"col\":3}},{\"id\":\"bN_6tHS-Ct\",\"type\":\"spacer\",\"data\":{\"col\":12}},{\"id\":\"yVEFZMqVwd\",\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Reports &amp; Masters</b></span>\",\"col\":12}},{\"id\":\"rwrmsTI58-\",\"type\":\"card\",\"data\":{\"card_name\":\"Production\",\"col\":4}},{\"id\":\"6dnsyX-siZ\",\"type\":\"card\",\"data\":{\"card_name\":\"Bill of Materials\",\"col\":4}},{\"id\":\"CIq-v5f5KC\",\"type\":\"card\",\"data\":{\"card_name\":\"Reports\",\"col\":4}},{\"id\":\"8RRiQeYr0G\",\"type\":\"card\",\"data\":{\"card_name\":\"Tools\",\"col\":4}},{\"id\":\"Pu8z7-82rT\",\"type\":\"card\",\"data\":{\"card_name\":\"Settings\",\"col\":4}}]",
+ "content": "[{\"id\":\"csBCiDglCE\",\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Your Shortcuts</b></span>\",\"col\":12}},{\"id\":\"YHCQG3wAGv\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"BOM Creator\",\"col\":3}},{\"id\":\"xit0dg7KvY\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"BOM\",\"col\":3}},{\"id\":\"LRhGV9GAov\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Production Plan\",\"col\":3}},{\"id\":\"69KKosI6Hg\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Work Order\",\"col\":3}},{\"id\":\"PwndxuIpB3\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Job Card\",\"col\":3}},{\"id\":\"Ubj6zXcmIQ\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Plant Floor\",\"col\":3}},{\"id\":\"OtMcArFRa5\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"BOM Stock Report\",\"col\":3}},{\"id\":\"76yYsI5imF\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Production Planning Report\",\"col\":3}},{\"id\":\"PIQJYZOMnD\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Learn Manufacturing\",\"col\":3}},{\"id\":\"OaiDqTT03Y\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Forecasting\",\"col\":3}},{\"id\":\"bN_6tHS-Ct\",\"type\":\"spacer\",\"data\":{\"col\":12}},{\"id\":\"yVEFZMqVwd\",\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Reports &amp; Masters</b></span>\",\"col\":12}},{\"id\":\"rwrmsTI58-\",\"type\":\"card\",\"data\":{\"card_name\":\"Production\",\"col\":4}},{\"id\":\"6dnsyX-siZ\",\"type\":\"card\",\"data\":{\"card_name\":\"Bill of Materials\",\"col\":4}},{\"id\":\"CIq-v5f5KC\",\"type\":\"card\",\"data\":{\"card_name\":\"Reports\",\"col\":4}},{\"id\":\"8RRiQeYr0G\",\"type\":\"card\",\"data\":{\"card_name\":\"Tools\",\"col\":4}},{\"id\":\"Pu8z7-82rT\",\"type\":\"card\",\"data\":{\"card_name\":\"Settings\",\"col\":4}}]",
  "creation": "2020-03-02 17:11:37.032604",
  "custom_blocks": [],
  "docstatus": 0,
@@ -316,7 +316,7 @@
    "type": "Link"
   }
  ],
- "modified": "2023-08-08 22:28:39.633891",
+ "modified": "2024-01-30 21:49:58.577218",
  "modified_by": "Administrator",
  "module": "Manufacturing",
  "name": "Manufacturing",
@@ -339,6 +339,13 @@
   {
    "color": "Grey",
    "doc_view": "List",
+   "label": "Plant Floor",
+   "link_to": "Plant Floor",
+   "type": "DocType"
+  },
+  {
+   "color": "Grey",
+   "doc_view": "List",
    "label": "BOM Creator",
    "link_to": "BOM Creator",
    "type": "DocType"
diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js
index f831a88..26795f7 100644
--- a/erpnext/public/js/controllers/transaction.js
+++ b/erpnext/public/js/controllers/transaction.js
@@ -502,6 +502,7 @@
 							project: item.project || me.frm.doc.project,
 							qty: item.qty || 1,
 							net_rate: item.rate,
+							base_net_rate: item.base_net_rate,
 							stock_qty: item.stock_qty,
 							conversion_factor: item.conversion_factor,
 							weight_per_unit: item.weight_per_unit,
@@ -798,14 +799,14 @@
 					}
 					let selling_doctypes_for_tc = ["Sales Invoice", "Quotation", "Sales Order", "Delivery Note"];
 					if (company_doc.default_selling_terms && frappe.meta.has_field(me.frm.doc.doctype, "tc_name") &&
-					selling_doctypes_for_tc.indexOf(me.frm.doc.doctype) != -1) {
+					selling_doctypes_for_tc.includes(me.frm.doc.doctype) && !me.frm.doc.tc_name) {
 						me.frm.set_value("tc_name", company_doc.default_selling_terms);
 					}
 					let buying_doctypes_for_tc = ["Request for Quotation", "Supplier Quotation", "Purchase Order",
 						"Material Request", "Purchase Receipt"];
 					// Purchase Invoice is excluded as per issue #3345
 					if (company_doc.default_buying_terms && frappe.meta.has_field(me.frm.doc.doctype, "tc_name") &&
-					buying_doctypes_for_tc.indexOf(me.frm.doc.doctype) != -1) {
+					buying_doctypes_for_tc.includes(me.frm.doc.doctype) && !me.frm.doc.tc_name) {
 						me.frm.set_value("tc_name", company_doc.default_buying_terms);
 					}
 				}
@@ -1902,7 +1903,7 @@
 			if (item.item_code) {
 				// Use combination of name and item code in case same item is added multiple times
 				item_codes.push([item.item_code, item.name]);
-				item_rates[item.name] = item.net_rate;
+				item_rates[item.name] = item.base_net_rate;
 				item_tax_templates[item.name] = item.item_tax_template;
 			}
 		});
diff --git a/erpnext/public/js/erpnext.bundle.js b/erpnext/public/js/erpnext.bundle.js
index dee9a06..b847e57 100644
--- a/erpnext/public/js/erpnext.bundle.js
+++ b/erpnext/public/js/erpnext.bundle.js
@@ -5,6 +5,8 @@
 import "./utils/party";
 import "./controllers/stock_controller";
 import "./payment/payments";
+import "./templates/visual_plant_floor_template.html";
+import "./plant_floor_visual/visual_plant";
 import "./controllers/taxes_and_totals";
 import "./controllers/transaction";
 import "./templates/item_selector.html";
diff --git a/erpnext/public/js/plant_floor_visual/visual_plant.js b/erpnext/public/js/plant_floor_visual/visual_plant.js
new file mode 100644
index 0000000..8cd73ad
--- /dev/null
+++ b/erpnext/public/js/plant_floor_visual/visual_plant.js
@@ -0,0 +1,157 @@
+class VisualPlantFloor {
+	constructor({wrapper, skip_filters=false, plant_floor=null}, page=null) {
+		this.wrapper = wrapper;
+		this.plant_floor = plant_floor;
+		this.skip_filters = skip_filters;
+
+		this.make();
+		if (!this.skip_filters) {
+			this.page = page;
+			this.add_filter();
+			this.prepare_menu();
+		}
+	}
+
+	make() {
+		this.wrapper.append(`
+			<div class="plant-floor">
+				<div class="plant-floor-filter">
+				</div>
+				<div class="plant-floor-container col-sm-12">
+				</div>
+			</div>
+		`);
+
+		if (!this.skip_filters) {
+			this.filter_wrapper = this.wrapper.find('.plant-floor-filter');
+			this.visualization_wrapper = this.wrapper.find('.plant-floor-visualization');
+		} else if(this.plant_floor) {
+			this.wrapper.find('.plant-floor').css('border', 'none');
+			this.prepare_data();
+		}
+	}
+
+	prepare_data() {
+		frappe.call({
+			method: 'erpnext.manufacturing.doctype.workstation.workstation.get_workstations',
+			args: {
+				plant_floor: this.plant_floor,
+			},
+			callback: (r) => {
+				this.workstations = r.message;
+				this.render_workstations();
+			}
+		});
+	}
+
+	add_filter() {
+		this.plant_floor = frappe.ui.form.make_control({
+			df: {
+				fieldtype: 'Link',
+				options: 'Plant Floor',
+				fieldname: 'plant_floor',
+				label: __('Plant Floor'),
+				reqd: 1,
+				onchange: () => {
+					this.render_plant_visualization();
+				}
+			},
+			parent: this.filter_wrapper,
+			render_input: true,
+		});
+
+		this.plant_floor.$wrapper.addClass('form-column col-sm-2');
+
+		this.workstation_type = frappe.ui.form.make_control({
+			df: {
+				fieldtype: 'Link',
+				options: 'Workstation Type',
+				fieldname: 'workstation_type',
+				label: __('Machine Type'),
+				onchange: () => {
+					this.render_plant_visualization();
+				}
+			},
+			parent: this.filter_wrapper,
+			render_input: true,
+		});
+
+		this.workstation_type.$wrapper.addClass('form-column col-sm-2');
+
+		this.workstation = frappe.ui.form.make_control({
+			df: {
+				fieldtype: 'Link',
+				options: 'Workstation',
+				fieldname: 'workstation',
+				label: __('Machine'),
+				onchange: () => {
+					this.render_plant_visualization();
+				},
+				get_query: () => {
+					if (this.workstation_type.get_value()) {
+						return {
+							filters: {
+								'workstation_type': this.workstation_type.get_value() || ''
+							}
+						}
+					}
+				}
+			},
+			parent: this.filter_wrapper,
+			render_input: true,
+		});
+
+		this.workstation.$wrapper.addClass('form-column col-sm-2');
+
+		this.workstation_status = frappe.ui.form.make_control({
+			df: {
+				fieldtype: 'Select',
+				options: '\nProduction\nOff\nIdle\nProblem\nMaintenance\nSetup',
+				fieldname: 'workstation_status',
+				label: __('Status'),
+				onchange: () => {
+					this.render_plant_visualization();
+				},
+			},
+			parent: this.filter_wrapper,
+			render_input: true,
+		});
+	}
+
+	render_plant_visualization() {
+		let plant_floor = this.plant_floor.get_value();
+
+		if (plant_floor) {
+			frappe.call({
+				method: 'erpnext.manufacturing.doctype.workstation.workstation.get_workstations',
+				args: {
+					plant_floor: plant_floor,
+					workstation_type: this.workstation_type.get_value(),
+					workstation: this.workstation.get_value(),
+					workstation_status: this.workstation_status.get_value()
+				},
+				callback: (r) => {
+					this.workstations = r.message;
+					this.render_workstations();
+				}
+			});
+		}
+	}
+
+	render_workstations() {
+		this.wrapper.find('.plant-floor-container').empty();
+		let template  = frappe.render_template("visual_plant_floor_template", {
+			workstations: this.workstations
+		});
+
+		$(template).appendTo(this.wrapper.find('.plant-floor-container'));
+	}
+
+	prepare_menu() {
+		this.page.add_menu_item(__('Refresh'), () => {
+			this.render_plant_visualization();
+		});
+	}
+}
+
+frappe.ui.VisualPlantFloor = VisualPlantFloor;
\ No newline at end of file
diff --git a/erpnext/public/js/templates/visual_plant_floor_template.html b/erpnext/public/js/templates/visual_plant_floor_template.html
new file mode 100644
index 0000000..2e67085
--- /dev/null
+++ b/erpnext/public/js/templates/visual_plant_floor_template.html
@@ -0,0 +1,19 @@
+{% $.each(workstations, (idx, row) => { %}
+	<div class="workstation-wrapper">
+		<div class="workstation-image">
+			<div class="flex items-center justify-center h-32 border-b-grey text-6xl text-grey-100">
+				<a class="workstation-image-link" href="{{row.workstation_link}}">
+					{% if(row.status_image) { %}
+						<img class="workstation-image-cls" src="{{row.status_image}}">
+					{% } else { %}
+						<div class="workstation-image-cls workstation-abbr">{{frappe.get_abbr(row.name, 2)}}</div>
+					{% } %}
+				</a>
+			</div>
+		</div>
+		<div class="workstation-card text-center">
+			<p style="background-color:{{row.background_color}};color:#fff">{{row.status}}</p>
+			<div>{{row.workstation_name}}</div>
+		</div>
+	</div>
+{% }); %}
\ No newline at end of file
diff --git a/erpnext/public/scss/erpnext.scss b/erpnext/public/scss/erpnext.scss
index 8ab5973..1626b7c 100644
--- a/erpnext/public/scss/erpnext.scss
+++ b/erpnext/public/scss/erpnext.scss
@@ -490,3 +490,53 @@
 .exercise-col {
 	padding: 10px;
 }
+
+.plant-floor, .workstation-wrapper, .workstation-card p {
+	border-radius: var(--border-radius-md);
+	border: 1px solid var(--border-color);
+	box-shadow: none;
+	background-color: var(--card-bg);
+	position: relative;
+}
+
+.plant-floor {
+	padding-bottom: 25px;
+}
+
+.plant-floor-filter {
+	padding-top: 10px;
+	display: flex;
+	flex-wrap: wrap;
+}
+
+.plant-floor-container {
+	display: grid;
+	grid-template-columns: repeat(6,minmax(0,1fr));
+	gap: var(--margin-xl);
+}
+
+@media screen and (max-width: 620px) {
+	.plant-floor-container {
+		grid-template-columns: repeat(2,minmax(0,1fr));
+	}
+}
+
+.plant-floor-container .workstation-card {
+	padding: 5px;
+}
+
+.plant-floor-container .workstation-image-link {
+	width: 100%;
+	font-size: 50px;
+	margin: var(--margin-sm);
+	min-height: 9rem;
+}
+
+.workstation-abbr {
+	display: flex;
+	background-color: var(--control-bg);
+	height:100%;
+	width:100%;
+	align-items: center;
+	justify-content: center;
+}
\ No newline at end of file
diff --git a/erpnext/selling/doctype/customer/customer.py b/erpnext/selling/doctype/customer/customer.py
index 2f6775f..415216c 100644
--- a/erpnext/selling/doctype/customer/customer.py
+++ b/erpnext/selling/doctype/customer/customer.py
@@ -230,6 +230,7 @@
 
 		if self.flags.is_new_doc:
 			self.link_lead_address_and_contact()
+			self.copy_communication()
 
 		self.update_customer_groups()
 
@@ -291,6 +292,17 @@
 					linked_doc.append("links", dict(link_doctype="Customer", link_name=self.name))
 					linked_doc.save(ignore_permissions=self.flags.ignore_permissions)
 
+	def copy_communication(self):
+		if not self.lead_name or not frappe.db.get_single_value(
+			"CRM Settings", "carry_forward_communication_and_comments"
+		):
+			return
+
+		from erpnext.crm.utils import copy_comments, link_communications
+
+		copy_comments("Lead", self.lead_name, self)
+		link_communications("Lead", self.lead_name, self)
+
 	def validate_name_with_customer_group(self):
 		if frappe.db.exists("Customer Group", self.name):
 			frappe.throw(
diff --git a/erpnext/selling/doctype/product_bundle/product_bundle.json b/erpnext/selling/doctype/product_bundle/product_bundle.json
index c4f21b6..1c37b85 100644
--- a/erpnext/selling/doctype/product_bundle/product_bundle.json
+++ b/erpnext/selling/doctype/product_bundle/product_bundle.json
@@ -2,7 +2,7 @@
  "actions": [],
  "allow_import": 1,
  "creation": "2013-06-20 11:53:21",
- "description": "Aggregate group of **Items** into another **Item**. This is useful if you are bundling a certain **Items** into a package and you maintain stock of the packed **Items** and not the aggregate **Item**. \n\nThe package **Item** will have \"Is Stock Item\" as \"No\" and \"Is Sales Item\" as \"Yes\".\n\nFor Example: If you are selling Laptops and Backpacks separately and have a special price if the customer buys both, then the Laptop + Backpack will be a new Product Bundle Item.\n\nNote: BOM = Bill of Materials",
+ "description": "Aggregate a group of Items into another Item. This is useful if you are maintaining the stock of the packed items and not the bundled item",
  "doctype": "DocType",
  "engine": "InnoDB",
  "field_order": [
@@ -77,7 +77,7 @@
  "icon": "fa fa-sitemap",
  "idx": 1,
  "links": [],
- "modified": "2023-11-22 15:20:46.805114",
+ "modified": "2024-01-30 13:57:04.951788",
  "modified_by": "Administrator",
  "module": "Selling",
  "name": "Product Bundle",
diff --git a/erpnext/setup/doctype/item_group/item_group.json b/erpnext/setup/doctype/item_group/item_group.json
index dfa5a8e..7c9233f 100644
--- a/erpnext/setup/doctype/item_group/item_group.json
+++ b/erpnext/setup/doctype/item_group/item_group.json
@@ -4,7 +4,7 @@
  "allow_rename": 1,
  "autoname": "field:item_group_name",
  "creation": "2013-03-28 10:35:29",
- "description": "Item Classification",
+ "description": "An Item Group is a way to classify items based on types.",
  "doctype": "DocType",
  "document_type": "Setup",
  "engine": "InnoDB",
@@ -135,7 +135,7 @@
  "is_tree": 1,
  "links": [],
  "max_attachments": 3,
- "modified": "2023-10-12 13:44:13.611287",
+ "modified": "2024-01-30 14:08:38.485616",
  "modified_by": "Administrator",
  "module": "Setup",
  "name": "Item Group",
diff --git a/erpnext/setup/doctype/sales_person/sales_person.json b/erpnext/setup/doctype/sales_person/sales_person.json
index e526ac4..79bd841 100644
--- a/erpnext/setup/doctype/sales_person/sales_person.json
+++ b/erpnext/setup/doctype/sales_person/sales_person.json
@@ -4,7 +4,7 @@
  "allow_rename": 1,
  "autoname": "field:sales_person_name",
  "creation": "2013-01-10 16:34:24",
- "description": "All Sales Transactions can be tagged against multiple **Sales Persons** so that you can set and monitor targets.",
+ "description": "All Sales Transactions can be tagged against multiple Sales Persons so that you can set and monitor targets.",
  "doctype": "DocType",
  "document_type": "Setup",
  "engine": "InnoDB",
@@ -145,10 +145,11 @@
  "idx": 1,
  "is_tree": 1,
  "links": [],
- "modified": "2020-03-18 18:11:13.968024",
+ "modified": "2024-01-30 13:57:26.436991",
  "modified_by": "Administrator",
  "module": "Setup",
  "name": "Sales Person",
+ "naming_rule": "By fieldname",
  "nsm_parent_field": "parent_sales_person",
  "owner": "Administrator",
  "permissions": [
@@ -181,5 +182,6 @@
  "search_fields": "parent_sales_person",
  "show_name_in_global_search": 1,
  "sort_field": "modified",
- "sort_order": "ASC"
+ "sort_order": "ASC",
+ "states": []
 }
\ No newline at end of file
diff --git a/erpnext/setup/doctype/terms_and_conditions/terms_and_conditions.json b/erpnext/setup/doctype/terms_and_conditions/terms_and_conditions.json
index f884864..76e52ae 100644
--- a/erpnext/setup/doctype/terms_and_conditions/terms_and_conditions.json
+++ b/erpnext/setup/doctype/terms_and_conditions/terms_and_conditions.json
@@ -4,7 +4,7 @@
  "allow_rename": 1,
  "autoname": "field:title",
  "creation": "2013-01-10 16:34:24",
- "description": "Standard Terms and Conditions that can be added to Sales and Purchases.\n\nExamples:\n\n1. Validity of the offer.\n1. Payment Terms (In Advance, On Credit, part advance etc).\n1. What is extra (or payable by the Customer).\n1. Safety / usage warning.\n1. Warranty if any.\n1. Returns Policy.\n1. Terms of shipping, if applicable.\n1. Ways of addressing disputes, indemnity, liability, etc.\n1. Address and Contact of your Company.",
+ "description": "Standard Terms and Conditions that can be added to Sales and Purchases. Examples: Validity of the offer, Payment Terms, Safety and Usage, etc.",
  "doctype": "DocType",
  "document_type": "Setup",
  "engine": "InnoDB",
@@ -77,7 +77,7 @@
  "icon": "icon-legal",
  "idx": 1,
  "links": [],
- "modified": "2023-02-01 14:33:39.246532",
+ "modified": "2024-01-30 12:47:52.325531",
  "modified_by": "Administrator",
  "module": "Setup",
  "name": "Terms and Conditions",
diff --git a/erpnext/stock/doctype/item_price/item_price.json b/erpnext/stock/doctype/item_price/item_price.json
index 2390ee2..707f346 100644
--- a/erpnext/stock/doctype/item_price/item_price.json
+++ b/erpnext/stock/doctype/item_price/item_price.json
@@ -3,7 +3,7 @@
  "allow_import": 1,
  "autoname": "hash",
  "creation": "2013-05-02 16:29:48",
- "description": "Multiple Item prices.",
+ "description": "Log the selling and buying rate of an Item",
  "doctype": "DocType",
  "document_type": "Setup",
  "engine": "InnoDB",
@@ -220,7 +220,7 @@
  "idx": 1,
  "index_web_pages_for_search": 1,
  "links": [],
- "modified": "2024-01-24 02:20:26.145996",
+ "modified": "2024-01-30 14:02:19.304854",
  "modified_by": "Administrator",
  "module": "Stock",
  "name": "Item Price",
diff --git a/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py b/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py
index b4f7708..dec7506 100644
--- a/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py
+++ b/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py
@@ -149,6 +149,13 @@
 				self.get("items")[item_count - 1].applicable_charges += diff
 
 	def validate_applicable_charges_for_item(self):
+		if self.distribute_charges_based_on == "Distribute Manually" and len(self.taxes) > 1:
+			frappe.throw(
+				_(
+					"Please keep one Applicable Charges, when 'Distribute Charges Based On' is 'Distribute Manually'. For more charges, please create another Landed Cost Voucher."
+				)
+			)
+
 		based_on = self.distribute_charges_based_on.lower()
 
 		if based_on != "distribute manually":
diff --git a/erpnext/stock/doctype/material_request/material_request.py b/erpnext/stock/doctype/material_request/material_request.py
index e784b70..02fbd3d 100644
--- a/erpnext/stock/doctype/material_request/material_request.py
+++ b/erpnext/stock/doctype/material_request/material_request.py
@@ -462,6 +462,7 @@
 		postprocess,
 	)
 
+	doclist.set_onload("load_after_mapping", False)
 	return doclist
 
 
diff --git a/erpnext/stock/doctype/price_list/price_list.json b/erpnext/stock/doctype/price_list/price_list.json
index 56340fb..38cd1ee 100644
--- a/erpnext/stock/doctype/price_list/price_list.json
+++ b/erpnext/stock/doctype/price_list/price_list.json
@@ -1,434 +1,134 @@
 {
- "allow_copy": 0,
- "allow_events_in_timeline": 0,
- "allow_guest_to_view": 0,
+ "actions": [],
  "allow_import": 1,
  "allow_rename": 1,
  "autoname": "field:price_list_name",
- "beta": 0,
  "creation": "2013-01-25 11:35:09",
- "custom": 0,
- "description": "Price List Master",
- "docstatus": 0,
+ "description": "A Price List is a collection of Item Prices either Selling, Buying, or both",
  "doctype": "DocType",
  "document_type": "Setup",
- "editable_grid": 0,
  "engine": "InnoDB",
+ "field_order": [
+  "enabled",
+  "sb_1",
+  "price_list_name",
+  "currency",
+  "buying",
+  "selling",
+  "price_not_uom_dependent",
+  "column_break_3",
+  "countries"
+ ],
  "fields": [
   {
-   "allow_bulk_edit": 0,
-   "allow_in_quick_entry": 0,
-   "allow_on_submit": 0,
-   "bold": 0,
-   "collapsible": 0,
-   "columns": 0,
    "default": "1",
-   "fetch_if_empty": 0,
    "fieldname": "enabled",
    "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": "Enabled",
-   "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,
-   "translatable": 0,
-   "unique": 0
+   "label": "Enabled"
   },
   {
-   "allow_bulk_edit": 0,
-   "allow_in_quick_entry": 0,
-   "allow_on_submit": 0,
-   "bold": 0,
-   "collapsible": 0,
-   "columns": 0,
-   "fetch_if_empty": 0,
    "fieldname": "sb_1",
-   "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,
-   "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,
-   "translatable": 0,
-   "unique": 0
+   "fieldtype": "Section Break"
   },
   {
-   "allow_bulk_edit": 0,
-   "allow_in_quick_entry": 0,
-   "allow_on_submit": 0,
-   "bold": 0,
-   "collapsible": 0,
-   "columns": 0,
-   "fetch_if_empty": 0,
    "fieldname": "price_list_name",
    "fieldtype": "Data",
-   "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": "Price List Name",
-   "length": 0,
    "no_copy": 1,
    "oldfieldname": "price_list_name",
    "oldfieldtype": "Data",
-   "permlevel": 0,
-   "print_hide": 0,
-   "print_hide_if_no_value": 0,
-   "read_only": 0,
-   "remember_last_selected_value": 0,
-   "report_hide": 0,
    "reqd": 1,
-   "search_index": 0,
-   "set_only_once": 0,
-   "translatable": 0,
    "unique": 1
   },
   {
-   "allow_bulk_edit": 0,
-   "allow_in_quick_entry": 0,
-   "allow_on_submit": 0,
-   "bold": 0,
-   "collapsible": 0,
-   "columns": 0,
-   "fetch_if_empty": 0,
    "fieldname": "currency",
    "fieldtype": "Link",
-   "hidden": 0,
-   "ignore_user_permissions": 0,
-   "ignore_xss_filter": 0,
-   "in_filter": 0,
-   "in_global_search": 0,
    "in_list_view": 1,
    "in_standard_filter": 1,
    "label": "Currency",
-   "length": 0,
-   "no_copy": 0,
    "options": "Currency",
-   "permlevel": 0,
-   "print_hide": 0,
-   "print_hide_if_no_value": 0,
-   "read_only": 0,
-   "remember_last_selected_value": 0,
-   "report_hide": 0,
-   "reqd": 1,
-   "search_index": 0,
-   "set_only_once": 0,
-   "translatable": 0,
-   "unique": 0
+   "reqd": 1
   },
   {
-   "allow_bulk_edit": 0,
-   "allow_in_quick_entry": 0,
-   "allow_on_submit": 0,
-   "bold": 0,
-   "collapsible": 0,
-   "columns": 0,
-   "fetch_if_empty": 0,
+   "default": "0",
    "fieldname": "buying",
    "fieldtype": "Check",
-   "hidden": 0,
-   "ignore_user_permissions": 0,
-   "ignore_xss_filter": 0,
-   "in_filter": 0,
-   "in_global_search": 0,
    "in_list_view": 1,
-   "in_standard_filter": 0,
-   "label": "Buying",
-   "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,
-   "translatable": 0,
-   "unique": 0
+   "label": "Buying"
   },
   {
-   "allow_bulk_edit": 0,
-   "allow_in_quick_entry": 0,
-   "allow_on_submit": 0,
-   "bold": 0,
-   "collapsible": 0,
-   "columns": 0,
-   "fetch_if_empty": 0,
+   "default": "0",
    "fieldname": "selling",
    "fieldtype": "Check",
-   "hidden": 0,
-   "ignore_user_permissions": 0,
-   "ignore_xss_filter": 0,
-   "in_filter": 0,
-   "in_global_search": 0,
    "in_list_view": 1,
-   "in_standard_filter": 0,
-   "label": "Selling",
-   "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,
-   "translatable": 0,
-   "unique": 0
+   "label": "Selling"
   },
   {
-   "allow_bulk_edit": 0,
-   "allow_in_quick_entry": 0,
-   "allow_on_submit": 0,
-   "bold": 0,
-   "collapsible": 0,
-   "columns": 0,
-   "fetch_if_empty": 0,
+   "default": "0",
    "fieldname": "price_not_uom_dependent",
    "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": "Price Not UOM Dependent",
-   "length": 0,
-   "no_copy": 0,
-   "permlevel": 0,
-   "precision": "",
-   "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,
-   "translatable": 0,
-   "unique": 0
+   "label": "Price Not UOM Dependent"
   },
   {
-   "allow_bulk_edit": 0,
-   "allow_in_quick_entry": 0,
-   "allow_on_submit": 0,
-   "bold": 0,
-   "collapsible": 0,
-   "columns": 0,
-   "fetch_if_empty": 0,
    "fieldname": "column_break_3",
-   "fieldtype": "Column 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,
-   "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,
-   "translatable": 0,
-   "unique": 0
+   "fieldtype": "Column Break"
   },
   {
-   "allow_bulk_edit": 0,
-   "allow_in_quick_entry": 0,
-   "allow_on_submit": 0,
-   "bold": 0,
-   "collapsible": 0,
-   "columns": 0,
-   "fetch_if_empty": 0,
    "fieldname": "countries",
    "fieldtype": "Table",
-   "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": "Applicable for Countries",
-   "length": 0,
-   "no_copy": 0,
-   "options": "Price List Country",
-   "permlevel": 0,
-   "precision": "",
-   "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,
-   "translatable": 0,
-   "unique": 0
+   "options": "Price List Country"
   }
  ],
- "has_web_view": 0,
- "hide_heading": 0,
- "hide_toolbar": 0,
  "icon": "fa fa-tags",
  "idx": 1,
- "image_view": 0,
- "in_create": 0,
- "is_submittable": 0,
- "issingle": 0,
- "istable": 0,
+ "links": [],
  "max_attachments": 1,
- "modified": "2019-06-24 17:16:28.027302",
+ "modified": "2024-01-30 14:39:26.328837",
  "modified_by": "Administrator",
  "module": "Stock",
  "name": "Price List",
+ "naming_rule": "By fieldname",
  "owner": "Administrator",
  "permissions": [
   {
-   "amend": 0,
-   "cancel": 0,
-   "create": 0,
-   "delete": 0,
-   "email": 0,
-   "export": 0,
-   "if_owner": 0,
-   "import": 0,
-   "permlevel": 0,
-   "print": 0,
    "read": 1,
    "report": 1,
-   "role": "Sales User",
-   "set_user_permissions": 0,
-   "share": 0,
-   "submit": 0,
-   "write": 0
+   "role": "Sales User"
   },
   {
-   "amend": 0,
-   "cancel": 0,
    "create": 1,
    "delete": 1,
-   "email": 0,
    "export": 1,
-   "if_owner": 0,
    "import": 1,
-   "permlevel": 0,
-   "print": 0,
    "read": 1,
    "report": 1,
    "role": "Sales Master Manager",
-   "set_user_permissions": 0,
    "share": 1,
-   "submit": 0,
    "write": 1
   },
   {
-   "amend": 0,
-   "cancel": 0,
-   "create": 0,
-   "delete": 0,
-   "email": 0,
-   "export": 0,
-   "if_owner": 0,
-   "import": 0,
-   "permlevel": 0,
-   "print": 0,
    "read": 1,
    "report": 1,
-   "role": "Purchase User",
-   "set_user_permissions": 0,
-   "share": 0,
-   "submit": 0,
-   "write": 0
+   "role": "Purchase User"
   },
   {
-   "amend": 0,
-   "cancel": 0,
    "create": 1,
    "delete": 1,
-   "email": 0,
-   "export": 0,
-   "if_owner": 0,
-   "import": 0,
-   "permlevel": 0,
-   "print": 0,
    "read": 1,
    "report": 1,
    "role": "Purchase Master Manager",
-   "set_user_permissions": 0,
    "share": 1,
-   "submit": 0,
    "write": 1
   },
   {
-   "amend": 0,
-   "cancel": 0,
-   "create": 0,
-   "delete": 0,
-   "email": 0,
-   "export": 0,
-   "if_owner": 0,
-   "import": 0,
-   "permlevel": 0,
-   "print": 0,
    "read": 1,
-   "report": 0,
-   "role": "Manufacturing User",
-   "set_user_permissions": 0,
-   "share": 0,
-   "submit": 0,
-   "write": 0
+   "role": "Manufacturing User"
   }
  ],
- "quick_entry": 0,
- "read_only": 0,
- "read_only_onload": 0,
  "search_fields": "currency",
  "show_name_in_global_search": 1,
+ "sort_field": "modified",
  "sort_order": "ASC",
- "track_changes": 0,
- "track_seen": 0,
- "track_views": 0
+ "states": []
 }
\ No newline at end of file
diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py
index bf6080b..a1f97c9 100644
--- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py
+++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py
@@ -1360,16 +1360,16 @@
 	for lcv in landed_cost_vouchers:
 		landed_cost_voucher_doc = frappe.get_doc("Landed Cost Voucher", lcv.parent)
 
+		based_on_field = None
 		# Use amount field for total item cost for manually cost distributed LCVs
-		if landed_cost_voucher_doc.distribute_charges_based_on == "Distribute Manually":
-			based_on_field = "amount"
-		else:
+		if landed_cost_voucher_doc.distribute_charges_based_on != "Distribute Manually":
 			based_on_field = frappe.scrub(landed_cost_voucher_doc.distribute_charges_based_on)
 
 		total_item_cost = 0
 
-		for item in landed_cost_voucher_doc.items:
-			total_item_cost += item.get(based_on_field)
+		if based_on_field:
+			for item in landed_cost_voucher_doc.items:
+				total_item_cost += item.get(based_on_field)
 
 		for item in landed_cost_voucher_doc.items:
 			if item.receipt_document == purchase_document:
diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py
index dd68d16..65c08c1 100644
--- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py
+++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py
@@ -1675,9 +1675,10 @@
 		make_stock_entry(
 			purpose="Material Receipt",
 			item_code=item.name,
-			qty=15,
+			qty=20,
 			company=company,
 			to_warehouse=from_warehouse,
+			posting_date=add_days(today(), -3),
 		)
 
 		# Step 3: Create Delivery Note with Internal Customer
@@ -1700,6 +1701,8 @@
 		from erpnext.stock.doctype.delivery_note.delivery_note import make_inter_company_purchase_receipt
 
 		pr = make_inter_company_purchase_receipt(dn.name)
+		pr.set_posting_time = 1
+		pr.posting_date = today()
 		pr.items[0].qty = 15
 		pr.items[0].from_warehouse = target_warehouse
 		pr.items[0].warehouse = to_warehouse
@@ -1718,6 +1721,7 @@
 			company=company,
 			from_warehouse=from_warehouse,
 			to_warehouse=target_warehouse,
+			posting_date=add_days(pr.posting_date, -1),
 		)
 
 		pr.reload()
diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py
index c371b70..faccfa3 100644
--- a/erpnext/stock/doctype/stock_entry/stock_entry.py
+++ b/erpnext/stock/doctype/stock_entry/stock_entry.py
@@ -228,7 +228,6 @@
 			self.fg_completed_qty = 0.0
 
 		self.validate_serialized_batch()
-		self.set_actual_qty()
 		self.calculate_rate_and_amount()
 		self.validate_putaway_capacity()
 
diff --git a/erpnext/stock/doctype/stock_entry/test_stock_entry.py b/erpnext/stock/doctype/stock_entry/test_stock_entry.py
index 23dacc8..9f3435e 100644
--- a/erpnext/stock/doctype/stock_entry/test_stock_entry.py
+++ b/erpnext/stock/doctype/stock_entry/test_stock_entry.py
@@ -1785,6 +1785,48 @@
 
 		self.assertRaises(frappe.ValidationError, se1.cancel)
 
+	def test_auto_reorder_level(self):
+		from erpnext.stock.reorder_item import reorder_item
+
+		item_doc = make_item(
+			"Test Auto Reorder Item - 001",
+			properties={"stock_uom": "Kg", "purchase_uom": "Nos", "is_stock_item": 1},
+			uoms=[{"uom": "Nos", "conversion_factor": 5}],
+		)
+
+		if not frappe.db.exists("Item Reorder", {"parent": item_doc.name}):
+			item_doc.append(
+				"reorder_levels",
+				{
+					"warehouse_reorder_level": 0,
+					"warehouse_reorder_qty": 10,
+					"warehouse": "_Test Warehouse - _TC",
+					"material_request_type": "Purchase",
+				},
+			)
+
+		item_doc.save(ignore_permissions=True)
+
+		frappe.db.set_single_value("Stock Settings", "auto_indent", 1)
+
+		mr_list = reorder_item()
+
+		frappe.db.set_single_value("Stock Settings", "auto_indent", 0)
+		mrs = frappe.get_all(
+			"Material Request Item",
+			fields=["qty", "stock_uom", "stock_qty"],
+			filters={"item_code": item_doc.name, "uom": "Nos"},
+		)
+
+		for mri in mrs:
+			self.assertEqual(mri.stock_uom, "Kg")
+			self.assertEqual(mri.stock_qty, 10)
+			self.assertEqual(mri.qty, 2)
+
+		for mr in mr_list:
+			mr.cancel()
+			mr.delete()
+
 
 def make_serialized_item(**args):
 	args = frappe._dict(args)
diff --git a/erpnext/stock/doctype/stock_settings/stock_settings.json b/erpnext/stock/doctype/stock_settings/stock_settings.json
index f84456a..dc27974 100644
--- a/erpnext/stock/doctype/stock_settings/stock_settings.json
+++ b/erpnext/stock/doctype/stock_settings/stock_settings.json
@@ -1,7 +1,7 @@
 {
  "actions": [],
  "creation": "2013-06-24 16:37:54",
- "description": "Settings",
+ "description": "Default settings for your stock-related transactions",
  "doctype": "DocType",
  "engine": "InnoDB",
  "field_order": [
@@ -427,7 +427,7 @@
  "index_web_pages_for_search": 1,
  "issingle": 1,
  "links": [],
- "modified": "2024-01-24 02:20:26.145996",
+ "modified": "2024-01-30 14:03:52.143457",
  "modified_by": "Administrator",
  "module": "Stock",
  "name": "Stock Settings",
diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py
index ebcdd11..1cb1057 100644
--- a/erpnext/stock/get_item_details.py
+++ b/erpnext/stock/get_item_details.py
@@ -86,7 +86,8 @@
 
 	get_party_item_code(args, item, out)
 
-	set_valuation_rate(out, args)
+	if args.get("doctype") in ["Sales Order", "Quotation"]:
+		set_valuation_rate(out, args)
 
 	update_party_blanket_order(args, out)
 
@@ -269,7 +270,9 @@
 	if not item:
 		item = frappe.get_doc("Item", args.get("item_code"))
 
-	if item.variant_of and not item.taxes:
+	if (
+		item.variant_of and not item.taxes and frappe.db.exists("Item Tax", {"parent": item.variant_of})
+	):
 		item.update_template_tables()
 
 	item_defaults = get_item_defaults(item.name, args.company)
@@ -543,7 +546,7 @@
 		args = {
 			"company": company,
 			"tax_category": tax_category,
-			"net_rate": item_rates.get(item_code[1]),
+			"base_net_rate": item_rates.get(item_code[1]),
 		}
 
 		if item_tax_templates:
@@ -635,7 +638,7 @@
 	if not flt(tax.maximum_net_rate):
 		# No range specified, just ignore
 		return True
-	elif flt(tax.minimum_net_rate) <= flt(args.get("net_rate")) <= flt(tax.maximum_net_rate):
+	elif flt(tax.minimum_net_rate) <= flt(args.get("base_net_rate")) <= flt(tax.maximum_net_rate):
 		return True
 
 	return False
diff --git a/erpnext/stock/reorder_item.py b/erpnext/stock/reorder_item.py
index 276531a..59f8b20 100644
--- a/erpnext/stock/reorder_item.py
+++ b/erpnext/stock/reorder_item.py
@@ -34,73 +34,157 @@
 		erpnext.get_default_company() or frappe.db.sql("""select name from tabCompany limit 1""")[0][0]
 	)
 
-	items_to_consider = frappe.db.sql_list(
-		"""select name from `tabItem` item
-		where is_stock_item=1 and has_variants=0
-			and disabled=0
-			and (end_of_life is null or end_of_life='0000-00-00' or end_of_life > %(today)s)
-			and (exists (select name from `tabItem Reorder` ir where ir.parent=item.name)
-				or (variant_of is not null and variant_of != ''
-				and exists (select name from `tabItem Reorder` ir where ir.parent=item.variant_of))
-			)""",
-		{"today": nowdate()},
-	)
+	items_to_consider = get_items_for_reorder()
 
 	if not items_to_consider:
 		return
 
 	item_warehouse_projected_qty = get_item_warehouse_projected_qty(items_to_consider)
 
-	def add_to_material_request(
-		item_code, warehouse, reorder_level, reorder_qty, material_request_type, warehouse_group=None
-	):
-		if warehouse not in warehouse_company:
+	def add_to_material_request(**kwargs):
+		if isinstance(kwargs, dict):
+			kwargs = frappe._dict(kwargs)
+
+		if kwargs.warehouse not in warehouse_company:
 			# a disabled warehouse
 			return
 
-		reorder_level = flt(reorder_level)
-		reorder_qty = flt(reorder_qty)
+		reorder_level = flt(kwargs.reorder_level)
+		reorder_qty = flt(kwargs.reorder_qty)
 
 		# projected_qty will be 0 if Bin does not exist
-		if warehouse_group:
-			projected_qty = flt(item_warehouse_projected_qty.get(item_code, {}).get(warehouse_group))
+		if kwargs.warehouse_group:
+			projected_qty = flt(
+				item_warehouse_projected_qty.get(kwargs.item_code, {}).get(kwargs.warehouse_group)
+			)
 		else:
-			projected_qty = flt(item_warehouse_projected_qty.get(item_code, {}).get(warehouse))
+			projected_qty = flt(
+				item_warehouse_projected_qty.get(kwargs.item_code, {}).get(kwargs.warehouse)
+			)
 
 		if (reorder_level or reorder_qty) and projected_qty <= reorder_level:
 			deficiency = reorder_level - projected_qty
 			if deficiency > reorder_qty:
 				reorder_qty = deficiency
 
-			company = warehouse_company.get(warehouse) or default_company
+			company = warehouse_company.get(kwargs.warehouse) or default_company
 
-			material_requests[material_request_type].setdefault(company, []).append(
-				{"item_code": item_code, "warehouse": warehouse, "reorder_qty": reorder_qty}
+			material_requests[kwargs.material_request_type].setdefault(company, []).append(
+				{
+					"item_code": kwargs.item_code,
+					"warehouse": kwargs.warehouse,
+					"reorder_qty": reorder_qty,
+					"item_details": kwargs.item_details,
+				}
 			)
 
-	for item_code in items_to_consider:
-		item = frappe.get_doc("Item", item_code)
+	for item_code, reorder_levels in items_to_consider.items():
+		for d in reorder_levels:
+			if d.has_variants:
+				continue
 
-		if item.variant_of and not item.get("reorder_levels"):
-			item.update_template_tables()
-
-		if item.get("reorder_levels"):
-			for d in item.get("reorder_levels"):
-				add_to_material_request(
-					item_code,
-					d.warehouse,
-					d.warehouse_reorder_level,
-					d.warehouse_reorder_qty,
-					d.material_request_type,
-					warehouse_group=d.warehouse_group,
-				)
+			add_to_material_request(
+				item_code=item_code,
+				warehouse=d.warehouse,
+				reorder_level=d.warehouse_reorder_level,
+				reorder_qty=d.warehouse_reorder_qty,
+				material_request_type=d.material_request_type,
+				warehouse_group=d.warehouse_group,
+				item_details=frappe._dict(
+					{
+						"item_code": item_code,
+						"name": item_code,
+						"item_name": d.item_name,
+						"item_group": d.item_group,
+						"brand": d.brand,
+						"description": d.description,
+						"stock_uom": d.stock_uom,
+						"purchase_uom": d.purchase_uom,
+					}
+				),
+			)
 
 	if material_requests:
 		return create_material_request(material_requests)
 
 
+def get_items_for_reorder() -> dict[str, list]:
+	reorder_table = frappe.qb.DocType("Item Reorder")
+	item_table = frappe.qb.DocType("Item")
+
+	query = (
+		frappe.qb.from_(reorder_table)
+		.inner_join(item_table)
+		.on(reorder_table.parent == item_table.name)
+		.select(
+			reorder_table.warehouse,
+			reorder_table.warehouse_group,
+			reorder_table.material_request_type,
+			reorder_table.warehouse_reorder_level,
+			reorder_table.warehouse_reorder_qty,
+			item_table.name,
+			item_table.stock_uom,
+			item_table.purchase_uom,
+			item_table.description,
+			item_table.item_name,
+			item_table.item_group,
+			item_table.brand,
+			item_table.variant_of,
+			item_table.has_variants,
+		)
+		.where(
+			(item_table.disabled == 0)
+			& (item_table.is_stock_item == 1)
+			& (
+				(item_table.end_of_life.isnull())
+				| (item_table.end_of_life > nowdate())
+				| (item_table.end_of_life == "0000-00-00")
+			)
+		)
+	)
+
+	data = query.run(as_dict=True)
+	itemwise_reorder = frappe._dict({})
+	for d in data:
+		itemwise_reorder.setdefault(d.name, []).append(d)
+
+	itemwise_reorder = get_reorder_levels_for_variants(itemwise_reorder)
+
+	return itemwise_reorder
+
+
+def get_reorder_levels_for_variants(itemwise_reorder):
+	item_table = frappe.qb.DocType("Item")
+
+	query = (
+		frappe.qb.from_(item_table)
+		.select(
+			item_table.name,
+			item_table.variant_of,
+		)
+		.where(
+			(item_table.disabled == 0)
+			& (item_table.is_stock_item == 1)
+			& (
+				(item_table.end_of_life.isnull())
+				| (item_table.end_of_life > nowdate())
+				| (item_table.end_of_life == "0000-00-00")
+			)
+			& (item_table.variant_of.notnull())
+		)
+	)
+
+	variants_item = query.run(as_dict=True)
+	for row in variants_item:
+		if not itemwise_reorder.get(row.name) and itemwise_reorder.get(row.variant_of):
+			itemwise_reorder.setdefault(row.name, []).extend(itemwise_reorder.get(row.variant_of, []))
+
+	return itemwise_reorder
+
+
 def get_item_warehouse_projected_qty(items_to_consider):
 	item_warehouse_projected_qty = {}
+	items_to_consider = list(items_to_consider.keys())
 
 	for item_code, warehouse, projected_qty in frappe.db.sql(
 		"""select item_code, warehouse, projected_qty
@@ -164,7 +248,7 @@
 
 				for d in items:
 					d = frappe._dict(d)
-					item = frappe.get_doc("Item", d.item_code)
+					item = d.get("item_details")
 					uom = item.stock_uom
 					conversion_factor = 1.0
 
@@ -190,6 +274,7 @@
 							"item_code": d.item_code,
 							"schedule_date": add_days(nowdate(), cint(item.lead_time_days)),
 							"qty": qty,
+							"conversion_factor": conversion_factor,
 							"uom": uom,
 							"stock_uom": item.stock_uom,
 							"warehouse": d.warehouse,
diff --git a/erpnext/stock/report/stock_balance/stock_balance.py b/erpnext/stock/report/stock_balance/stock_balance.py
index ed84a5c..2693238 100644
--- a/erpnext/stock/report/stock_balance/stock_balance.py
+++ b/erpnext/stock/report/stock_balance/stock_balance.py
@@ -90,8 +90,7 @@
 				self.opening_data.setdefault(group_by_key, entry)
 
 	def prepare_new_data(self):
-		if not self.sle_entries:
-			return
+		self.item_warehouse_map = self.get_item_warehouse_map()
 
 		if self.filters.get("show_stock_ageing_data"):
 			self.filters["show_warehouse_wise_stock"] = True
@@ -99,7 +98,8 @@
 
 		_func = itemgetter(1)
 
-		self.item_warehouse_map = self.get_item_warehouse_map()
+		del self.sle_entries
+
 		sre_details = self.get_sre_reserved_qty_details()
 
 		variant_values = {}
@@ -143,15 +143,22 @@
 		item_warehouse_map = {}
 		self.opening_vouchers = self.get_opening_vouchers()
 
-		for entry in self.sle_entries:
-			group_by_key = self.get_group_by_key(entry)
-			if group_by_key not in item_warehouse_map:
-				self.initialize_data(item_warehouse_map, group_by_key, entry)
+		if self.filters.get("show_stock_ageing_data"):
+			self.sle_entries = self.sle_query.run(as_dict=True)
 
-			self.prepare_item_warehouse_map(item_warehouse_map, entry, group_by_key)
+		with frappe.db.unbuffered_cursor():
+			if not self.filters.get("show_stock_ageing_data"):
+				self.sle_entries = self.sle_query.run(as_dict=True, as_iterator=True)
 
-			if self.opening_data.get(group_by_key):
-				del self.opening_data[group_by_key]
+			for entry in self.sle_entries:
+				group_by_key = self.get_group_by_key(entry)
+				if group_by_key not in item_warehouse_map:
+					self.initialize_data(item_warehouse_map, group_by_key, entry)
+
+				self.prepare_item_warehouse_map(item_warehouse_map, entry, group_by_key)
+
+				if self.opening_data.get(group_by_key):
+					del self.opening_data[group_by_key]
 
 		for group_by_key, entry in self.opening_data.items():
 			if group_by_key not in item_warehouse_map:
@@ -252,7 +259,8 @@
 			.where(
 				(table.docstatus == 1)
 				& (table.company == self.filters.company)
-				& ((table.to_date <= self.from_date))
+				& (table.to_date <= self.from_date)
+				& (table.status == "Completed")
 			)
 			.orderby(table.to_date, order=Order.desc)
 			.limit(1)
@@ -305,7 +313,7 @@
 		if self.filters.get("company"):
 			query = query.where(sle.company == self.filters.get("company"))
 
-		self.sle_entries = query.run(as_dict=True)
+		self.sle_query = query
 
 	def apply_inventory_dimensions_filters(self, query, sle) -> str:
 		inventory_dimension_fields = self.get_inventory_dimension_fields()
diff --git a/erpnext/stock/serial_batch_bundle.py b/erpnext/stock/serial_batch_bundle.py
index 4cfe5d8..78df755 100644
--- a/erpnext/stock/serial_batch_bundle.py
+++ b/erpnext/stock/serial_batch_bundle.py
@@ -283,6 +283,7 @@
 				if (sn_table.purchase_document_no != self.sle.voucher_no and self.sle.is_cancelled != 1)
 				else "Inactive",
 			)
+			.set(sn_table.company, self.sle.company)
 			.where(sn_table.name.isin(serial_nos))
 		).run()
 
diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py
index 45764f3..e88b192 100644
--- a/erpnext/stock/stock_ledger.py
+++ b/erpnext/stock/stock_ledger.py
@@ -897,9 +897,12 @@
 
 		self.wh_data.stock_value = round_off_if_near_zero(self.wh_data.stock_value + doc.total_amount)
 
-		self.wh_data.qty_after_transaction += doc.total_qty
+		precision = doc.precision("total_qty")
+		self.wh_data.qty_after_transaction += flt(doc.total_qty, precision)
 		if self.wh_data.qty_after_transaction:
-			self.wh_data.valuation_rate = self.wh_data.stock_value / self.wh_data.qty_after_transaction
+			self.wh_data.valuation_rate = flt(self.wh_data.stock_value, precision) / flt(
+				self.wh_data.qty_after_transaction, precision
+			)
 
 	def validate_negative_stock(self, sle):
 		"""
diff --git a/erpnext/templates/pages/order.html b/erpnext/templates/pages/order.html
index 97bf487..6c59a96 100644
--- a/erpnext/templates/pages/order.html
+++ b/erpnext/templates/pages/order.html
@@ -34,6 +34,18 @@
 				</a>
 			</ul>
 		</div>
+		{% if show_pay_button %}
+			<div class="form-column col-sm-6">
+				<div class="page-header-actions-block" data-html-block="header-actions">
+					<p>
+						<a href="/api/method/erpnext.accounts.doctype.payment_request.payment_request.make_payment_request?dn={{ doc.name }}&dt={{ doc.doctype }}&submit_doc=1&order_type=Shopping Cart"
+							class="btn btn-primary btn-sm" id="pay-for-order">
+							{{ _("Pay") }} {{doc.get_formatted("grand_total") }}
+						</a>
+					</p>
+				</div>
+			</div>
+		{% endif %}
 	</div>
 {% endblock %}
 
diff --git a/erpnext/templates/pages/order.py b/erpnext/templates/pages/order.py
index d0968bf..21d4b86 100644
--- a/erpnext/templates/pages/order.py
+++ b/erpnext/templates/pages/order.py
@@ -48,7 +48,10 @@
 			)
 			context.available_loyalty_points = int(loyalty_program_details.get("loyalty_points"))
 
-	context.show_pay_button = frappe.db.get_single_value("Buying Settings", "show_pay_button")
+	context.show_pay_button = (
+		"payments" in frappe.get_installed_apps()
+		and frappe.db.get_single_value("Buying Settings", "show_pay_button")
+	)
 	context.show_make_pi_button = False
 	if context.doc.get("supplier"):
 		# show Make Purchase Invoice button based on permission