Merge pull request #40917 from frappe/l10n_develop

fix: sync translations from crowdin
diff --git a/erpnext/accounts/doctype/gl_entry/gl_entry.py b/erpnext/accounts/doctype/gl_entry/gl_entry.py
index c494eec..d74224c 100644
--- a/erpnext/accounts/doctype/gl_entry/gl_entry.py
+++ b/erpnext/accounts/doctype/gl_entry/gl_entry.py
@@ -182,6 +182,7 @@
 				and self.company == dimension.company
 				and dimension.mandatory_for_pl
 				and not dimension.disabled
+				and not self.is_cancelled
 			):
 				if not self.get(dimension.fieldname):
 					frappe.throw(
@@ -195,6 +196,7 @@
 				and self.company == dimension.company
 				and dimension.mandatory_for_bs
 				and not dimension.disabled
+				and not self.is_cancelled
 			):
 				if not self.get(dimension.fieldname):
 					frappe.throw(
diff --git a/erpnext/accounts/doctype/ledger_health/__init__.py b/erpnext/accounts/doctype/ledger_health/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/accounts/doctype/ledger_health/__init__.py
diff --git a/erpnext/accounts/doctype/ledger_health/ledger_health.js b/erpnext/accounts/doctype/ledger_health/ledger_health.js
new file mode 100644
index 0000000..e207dae
--- /dev/null
+++ b/erpnext/accounts/doctype/ledger_health/ledger_health.js
@@ -0,0 +1,8 @@
+// Copyright (c) 2024, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+
+// frappe.ui.form.on("Ledger Health", {
+// 	refresh(frm) {
+
+// 	},
+// });
diff --git a/erpnext/accounts/doctype/ledger_health/ledger_health.json b/erpnext/accounts/doctype/ledger_health/ledger_health.json
new file mode 100644
index 0000000..fb2da3d
--- /dev/null
+++ b/erpnext/accounts/doctype/ledger_health/ledger_health.json
@@ -0,0 +1,70 @@
+{
+ "actions": [],
+ "autoname": "autoincrement",
+ "creation": "2024-03-26 17:01:47.443986",
+ "doctype": "DocType",
+ "engine": "InnoDB",
+ "field_order": [
+  "voucher_type",
+  "voucher_no",
+  "checked_on",
+  "debit_credit_mismatch",
+  "general_and_payment_ledger_mismatch"
+ ],
+ "fields": [
+  {
+   "fieldname": "voucher_type",
+   "fieldtype": "Data",
+   "label": "Voucher Type"
+  },
+  {
+   "fieldname": "voucher_no",
+   "fieldtype": "Data",
+   "label": "Voucher No"
+  },
+  {
+   "default": "0",
+   "fieldname": "debit_credit_mismatch",
+   "fieldtype": "Check",
+   "label": "Debit-Credit mismatch"
+  },
+  {
+   "fieldname": "checked_on",
+   "fieldtype": "Datetime",
+   "label": "Checked On"
+  },
+  {
+   "default": "0",
+   "fieldname": "general_and_payment_ledger_mismatch",
+   "fieldtype": "Check",
+   "label": "General and Payment Ledger mismatch"
+  }
+ ],
+ "in_create": 1,
+ "index_web_pages_for_search": 1,
+ "links": [],
+ "modified": "2024-04-09 11:16:07.044484",
+ "modified_by": "Administrator",
+ "module": "Accounts",
+ "name": "Ledger Health",
+ "naming_rule": "Autoincrement",
+ "owner": "Administrator",
+ "permissions": [
+  {
+   "create": 1,
+   "delete": 1,
+   "email": 1,
+   "export": 1,
+   "print": 1,
+   "read": 1,
+   "report": 1,
+   "role": "System Manager",
+   "share": 1,
+   "write": 1
+  }
+ ],
+ "read_only": 1,
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "states": []
+}
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/ledger_health/ledger_health.py b/erpnext/accounts/doctype/ledger_health/ledger_health.py
new file mode 100644
index 0000000..590ff80
--- /dev/null
+++ b/erpnext/accounts/doctype/ledger_health/ledger_health.py
@@ -0,0 +1,25 @@
+# Copyright (c) 2024, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+# import frappe
+from frappe.model.document import Document
+
+
+class LedgerHealth(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
+
+		checked_on: DF.Datetime | None
+		debit_credit_mismatch: DF.Check
+		general_and_payment_ledger_mismatch: DF.Check
+		name: DF.Int | None
+		voucher_no: DF.Data | None
+		voucher_type: DF.Data | None
+	# end: auto-generated types
+
+	pass
diff --git a/erpnext/accounts/doctype/ledger_health/test_ledger_health.py b/erpnext/accounts/doctype/ledger_health/test_ledger_health.py
new file mode 100644
index 0000000..d35b39d
--- /dev/null
+++ b/erpnext/accounts/doctype/ledger_health/test_ledger_health.py
@@ -0,0 +1,109 @@
+# Copyright (c) 2024, Frappe Technologies Pvt. Ltd. and Contributors
+# See license.txt
+
+import frappe
+from frappe import qb
+from frappe.tests.utils import FrappeTestCase
+from frappe.utils import nowdate
+
+from erpnext.accounts.test.accounts_mixin import AccountsTestMixin
+from erpnext.accounts.utils import run_ledger_health_checks
+
+
+class TestLedgerHealth(AccountsTestMixin, FrappeTestCase):
+	def setUp(self):
+		self.create_company()
+		self.create_customer()
+		self.configure_monitoring_tool()
+		self.clear_old_entries()
+
+	def tearDown(self):
+		frappe.db.rollback()
+
+	def configure_monitoring_tool(self):
+		monitor_settings = frappe.get_doc("Ledger Health Monitor")
+		monitor_settings.enable_health_monitor = True
+		monitor_settings.enable_for_last_x_days = 60
+		monitor_settings.debit_credit_mismatch = True
+		monitor_settings.general_and_payment_ledger_mismatch = True
+		exists = [x for x in monitor_settings.companies if x.company == self.company]
+		if not exists:
+			monitor_settings.append("companies", {"company": self.company})
+		monitor_settings.save()
+
+	def clear_old_entries(self):
+		super().clear_old_entries()
+		lh = qb.DocType("Ledger Health")
+		qb.from_(lh).delete().run()
+
+	def create_journal(self):
+		je = frappe.new_doc("Journal Entry")
+		je.company = self.company
+		je.voucher_type = "Journal Entry"
+		je.posting_date = nowdate()
+		je.append(
+			"accounts",
+			{
+				"account": self.debit_to,
+				"party_type": "Customer",
+				"party": self.customer,
+				"debit_in_account_currency": 10000,
+			},
+		)
+		je.append("accounts", {"account": self.income_account, "credit_in_account_currency": 10000})
+		je.save().submit()
+		self.je = je
+
+	def test_debit_credit_mismatch(self):
+		self.create_journal()
+
+		# manually cause debit-credit mismatch
+		gle = frappe.db.get_all(
+			"GL Entry", filters={"voucher_no": self.je.name, "account": self.income_account}
+		)[0]
+		frappe.db.set_value("GL Entry", gle.name, "credit", 8000)
+
+		run_ledger_health_checks()
+		expected = {
+			"voucher_type": self.je.doctype,
+			"voucher_no": self.je.name,
+			"debit_credit_mismatch": True,
+			"general_and_payment_ledger_mismatch": False,
+		}
+		actual = frappe.db.get_all(
+			"Ledger Health",
+			fields=[
+				"voucher_type",
+				"voucher_no",
+				"debit_credit_mismatch",
+				"general_and_payment_ledger_mismatch",
+			],
+		)
+		self.assertEqual(len(actual), 1)
+		self.assertEqual(expected, actual[0])
+
+	def test_gl_and_pl_mismatch(self):
+		self.create_journal()
+
+		# manually cause GL and PL discrepancy
+		ple = frappe.db.get_all("Payment Ledger Entry", filters={"voucher_no": self.je.name})[0]
+		frappe.db.set_value("Payment Ledger Entry", ple.name, "amount", 11000)
+
+		run_ledger_health_checks()
+		expected = {
+			"voucher_type": self.je.doctype,
+			"voucher_no": self.je.name,
+			"debit_credit_mismatch": False,
+			"general_and_payment_ledger_mismatch": True,
+		}
+		actual = frappe.db.get_all(
+			"Ledger Health",
+			fields=[
+				"voucher_type",
+				"voucher_no",
+				"debit_credit_mismatch",
+				"general_and_payment_ledger_mismatch",
+			],
+		)
+		self.assertEqual(len(actual), 1)
+		self.assertEqual(expected, actual[0])
diff --git a/erpnext/accounts/doctype/ledger_health_monitor/__init__.py b/erpnext/accounts/doctype/ledger_health_monitor/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/accounts/doctype/ledger_health_monitor/__init__.py
diff --git a/erpnext/accounts/doctype/ledger_health_monitor/ledger_health_monitor.js b/erpnext/accounts/doctype/ledger_health_monitor/ledger_health_monitor.js
new file mode 100644
index 0000000..cf11276
--- /dev/null
+++ b/erpnext/accounts/doctype/ledger_health_monitor/ledger_health_monitor.js
@@ -0,0 +1,8 @@
+// Copyright (c) 2024, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+
+// frappe.ui.form.on("Ledger Health Monitor", {
+// 	refresh(frm) {
+
+// 	},
+// });
diff --git a/erpnext/accounts/doctype/ledger_health_monitor/ledger_health_monitor.json b/erpnext/accounts/doctype/ledger_health_monitor/ledger_health_monitor.json
new file mode 100644
index 0000000..6e68833
--- /dev/null
+++ b/erpnext/accounts/doctype/ledger_health_monitor/ledger_health_monitor.json
@@ -0,0 +1,104 @@
+{
+ "actions": [],
+ "allow_rename": 1,
+ "creation": "2024-03-27 09:38:07.427997",
+ "doctype": "DocType",
+ "engine": "InnoDB",
+ "field_order": [
+  "enable_health_monitor",
+  "monitor_section",
+  "monitor_for_last_x_days",
+  "debit_credit_mismatch",
+  "general_and_payment_ledger_mismatch",
+  "section_break_xdsp",
+  "companies"
+ ],
+ "fields": [
+  {
+   "default": "0",
+   "fieldname": "enable_health_monitor",
+   "fieldtype": "Check",
+   "label": "Enable Health Monitor"
+  },
+  {
+   "fieldname": "monitor_section",
+   "fieldtype": "Section Break",
+   "label": "Configuration"
+  },
+  {
+   "default": "0",
+   "fieldname": "debit_credit_mismatch",
+   "fieldtype": "Check",
+   "label": "Debit-Credit Mismatch"
+  },
+  {
+   "default": "0",
+   "fieldname": "general_and_payment_ledger_mismatch",
+   "fieldtype": "Check",
+   "label": "Discrepancy between General and Payment Ledger"
+  },
+  {
+   "default": "60",
+   "fieldname": "monitor_for_last_x_days",
+   "fieldtype": "Int",
+   "in_list_view": 1,
+   "label": "Monitor for Last 'X' days",
+   "reqd": 1
+  },
+  {
+   "fieldname": "section_break_xdsp",
+   "fieldtype": "Section Break",
+   "label": "Companies"
+  },
+  {
+   "fieldname": "companies",
+   "fieldtype": "Table",
+   "options": "Ledger Health Monitor Company"
+  }
+ ],
+ "hide_toolbar": 1,
+ "index_web_pages_for_search": 1,
+ "issingle": 1,
+ "links": [],
+ "modified": "2024-03-27 10:14:16.511681",
+ "modified_by": "Administrator",
+ "module": "Accounts",
+ "name": "Ledger Health Monitor",
+ "owner": "Administrator",
+ "permissions": [
+  {
+   "create": 1,
+   "delete": 1,
+   "email": 1,
+   "print": 1,
+   "read": 1,
+   "role": "System Manager",
+   "share": 1,
+   "write": 1
+  },
+  {
+   "create": 1,
+   "delete": 1,
+   "email": 1,
+   "print": 1,
+   "read": 1,
+   "role": "Accounts Manager",
+   "share": 1,
+   "write": 1
+  },
+  {
+   "create": 1,
+   "delete": 1,
+   "email": 1,
+   "print": 1,
+   "read": 1,
+   "role": "Accounts User",
+   "share": 1,
+   "write": 1
+  }
+ ],
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "states": [],
+ "track_changes": 1
+}
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/ledger_health_monitor/ledger_health_monitor.py b/erpnext/accounts/doctype/ledger_health_monitor/ledger_health_monitor.py
new file mode 100644
index 0000000..9f7c569
--- /dev/null
+++ b/erpnext/accounts/doctype/ledger_health_monitor/ledger_health_monitor.py
@@ -0,0 +1,28 @@
+# Copyright (c) 2024, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+# import frappe
+from frappe.model.document import Document
+
+
+class LedgerHealthMonitor(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
+
+		from erpnext.accounts.doctype.ledger_health_monitor_company.ledger_health_monitor_company import (
+			LedgerHealthMonitorCompany,
+		)
+
+		companies: DF.Table[LedgerHealthMonitorCompany]
+		debit_credit_mismatch: DF.Check
+		enable_health_monitor: DF.Check
+		general_and_payment_ledger_mismatch: DF.Check
+		monitor_for_last_x_days: DF.Int
+	# end: auto-generated types
+
+	pass
diff --git a/erpnext/accounts/doctype/ledger_health_monitor/test_ledger_health_monitor.py b/erpnext/accounts/doctype/ledger_health_monitor/test_ledger_health_monitor.py
new file mode 100644
index 0000000..e0ba443
--- /dev/null
+++ b/erpnext/accounts/doctype/ledger_health_monitor/test_ledger_health_monitor.py
@@ -0,0 +1,9 @@
+# Copyright (c) 2024, Frappe Technologies Pvt. Ltd. and Contributors
+# See license.txt
+
+# import frappe
+from frappe.tests.utils import FrappeTestCase
+
+
+class TestLedgerHealthMonitor(FrappeTestCase):
+	pass
diff --git a/erpnext/accounts/doctype/ledger_health_monitor_company/__init__.py b/erpnext/accounts/doctype/ledger_health_monitor_company/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/accounts/doctype/ledger_health_monitor_company/__init__.py
diff --git a/erpnext/accounts/doctype/ledger_health_monitor_company/ledger_health_monitor_company.json b/erpnext/accounts/doctype/ledger_health_monitor_company/ledger_health_monitor_company.json
new file mode 100644
index 0000000..87fa3e3
--- /dev/null
+++ b/erpnext/accounts/doctype/ledger_health_monitor_company/ledger_health_monitor_company.json
@@ -0,0 +1,32 @@
+{
+ "actions": [],
+ "allow_rename": 1,
+ "creation": "2024-03-27 10:04:45.727054",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+  "company"
+ ],
+ "fields": [
+  {
+   "fieldname": "company",
+   "fieldtype": "Link",
+   "in_list_view": 1,
+   "label": "Company",
+   "options": "Company"
+  }
+ ],
+ "index_web_pages_for_search": 1,
+ "istable": 1,
+ "links": [],
+ "modified": "2024-03-27 10:06:22.806155",
+ "modified_by": "Administrator",
+ "module": "Accounts",
+ "name": "Ledger Health Monitor Company",
+ "owner": "Administrator",
+ "permissions": [],
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "states": []
+}
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/ledger_health_monitor_company/ledger_health_monitor_company.py b/erpnext/accounts/doctype/ledger_health_monitor_company/ledger_health_monitor_company.py
new file mode 100644
index 0000000..5890410
--- /dev/null
+++ b/erpnext/accounts/doctype/ledger_health_monitor_company/ledger_health_monitor_company.py
@@ -0,0 +1,23 @@
+# Copyright (c) 2024, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+# import frappe
+from frappe.model.document import Document
+
+
+class LedgerHealthMonitorCompany(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
+		parent: DF.Data
+		parentfield: DF.Data
+		parenttype: DF.Data
+	# end: auto-generated types
+
+	pass
diff --git a/erpnext/accounts/doctype/purchase_taxes_and_charges/purchase_taxes_and_charges.json b/erpnext/accounts/doctype/purchase_taxes_and_charges/purchase_taxes_and_charges.json
index 87f666b..c1d6935 100644
--- a/erpnext/accounts/doctype/purchase_taxes_and_charges/purchase_taxes_and_charges.json
+++ b/erpnext/accounts/doctype/purchase_taxes_and_charges/purchase_taxes_and_charges.json
@@ -16,6 +16,7 @@
   "col_break1",
   "account_head",
   "description",
+  "is_tax_withholding_account",
   "section_break_10",
   "rate",
   "accounting_dimensions_section",
@@ -225,15 +226,23 @@
    "label": "Account Currency",
    "options": "Currency",
    "read_only": 1
+  },
+  {
+   "default": "0",
+   "fieldname": "is_tax_withholding_account",
+   "fieldtype": "Check",
+   "label": "Is Tax Withholding Account",
+   "read_only": 1
   }
  ],
  "idx": 1,
  "istable": 1,
  "links": [],
- "modified": "2024-03-27 13:10:26.775139",
+ "modified": "2024-04-08 19:51:36.678551",
  "modified_by": "Administrator",
  "module": "Accounts",
  "name": "Purchase Taxes and Charges",
+ "naming_rule": "Random",
  "owner": "Administrator",
  "permissions": [],
  "sort_field": "creation",
diff --git a/erpnext/accounts/doctype/purchase_taxes_and_charges/purchase_taxes_and_charges.py b/erpnext/accounts/doctype/purchase_taxes_and_charges/purchase_taxes_and_charges.py
index d6c0292..585d5e6 100644
--- a/erpnext/accounts/doctype/purchase_taxes_and_charges/purchase_taxes_and_charges.py
+++ b/erpnext/accounts/doctype/purchase_taxes_and_charges/purchase_taxes_and_charges.py
@@ -33,6 +33,7 @@
 		description: DF.SmallText
 		included_in_paid_amount: DF.Check
 		included_in_print_rate: DF.Check
+		is_tax_withholding_account: DF.Check
 		item_wise_tax_detail: DF.Code | None
 		parent: DF.Data
 		parentfield: DF.Data
diff --git a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py
index b043f9a..74e54dc 100644
--- a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py
+++ b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py
@@ -146,7 +146,12 @@
 		tax_row = get_tax_row_for_tcs(inv, tax_details, tax_amount, tax_deducted)
 
 	cost_center = get_cost_center(inv)
-	tax_row.update({"cost_center": cost_center})
+	tax_row.update(
+		{
+			"cost_center": cost_center,
+			"is_tax_withholding_account": 1,
+		}
+	)
 
 	if inv.doctype == "Purchase Invoice":
 		return tax_row, tax_deducted_on_advances, voucher_wise_amount
diff --git a/erpnext/accounts/report/general_ledger/general_ledger.html b/erpnext/accounts/report/general_ledger/general_ledger.html
index 2d5ca49..3c4e1a0 100644
--- a/erpnext/accounts/report/general_ledger/general_ledger.html
+++ b/erpnext/accounts/report/general_ledger/general_ledger.html
@@ -55,10 +55,10 @@
 					</span>
 				</td>
 				<td style="text-align: right">
-					{%= format_currency(data[i].debit, filters.presentation_currency) %}
+					{%= format_currency(data[i].debit, filters.presentation_currency || data[i].account_currency) %}
 				</td>
 				<td style="text-align: right">
-					{%= format_currency(data[i].credit, filters.presentation_currency) %}
+					{%= format_currency(data[i].credit, filters.presentation_currency || data[i].account_currency) %}
 				</td>
 			{% } else { %}
 				<td></td>
diff --git a/erpnext/accounts/report/general_ledger/general_ledger.py b/erpnext/accounts/report/general_ledger/general_ledger.py
index 888e040..ade17be 100644
--- a/erpnext/accounts/report/general_ledger/general_ledger.py
+++ b/erpnext/accounts/report/general_ledger/general_ledger.py
@@ -347,7 +347,7 @@
 			# acc
 			if acc_dict.entries:
 				# opening
-				data.append({})
+				data.append({"debit_in_transaction_currency": None, "credit_in_transaction_currency": None})
 				if filters.get("group_by") != "Group by Voucher":
 					data.append(acc_dict.totals.opening)
 
@@ -359,7 +359,8 @@
 				# closing
 				if filters.get("group_by") != "Group by Voucher":
 					data.append(acc_dict.totals.closing)
-		data.append({})
+
+		data.append({"debit_in_transaction_currency": None, "credit_in_transaction_currency": None})
 	else:
 		data += entries
 
@@ -380,6 +381,8 @@
 			credit=0.0,
 			debit_in_account_currency=0.0,
 			credit_in_account_currency=0.0,
+			debit_in_transaction_currency=None,
+			credit_in_transaction_currency=None,
 		)
 
 	return _dict(
@@ -424,6 +427,10 @@
 		data[key].debit_in_account_currency += gle.debit_in_account_currency
 		data[key].credit_in_account_currency += gle.credit_in_account_currency
 
+		if filters.get("add_values_in_transaction_currency") and key not in ["opening", "closing", "total"]:
+			data[key].debit_in_transaction_currency += gle.debit_in_transaction_currency
+			data[key].credit_in_transaction_currency += gle.credit_in_transaction_currency
+
 		if filters.get("show_net_values_in_party_account") and account_type_map.get(data[key].account) in (
 			"Receivable",
 			"Payable",
diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py
index 869fd42..65ba9af 100644
--- a/erpnext/accounts/utils.py
+++ b/erpnext/accounts/utils.py
@@ -13,11 +13,13 @@
 from frappe.query_builder.functions import Round, Sum
 from frappe.query_builder.utils import DocType
 from frappe.utils import (
+	add_days,
 	cint,
 	create_batch,
 	cstr,
 	flt,
 	formatdate,
+	get_datetime,
 	get_number_format_info,
 	getdate,
 	now,
@@ -1721,6 +1723,7 @@
 		)
 
 		ref_doc.set_status(update=True)
+		ref_doc.notify_update()
 
 
 def delink_original_entry(pl_entry, partial_cancel=False):
@@ -2071,3 +2074,44 @@
 
 def get_party_types_from_account_type(account_type):
 	return frappe.db.get_all("Party Type", {"account_type": account_type}, pluck="name")
+
+
+def run_ledger_health_checks():
+	health_monitor_settings = frappe.get_doc("Ledger Health Monitor")
+	if health_monitor_settings.enable_health_monitor:
+		period_end = getdate()
+		period_start = add_days(period_end, -abs(health_monitor_settings.monitor_for_last_x_days))
+
+		run_date = get_datetime()
+
+		# Debit-Credit mismatch report
+		if health_monitor_settings.debit_credit_mismatch:
+			for x in health_monitor_settings.companies:
+				filters = {"company": x.company, "from_date": period_start, "to_date": period_end}
+				voucher_wise = frappe.get_doc("Report", "Voucher-wise Balance")
+				res = voucher_wise.execute_script_report(filters=filters)
+				for x in res[1]:
+					doc = frappe.new_doc("Ledger Health")
+					doc.voucher_type = x.voucher_type
+					doc.voucher_no = x.voucher_no
+					doc.debit_credit_mismatch = True
+					doc.checked_on = run_date
+					doc.save()
+
+		# General Ledger and Payment Ledger discrepancy
+		if health_monitor_settings.general_and_payment_ledger_mismatch:
+			for x in health_monitor_settings.companies:
+				filters = {
+					"company": x.company,
+					"period_start_date": period_start,
+					"period_end_date": period_end,
+				}
+				gl_pl_comparison = frappe.get_doc("Report", "General and Payment Ledger Comparison")
+				res = gl_pl_comparison.execute_script_report(filters=filters)
+				for x in res[1]:
+					doc = frappe.new_doc("Ledger Health")
+					doc.voucher_type = x.voucher_type
+					doc.voucher_no = x.voucher_no
+					doc.general_and_payment_ledger_mismatch = True
+					doc.checked_on = run_date
+					doc.save()
diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py
index 3ecef85..66097ce 100644
--- a/erpnext/controllers/accounts_controller.py
+++ b/erpnext/controllers/accounts_controller.py
@@ -1032,10 +1032,10 @@
 				"transaction_currency": self.get("currency") or self.company_currency,
 				"transaction_exchange_rate": self.get("conversion_rate", 1),
 				"debit_in_transaction_currency": self.get_value_in_transaction_currency(
-					account_currency, args, "debit"
+					account_currency, gl_dict, "debit"
 				),
 				"credit_in_transaction_currency": self.get_value_in_transaction_currency(
-					account_currency, args, "credit"
+					account_currency, gl_dict, "credit"
 				),
 			}
 		)
@@ -1067,11 +1067,11 @@
 			return "Debit Note"
 		return self.doctype
 
-	def get_value_in_transaction_currency(self, account_currency, args, field):
+	def get_value_in_transaction_currency(self, account_currency, gl_dict, field):
 		if account_currency == self.get("currency"):
-			return args.get(field + "_in_account_currency")
+			return gl_dict.get(field + "_in_account_currency")
 		else:
-			return flt(args.get(field, 0) / self.get("conversion_rate", 1))
+			return flt(gl_dict.get(field, 0) / self.get("conversion_rate", 1))
 
 	def validate_zero_qty_for_return_invoices_with_stock(self):
 		rows = []
diff --git a/erpnext/controllers/taxes_and_totals.py b/erpnext/controllers/taxes_and_totals.py
index d90c14a..21b9186 100644
--- a/erpnext/controllers/taxes_and_totals.py
+++ b/erpnext/controllers/taxes_and_totals.py
@@ -467,7 +467,16 @@
 		if tax.charge_type == "Actual":
 			# distribute the tax amount proportionally to each item row
 			actual = flt(tax.tax_amount, tax.precision("tax_amount"))
-			current_tax_amount = item.net_amount * actual / self.doc.net_total if self.doc.net_total else 0.0
+
+			if tax.get("is_tax_withholding_account") and item.meta.get_field("apply_tds"):
+				if not item.get("apply_tds") or not self.doc.tax_withholding_net_total:
+					current_tax_amount = 0.0
+				else:
+					current_tax_amount = item.net_amount * actual / self.doc.tax_withholding_net_total
+			else:
+				current_tax_amount = (
+					item.net_amount * actual / self.doc.net_total if self.doc.net_total else 0.0
+				)
 
 		elif tax.charge_type == "On Net Total":
 			current_tax_amount = (tax_rate / 100.0) * item.net_amount
diff --git a/erpnext/hooks.py b/erpnext/hooks.py
index 9c1521a..a31f011 100644
--- a/erpnext/hooks.py
+++ b/erpnext/hooks.py
@@ -434,6 +434,7 @@
 		"erpnext.buying.doctype.supplier_quotation.supplier_quotation.set_expired_status",
 		"erpnext.accounts.doctype.process_statement_of_accounts.process_statement_of_accounts.send_auto_email",
 		"erpnext.accounts.utils.auto_create_exchange_rate_revaluation_daily",
+		"erpnext.accounts.utils.run_ledger_health_checks",
 	],
 	"weekly": [
 		"erpnext.accounts.utils.auto_create_exchange_rate_revaluation_weekly",
diff --git a/erpnext/public/js/utils.js b/erpnext/public/js/utils.js
index 4127218..8478e20 100755
--- a/erpnext/public/js/utils.js
+++ b/erpnext/public/js/utils.js
@@ -452,6 +452,9 @@
 	},
 
 	get_fiscal_year: function (date, with_dates = false, boolean = false) {
+		if (!frappe.boot.setup_complete) {
+			return;
+		}
 		if (!date) {
 			date = frappe.datetime.get_today();
 		}
diff --git a/erpnext/stock/doctype/pick_list/pick_list.py b/erpnext/stock/doctype/pick_list/pick_list.py
index 9aaa08e..e3dbdb5 100644
--- a/erpnext/stock/doctype/pick_list/pick_list.py
+++ b/erpnext/stock/doctype/pick_list/pick_list.py
@@ -12,7 +12,7 @@
 from frappe.query_builder import Case
 from frappe.query_builder.custom import GROUP_CONCAT
 from frappe.query_builder.functions import Coalesce, Locate, Replace, Sum
-from frappe.utils import ceil, cint, floor, flt
+from frappe.utils import ceil, cint, floor, flt, get_link_to_form
 from frappe.utils.nestedset import get_descendants_of
 
 from erpnext.selling.doctype.sales_order.sales_order import (
@@ -23,7 +23,11 @@
 	get_picked_serial_nos,
 )
 from erpnext.stock.get_item_details import get_conversion_factor
-from erpnext.stock.serial_batch_bundle import SerialBatchCreation
+from erpnext.stock.serial_batch_bundle import (
+	SerialBatchCreation,
+	get_batches_from_bundle,
+	get_serial_nos_from_bundle,
+)
 
 # TODO: Prioritize SO or WO group warehouse
 
@@ -201,10 +205,11 @@
 				row.db_set("serial_and_batch_bundle", None)
 
 	def on_update(self):
-		self.linked_serial_and_batch_bundle()
+		if self.get("locations"):
+			self.linked_serial_and_batch_bundle()
 
 	def linked_serial_and_batch_bundle(self):
-		for row in self.locations:
+		for row in self.get("locations"):
 			if row.serial_and_batch_bundle:
 				frappe.get_doc(
 					"Serial and Batch Bundle", row.serial_and_batch_bundle
@@ -513,55 +518,82 @@
 	def get_picked_items_details(self, items):
 		picked_items = frappe._dict()
 
-		if items:
-			pi = frappe.qb.DocType("Pick List")
-			pi_item = frappe.qb.DocType("Pick List Item")
-			query = (
-				frappe.qb.from_(pi)
-				.inner_join(pi_item)
-				.on(pi.name == pi_item.parent)
-				.select(
-					pi_item.item_code,
-					pi_item.warehouse,
-					pi_item.batch_no,
-					pi_item.serial_and_batch_bundle,
-					Sum(Case().when(pi_item.picked_qty > 0, pi_item.picked_qty).else_(pi_item.stock_qty)).as_(
-						"picked_qty"
-					),
-					Replace(GROUP_CONCAT(pi_item.serial_no), ",", "\n").as_("serial_no"),
-				)
-				.where(
-					(pi_item.item_code.isin([x.item_code for x in items]))
-					& ((pi_item.picked_qty > 0) | (pi_item.stock_qty > 0))
-					& (pi.status != "Completed")
-					& (pi.status != "Cancelled")
-					& (pi_item.docstatus != 2)
-				)
-				.groupby(
-					pi_item.item_code,
-					pi_item.warehouse,
-					pi_item.batch_no,
-				)
-			)
+		if not items:
+			return picked_items
 
-			if self.name:
-				query = query.where(pi_item.parent != self.name)
+		items_data = self._get_pick_list_items(items)
 
-			items_data = query.run(as_dict=True)
+		for item_data in items_data:
+			key = (item_data.warehouse, item_data.batch_no) if item_data.batch_no else item_data.warehouse
+			serial_no = [x for x in item_data.serial_no.split("\n") if x] if item_data.serial_no else None
 
-			for item_data in items_data:
-				key = (item_data.warehouse, item_data.batch_no) if item_data.batch_no else item_data.warehouse
-				serial_no = [x for x in item_data.serial_no.split("\n") if x] if item_data.serial_no else None
-				data = {"picked_qty": item_data.picked_qty}
-				if serial_no:
-					data["serial_no"] = serial_no
-				if item_data.item_code not in picked_items:
-					picked_items[item_data.item_code] = {key: data}
-				else:
-					picked_items[item_data.item_code][key] = data
+			if item_data.serial_and_batch_bundle:
+				if not serial_no:
+					serial_no = get_serial_nos_from_bundle(item_data.serial_and_batch_bundle)
+
+				if not item_data.batch_no and not serial_no:
+					bundle_batches = get_batches_from_bundle(item_data.serial_and_batch_bundle)
+					for batch_no, batch_qty in bundle_batches.items():
+						batch_qty = abs(batch_qty)
+
+						key = (item_data.warehouse, batch_no)
+						if item_data.item_code not in picked_items:
+							picked_items[item_data.item_code] = {key: {"picked_qty": batch_qty}}
+						else:
+							picked_items[item_data.item_code][key]["picked_qty"] += batch_qty
+
+					continue
+
+			if item_data.item_code not in picked_items:
+				picked_items[item_data.item_code] = {}
+
+			if key not in picked_items[item_data.item_code]:
+				picked_items[item_data.item_code][key] = frappe._dict(
+					{
+						"picked_qty": 0,
+						"serial_no": [],
+						"batch_no": item_data.batch_no or "",
+						"warehouse": item_data.warehouse,
+					}
+				)
+
+			picked_items[item_data.item_code][key]["picked_qty"] += item_data.picked_qty
+			if serial_no:
+				picked_items[item_data.item_code][key]["serial_no"].extend(serial_no)
 
 		return picked_items
 
+	def _get_pick_list_items(self, items):
+		pi = frappe.qb.DocType("Pick List")
+		pi_item = frappe.qb.DocType("Pick List Item")
+		query = (
+			frappe.qb.from_(pi)
+			.inner_join(pi_item)
+			.on(pi.name == pi_item.parent)
+			.select(
+				pi_item.item_code,
+				pi_item.warehouse,
+				pi_item.batch_no,
+				pi_item.serial_and_batch_bundle,
+				pi_item.serial_no,
+				(Case().when(pi_item.picked_qty > 0, pi_item.picked_qty).else_(pi_item.stock_qty)).as_(
+					"picked_qty"
+				),
+			)
+			.where(
+				(pi_item.item_code.isin([x.item_code for x in items]))
+				& ((pi_item.picked_qty > 0) | (pi_item.stock_qty > 0))
+				& (pi.status != "Completed")
+				& (pi.status != "Cancelled")
+				& (pi_item.docstatus != 2)
+			)
+		)
+
+		if self.name:
+			query = query.where(pi_item.parent != self.name)
+
+		return query.run(as_dict=True)
+
 	def _get_product_bundles(self) -> dict[str, str]:
 		# Dict[so_item_row: item_code]
 		product_bundles = {}
@@ -718,9 +750,7 @@
 	consider_rejected_warehouses=False,
 ):
 	locations = []
-	total_picked_qty = (
-		sum([v.get("picked_qty") for k, v in picked_item_details.items()]) if picked_item_details else 0
-	)
+
 	has_serial_no = frappe.get_cached_value("Item", item_code, "has_serial_no")
 	has_batch_no = frappe.get_cached_value("Item", item_code, "has_batch_no")
 
@@ -730,63 +760,90 @@
 			from_warehouses,
 			required_qty,
 			company,
-			total_picked_qty,
 			consider_rejected_warehouses=consider_rejected_warehouses,
 		)
 	elif has_serial_no:
 		locations = get_available_item_locations_for_serialized_item(
 			item_code,
 			from_warehouses,
-			required_qty,
 			company,
-			total_picked_qty,
 			consider_rejected_warehouses=consider_rejected_warehouses,
 		)
 	elif has_batch_no:
 		locations = get_available_item_locations_for_batched_item(
 			item_code,
 			from_warehouses,
-			required_qty,
-			company,
-			total_picked_qty,
 			consider_rejected_warehouses=consider_rejected_warehouses,
 		)
 	else:
 		locations = get_available_item_locations_for_other_item(
 			item_code,
 			from_warehouses,
-			required_qty,
 			company,
-			total_picked_qty,
 			consider_rejected_warehouses=consider_rejected_warehouses,
 		)
 
+	if picked_item_details:
+		locations = filter_locations_by_picked_materials(locations, picked_item_details)
+
+	if locations:
+		locations = get_locations_based_on_required_qty(locations, required_qty)
+
+	if not ignore_validation:
+		validate_picked_materials(item_code, required_qty, locations)
+
+	return locations
+
+
+def get_locations_based_on_required_qty(locations, required_qty):
+	filtered_locations = []
+
+	for location in locations:
+		if location.qty >= required_qty:
+			location.qty = required_qty
+			filtered_locations.append(location)
+			break
+
+		required_qty -= location.qty
+		filtered_locations.append(location)
+
+	return filtered_locations
+
+
+def validate_picked_materials(item_code, required_qty, locations):
+	for location in list(locations):
+		if location["qty"] < 0:
+			locations.remove(location)
+
 	total_qty_available = sum(location.get("qty") for location in locations)
 	remaining_qty = required_qty - total_qty_available
 
-	if remaining_qty > 0 and not ignore_validation:
+	if remaining_qty > 0:
 		frappe.msgprint(
-			_("{0} units of Item {1} is not available.").format(
-				remaining_qty, frappe.get_desk_link("Item", item_code)
+			_("{0} units of Item {1} is picked in another Pick List.").format(
+				remaining_qty, get_link_to_form("Item", item_code)
 			),
-			title=_("Insufficient Stock"),
+			title=_("Already Picked"),
 		)
 
-	if picked_item_details:
-		for location in list(locations):
-			if location["qty"] < 0:
-				locations.remove(location)
 
-		total_qty_available = sum(location.get("qty") for location in locations)
-		remaining_qty = required_qty - total_qty_available
+def filter_locations_by_picked_materials(locations, picked_item_details) -> list[dict]:
+	for row in locations:
+		key = row.warehouse
+		if row.batch_no:
+			key = (row.warehouse, row.batch_no)
 
-		if remaining_qty > 0 and not ignore_validation:
-			frappe.msgprint(
-				_("{0} units of Item {1} is picked in another Pick List.").format(
-					remaining_qty, frappe.get_desk_link("Item", item_code)
-				),
-				title=_("Already Picked"),
-			)
+		picked_qty = picked_item_details.get(key, {}).get("picked_qty", 0)
+		if not picked_qty:
+			continue
+		if picked_qty > row.qty:
+			row.qty = 0
+			picked_item_details[key]["picked_qty"] -= row.qty
+		else:
+			row.qty -= picked_qty
+			picked_item_details[key]["picked_qty"] = 0.0
+			if row.serial_nos:
+				row.serial_nos = list(set(row.serial_nos) - set(picked_item_details[key].get("serial_no")))
 
 	return locations
 
@@ -796,15 +853,12 @@
 	from_warehouses,
 	required_qty,
 	company,
-	total_picked_qty=0,
 	consider_rejected_warehouses=False,
 ):
 	# Get batch nos by FIFO
 	locations = get_available_item_locations_for_batched_item(
 		item_code,
 		from_warehouses,
-		required_qty,
-		company,
 		consider_rejected_warehouses=consider_rejected_warehouses,
 	)
 
@@ -824,7 +878,6 @@
 					(conditions) & (sn.batch_no == location.batch_no) & (sn.warehouse == location.warehouse)
 				)
 				.orderby(sn.creation)
-				.limit(ceil(location.qty + total_picked_qty))
 			).run(as_dict=True)
 
 			serial_nos = [sn.name for sn in serial_nos]
@@ -837,18 +890,14 @@
 def get_available_item_locations_for_serialized_item(
 	item_code,
 	from_warehouses,
-	required_qty,
 	company,
-	total_picked_qty=0,
 	consider_rejected_warehouses=False,
 ):
-	picked_serial_nos = get_picked_serial_nos(item_code, from_warehouses)
-
 	sn = frappe.qb.DocType("Serial No")
 	query = (
 		frappe.qb.from_(sn)
 		.select(sn.name, sn.warehouse)
-		.where((sn.item_code == item_code) & (sn.company == company))
+		.where(sn.item_code == item_code)
 		.orderby(sn.creation)
 	)
 
@@ -856,6 +905,7 @@
 		query = query.where(sn.warehouse.isin(from_warehouses))
 	else:
 		query = query.where(Coalesce(sn.warehouse, "") != "")
+		query = query.where(sn.company == company)
 
 	if not consider_rejected_warehouses:
 		if rejected_warehouses := get_rejected_warehouses():
@@ -864,16 +914,8 @@
 	serial_nos = query.run(as_list=True)
 
 	warehouse_serial_nos_map = frappe._dict()
-	picked_qty = required_qty
 	for serial_no, warehouse in serial_nos:
-		if serial_no in picked_serial_nos:
-			continue
-
-		if picked_qty <= 0:
-			break
-
 		warehouse_serial_nos_map.setdefault(warehouse, []).append(serial_no)
-		picked_qty -= 1
 
 	locations = []
 
@@ -881,12 +923,14 @@
 		qty = len(serial_nos)
 
 		locations.append(
-			{
-				"qty": qty,
-				"warehouse": warehouse,
-				"item_code": item_code,
-				"serial_nos": serial_nos,
-			}
+			frappe._dict(
+				{
+					"qty": qty,
+					"warehouse": warehouse,
+					"item_code": item_code,
+					"serial_nos": serial_nos,
+				}
+			)
 		)
 
 	return locations
@@ -895,9 +939,6 @@
 def get_available_item_locations_for_batched_item(
 	item_code,
 	from_warehouses,
-	required_qty,
-	company,
-	total_picked_qty=0,
 	consider_rejected_warehouses=False,
 ):
 	locations = []
@@ -906,8 +947,6 @@
 			{
 				"item_code": item_code,
 				"warehouse": from_warehouses,
-				"qty": required_qty,
-				"is_pick_list": True,
 			}
 		)
 	)
@@ -943,9 +982,7 @@
 def get_available_item_locations_for_other_item(
 	item_code,
 	from_warehouses,
-	required_qty,
 	company,
-	total_picked_qty=0,
 	consider_rejected_warehouses=False,
 ):
 	bin = frappe.qb.DocType("Bin")
@@ -954,7 +991,6 @@
 		.select(bin.warehouse, bin.actual_qty.as_("qty"))
 		.where((bin.item_code == item_code) & (bin.actual_qty > 0))
 		.orderby(bin.creation)
-		.limit(cint(required_qty + total_picked_qty))
 	)
 
 	if from_warehouses:
diff --git a/erpnext/stock/doctype/pick_list/test_pick_list.py b/erpnext/stock/doctype/pick_list/test_pick_list.py
index 0fe869b..87a7150 100644
--- a/erpnext/stock/doctype/pick_list/test_pick_list.py
+++ b/erpnext/stock/doctype/pick_list/test_pick_list.py
@@ -815,7 +815,7 @@
 
 	def test_pick_list_status(self):
 		warehouse = "_Test Warehouse - _TC"
-		item = make_item(properties={"maintain_stock": 1}).name
+		item = make_item(properties={"is_stock_item": 1}).name
 		make_stock_entry(item=item, to_warehouse=warehouse, qty=10)
 
 		so = make_sales_order(item_code=item, qty=10, rate=100)
@@ -845,3 +845,135 @@
 		pl.cancel()
 		pl.reload()
 		self.assertEqual(pl.status, "Cancelled")
+
+	def test_pick_list_validation(self):
+		warehouse = "_Test Warehouse - _TC"
+		item = make_item("Test Non Serialized Pick List Item", properties={"is_stock_item": 1}).name
+
+		make_stock_entry(item=item, to_warehouse=warehouse, qty=10)
+
+		so = make_sales_order(item_code=item, qty=5, rate=100)
+
+		pl = create_pick_list(so.name)
+		pl.save()
+		pl.submit()
+		self.assertEqual(pl.locations[0].qty, 5.0)
+		self.assertTrue(hasattr(pl, "locations"))
+
+		so = make_sales_order(item_code=item, qty=5, rate=100)
+
+		pl = create_pick_list(so.name)
+		pl.save()
+		self.assertEqual(pl.locations[0].qty, 5.0)
+		self.assertTrue(hasattr(pl, "locations"))
+
+		so = make_sales_order(item_code=item, qty=4, rate=100)
+		pl = create_pick_list(so.name)
+		self.assertFalse(hasattr(pl, "locations"))
+
+	def test_pick_list_validation_for_serial_no(self):
+		warehouse = "_Test Warehouse - _TC"
+		item = make_item(
+			"Test Serialized Pick List Item",
+			properties={"is_stock_item": 1, "has_serial_no": 1, "serial_no_series": "SN-SPLI-.####"},
+		).name
+
+		make_stock_entry(item=item, to_warehouse=warehouse, qty=10)
+
+		so = make_sales_order(item_code=item, qty=5, rate=100)
+
+		pl = create_pick_list(so.name)
+		pl.locations[0].qty = 5
+		pl.save()
+		pl.submit()
+		self.assertTrue(pl.locations[0].serial_no)
+		self.assertEqual(pl.locations[0].qty, 5.0)
+		self.assertTrue(hasattr(pl, "locations"))
+
+		so = make_sales_order(item_code=item, qty=5, rate=100)
+
+		pl = create_pick_list(so.name)
+		pl.save()
+		self.assertTrue(pl.locations[0].serial_no)
+		self.assertEqual(pl.locations[0].qty, 5.0)
+		self.assertTrue(hasattr(pl, "locations"))
+
+		so = make_sales_order(item_code=item, qty=4, rate=100)
+		pl = create_pick_list(so.name)
+		self.assertFalse(hasattr(pl, "locations"))
+
+	def test_pick_list_validation_for_batch_no(self):
+		warehouse = "_Test Warehouse - _TC"
+		item = make_item(
+			"Test Batch Pick List Item",
+			properties={
+				"is_stock_item": 1,
+				"has_batch_no": 1,
+				"batch_number_series": "BATCH-SPLI-.####",
+				"create_new_batch": 1,
+			},
+		).name
+
+		make_stock_entry(item=item, to_warehouse=warehouse, qty=10)
+
+		so = make_sales_order(item_code=item, qty=5, rate=100)
+
+		pl = create_pick_list(so.name)
+		pl.locations[0].qty = 5
+		pl.save()
+		pl.submit()
+		self.assertTrue(pl.locations[0].batch_no)
+		self.assertEqual(pl.locations[0].qty, 5.0)
+		self.assertTrue(hasattr(pl, "locations"))
+
+		so = make_sales_order(item_code=item, qty=5, rate=100)
+
+		pl = create_pick_list(so.name)
+		pl.save()
+		self.assertTrue(pl.locations[0].batch_no)
+		self.assertEqual(pl.locations[0].qty, 5.0)
+		self.assertTrue(hasattr(pl, "locations"))
+
+		so = make_sales_order(item_code=item, qty=4, rate=100)
+		pl = create_pick_list(so.name)
+		self.assertFalse(hasattr(pl, "locations"))
+
+	def test_pick_list_validation_for_batch_no_and_serial_item(self):
+		warehouse = "_Test Warehouse - _TC"
+		item = make_item(
+			"Test Serialized Batch Pick List Item",
+			properties={
+				"is_stock_item": 1,
+				"has_batch_no": 1,
+				"batch_number_series": "SN-BT-BATCH-SPLI-.####",
+				"create_new_batch": 1,
+				"has_serial_no": 1,
+				"serial_no_series": "SN-BT-SPLI-.####",
+			},
+		).name
+
+		make_stock_entry(item=item, to_warehouse=warehouse, qty=10)
+
+		so = make_sales_order(item_code=item, qty=5, rate=100)
+
+		pl = create_pick_list(so.name)
+		pl.locations[0].qty = 5
+		pl.save()
+		pl.submit()
+		self.assertTrue(pl.locations[0].batch_no)
+		self.assertTrue(pl.locations[0].serial_no)
+		self.assertEqual(pl.locations[0].qty, 5.0)
+		self.assertTrue(hasattr(pl, "locations"))
+
+		so = make_sales_order(item_code=item, qty=5, rate=100)
+
+		pl = create_pick_list(so.name)
+		pl.save()
+		self.assertTrue(pl.locations[0].batch_no)
+		self.assertTrue(pl.locations[0].serial_no)
+		self.assertEqual(pl.locations[0].qty, 5.0)
+		self.assertTrue(hasattr(pl, "locations"))
+
+		so = make_sales_order(item_code=item, qty=4, rate=100)
+		pl = create_pick_list(so.name)
+		self.assertFalse(hasattr(pl, "locations"))
diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json
index 339d508..0c460b4 100755
--- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json
+++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json
@@ -58,6 +58,8 @@
   "column_break_27",
   "total",
   "net_total",
+  "tax_withholding_net_total",
+  "base_tax_withholding_net_total",
   "taxes_charges_section",
   "tax_category",
   "taxes_and_charges",
@@ -1246,13 +1248,31 @@
    "label": "Subcontracting Receipt",
    "options": "Subcontracting Receipt",
    "search_index": 1
+  },
+  {
+   "fieldname": "tax_withholding_net_total",
+   "fieldtype": "Currency",
+   "hidden": 1,
+   "label": "Tax Withholding Net Total",
+   "no_copy": 1,
+   "options": "currency",
+   "read_only": 1
+  },
+  {
+   "fieldname": "base_tax_withholding_net_total",
+   "fieldtype": "Currency",
+   "hidden": 1,
+   "label": "Base Tax Withholding Net Total",
+   "no_copy": 1,
+   "print_hide": 1,
+   "read_only": 1
   }
  ],
  "icon": "fa fa-truck",
  "idx": 261,
  "is_submittable": 1,
  "links": [],
- "modified": "2024-03-27 13:10:25.441066",
+ "modified": "2024-04-08 20:23:03.699201",
  "modified_by": "Administrator",
  "module": "Stock",
  "name": "Purchase Receipt",
diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py
index ae101df..5ae0841 100644
--- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py
+++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py
@@ -52,6 +52,7 @@
 		base_net_total: DF.Currency
 		base_rounded_total: DF.Currency
 		base_rounding_adjustment: DF.Currency
+		base_tax_withholding_net_total: DF.Currency
 		base_taxes_and_charges_added: DF.Currency
 		base_taxes_and_charges_deducted: DF.Currency
 		base_total: DF.Currency
@@ -121,6 +122,7 @@
 		supplier_name: DF.Data | None
 		supplier_warehouse: DF.Link | None
 		tax_category: DF.Link | None
+		tax_withholding_net_total: DF.Currency
 		taxes: DF.Table[PurchaseTaxesandCharges]
 		taxes_and_charges: DF.Link | None
 		taxes_and_charges_added: DF.Currency
diff --git a/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json b/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json
index 7cd3799..6ba1469 100644
--- a/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json
+++ b/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json
@@ -58,6 +58,7 @@
   "pricing_rules",
   "stock_uom_rate",
   "is_free_item",
+  "apply_tds",
   "section_break_29",
   "net_rate",
   "net_amount",
@@ -1107,12 +1108,20 @@
    "fieldname": "use_serial_batch_fields",
    "fieldtype": "Check",
    "label": "Use Serial No / Batch Fields"
+  },
+  {
+   "default": "1",
+   "fieldname": "apply_tds",
+   "fieldtype": "Check",
+   "hidden": 1,
+   "label": "Apply TDS",
+   "read_only": 1
   }
  ],
  "idx": 1,
  "istable": 1,
  "links": [],
- "modified": "2024-03-27 13:10:25.896543",
+ "modified": "2024-04-08 20:00:16.277292",
  "modified_by": "Administrator",
  "module": "Stock",
  "name": "Purchase Receipt Item",
diff --git a/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.py b/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.py
index 3c6dcdc..908c0a7 100644
--- a/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.py
+++ b/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.py
@@ -16,6 +16,7 @@
 
 		allow_zero_valuation_rate: DF.Check
 		amount: DF.Currency
+		apply_tds: DF.Check
 		asset_category: DF.Link | None
 		asset_location: DF.Link | None
 		barcode: DF.Data | None