Merge pull request #40827 from Nihantra-Patel/fix_po_quot_cre

fix: purchase order and quotation creation
diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py
index 90ff5b1..1ff1cb7 100644
--- a/erpnext/accounts/doctype/payment_entry/payment_entry.py
+++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py
@@ -188,6 +188,7 @@
 		self.update_outstanding_amounts()
 		self.update_advance_paid()
 		self.update_payment_schedule()
+		self.set_payment_req_status()
 		self.set_status()
 
 	def set_liability_account(self):
@@ -484,7 +485,7 @@
 		self,
 		force: bool = False,
 		update_ref_details_only_for: list | None = None,
-		ref_exchange_rate: float | None = None,
+		reference_exchange_details: dict | None = None,
 	) -> None:
 		for d in self.get("references"):
 			if d.allocated_amount:
@@ -499,8 +500,12 @@
 				)
 
 				# Only update exchange rate when the reference is Journal Entry
-				if ref_exchange_rate and d.reference_doctype == "Journal Entry":
-					ref_details.update({"exchange_rate": ref_exchange_rate})
+				if (
+					reference_exchange_details
+					and d.reference_doctype == reference_exchange_details.reference_doctype
+					and d.reference_name == reference_exchange_details.reference_name
+				):
+					ref_details.update({"exchange_rate": reference_exchange_details.exchange_rate})
 
 				for field, value in ref_details.items():
 					if d.exchange_gain_loss:
diff --git a/erpnext/accounts/doctype/payment_gateway_account/test_payment_gateway_account.py b/erpnext/accounts/doctype/payment_gateway_account/test_payment_gateway_account.py
index 7a8cdf7..6c8f640 100644
--- a/erpnext/accounts/doctype/payment_gateway_account/test_payment_gateway_account.py
+++ b/erpnext/accounts/doctype/payment_gateway_account/test_payment_gateway_account.py
@@ -5,6 +5,8 @@
 
 # test_records = frappe.get_test_records('Payment Gateway Account')
 
+test_ignore = ["Payment Gateway"]
+
 
 class TestPaymentGatewayAccount(unittest.TestCase):
 	pass
diff --git a/erpnext/accounts/doctype/payment_request/payment_request.py b/erpnext/accounts/doctype/payment_request/payment_request.py
index 196838a..5272294 100644
--- a/erpnext/accounts/doctype/payment_request/payment_request.py
+++ b/erpnext/accounts/doctype/payment_request/payment_request.py
@@ -149,35 +149,37 @@
 					).format(self.grand_total, amount)
 				)
 
-	def on_submit(self):
-		if self.payment_request_type == "Outward":
-			self.db_set("status", "Initiated")
-			return
-		elif self.payment_request_type == "Inward":
-			self.db_set("status", "Requested")
-
-		send_mail = self.payment_gateway_validation() if self.payment_gateway else None
+	def on_change(self):
 		ref_doc = frappe.get_doc(self.reference_doctype, self.reference_name)
-
-		if (
-			hasattr(ref_doc, "order_type") and ref_doc.order_type == "Shopping Cart"
-		) or self.flags.mute_email:
-			send_mail = False
-
-		if send_mail and self.payment_channel != "Phone":
-			self.set_payment_request_url()
-			self.send_email()
-			self.make_communication_entry()
-
-		elif self.payment_channel == "Phone":
-			self.request_phone_payment()
-
 		advance_payment_doctypes = frappe.get_hooks("advance_payment_receivable_doctypes") + frappe.get_hooks(
 			"advance_payment_payable_doctypes"
 		)
 		if self.reference_doctype in advance_payment_doctypes:
 			# set advance payment status
-			ref_doc.set_total_advance_paid()
+			ref_doc.set_advance_payment_status()
+
+	def on_submit(self):
+		if self.payment_request_type == "Outward":
+			self.db_set("status", "Initiated")
+		elif self.payment_request_type == "Inward":
+			self.db_set("status", "Requested")
+
+		if self.payment_request_type == "Inward":
+			send_mail = self.payment_gateway_validation() if self.payment_gateway else None
+			ref_doc = frappe.get_doc(self.reference_doctype, self.reference_name)
+
+			if (
+				hasattr(ref_doc, "order_type") and ref_doc.order_type == "Shopping Cart"
+			) or self.flags.mute_email:
+				send_mail = False
+
+			if send_mail and self.payment_channel != "Phone":
+				self.set_payment_request_url()
+				self.send_email()
+				self.make_communication_entry()
+
+			elif self.payment_channel == "Phone":
+				self.request_phone_payment()
 
 	def request_phone_payment(self):
 		controller = _get_payment_gateway_controller(self.payment_gateway)
@@ -217,14 +219,6 @@
 		self.check_if_payment_entry_exists()
 		self.set_as_cancelled()
 
-		ref_doc = frappe.get_doc(self.reference_doctype, self.reference_name)
-		advance_payment_doctypes = frappe.get_hooks("advance_payment_receivable_doctypes") + frappe.get_hooks(
-			"advance_payment_payable_doctypes"
-		)
-		if self.reference_doctype in advance_payment_doctypes:
-			# set advance payment status
-			ref_doc.set_total_advance_paid()
-
 	def make_invoice(self):
 		ref_doc = frappe.get_doc(self.reference_doctype, self.reference_name)
 		if hasattr(ref_doc, "order_type") and ref_doc.order_type == "Shopping Cart":
diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py
index b61f195..869fd42 100644
--- a/erpnext/accounts/utils.py
+++ b/erpnext/accounts/utils.py
@@ -720,7 +720,19 @@
 	payment_entry.setup_party_account_field()
 	payment_entry.set_missing_values()
 	if not skip_ref_details_update_for_pe:
-		payment_entry.set_missing_ref_details(ref_exchange_rate=d.exchange_rate or None)
+		reference_exchange_details = frappe._dict()
+		if d.against_voucher_type == "Journal Entry" and d.exchange_rate:
+			reference_exchange_details.update(
+				{
+					"reference_doctype": d.against_voucher_type,
+					"reference_name": d.against_voucher,
+					"exchange_rate": d.exchange_rate,
+				}
+			)
+		payment_entry.set_missing_ref_details(
+			update_ref_details_only_for=[(d.against_voucher_type, d.against_voucher)],
+			reference_exchange_details=reference_exchange_details,
+		)
 	payment_entry.set_amounts()
 
 	payment_entry.make_exchange_gain_loss_journal(
diff --git a/erpnext/assets/doctype/asset/test_asset.py b/erpnext/assets/doctype/asset/test_asset.py
index 088641a..1b3951e 100644
--- a/erpnext/assets/doctype/asset/test_asset.py
+++ b/erpnext/assets/doctype/asset/test_asset.py
@@ -232,7 +232,11 @@
 			asset.precision("gross_purchase_amount"),
 		)
 		pro_rata_amount, _, _ = _get_pro_rata_amt(
-			asset.finance_books[0], 9000, get_last_day(add_months(purchase_date, 1)), date
+			asset.finance_books[0],
+			9000,
+			get_last_day(add_months(purchase_date, 1)),
+			date,
+			original_schedule_date=get_last_day(nowdate()),
 		)
 		pro_rata_amount = flt(pro_rata_amount, asset.precision("gross_purchase_amount"))
 		self.assertEqual(
@@ -314,7 +318,11 @@
 		self.assertEqual(first_asset_depr_schedule.status, "Cancelled")
 
 		pro_rata_amount, _, _ = _get_pro_rata_amt(
-			asset.finance_books[0], 9000, get_last_day(add_months(purchase_date, 1)), date
+			asset.finance_books[0],
+			9000,
+			get_last_day(add_months(purchase_date, 1)),
+			date,
+			original_schedule_date=get_last_day(nowdate()),
 		)
 		pro_rata_amount = flt(pro_rata_amount, asset.precision("gross_purchase_amount"))
 
@@ -332,7 +340,6 @@
 			),
 			("Debtors - _TC", 25000.0, 0.0),
 		)
-
 		gle = get_gl_entries("Sales Invoice", si.name)
 		self.assertSequenceEqual(gle, expected_gle)
 
@@ -378,7 +385,7 @@
 
 		self.assertEqual(frappe.db.get_value("Asset", asset.name, "status"), "Sold")
 
-		expected_values = [["2023-03-31", 12000, 36000], ["2023-05-23", 1742.47, 37742.47]]
+		expected_values = [["2023-03-31", 12000, 36000], ["2023-05-23", 1737.7, 37737.7]]
 
 		second_asset_depr_schedule = get_depr_schedule(asset.name, "Active")
 
@@ -391,7 +398,7 @@
 		expected_gle = (
 			(
 				"_Test Accumulated Depreciations - _TC",
-				37742.47,
+				37737.7,
 				0.0,
 			),
 			(
@@ -402,7 +409,7 @@
 			(
 				"_Test Gain/Loss on Asset Disposal - _TC",
 				0.0,
-				17742.47,
+				17737.7,
 			),
 			("Debtors - _TC", 40000.0, 0.0),
 		)
@@ -707,25 +714,24 @@
 		)
 
 		expected_schedules = [
-			["2023-01-31", 1021.98, 1021.98],
-			["2023-02-28", 923.08, 1945.06],
-			["2023-03-31", 1021.98, 2967.04],
-			["2023-04-30", 989.01, 3956.05],
-			["2023-05-31", 1021.98, 4978.03],
-			["2023-06-30", 989.01, 5967.04],
-			["2023-07-31", 1021.98, 6989.02],
-			["2023-08-31", 1021.98, 8011.0],
-			["2023-09-30", 989.01, 9000.01],
-			["2023-10-31", 1021.98, 10021.99],
-			["2023-11-30", 989.01, 11011.0],
-			["2023-12-31", 989.0, 12000.0],
+			["2023-01-31", 1019.18, 1019.18],
+			["2023-02-28", 920.55, 1939.73],
+			["2023-03-31", 1019.18, 2958.91],
+			["2023-04-30", 986.3, 3945.21],
+			["2023-05-31", 1019.18, 4964.39],
+			["2023-06-30", 986.3, 5950.69],
+			["2023-07-31", 1019.18, 6969.87],
+			["2023-08-31", 1019.18, 7989.05],
+			["2023-09-30", 986.3, 8975.35],
+			["2023-10-31", 1019.18, 9994.53],
+			["2023-11-30", 986.3, 10980.83],
+			["2023-12-31", 1019.17, 12000.0],
 		]
 
 		schedules = [
 			[cstr(d.schedule_date), d.depreciation_amount, d.accumulated_depreciation_amount]
 			for d in get_depr_schedule(asset.name, "Draft")
 		]
-
 		self.assertEqual(schedules, expected_schedules)
 
 	def test_schedule_for_straight_line_method_for_existing_asset(self):
diff --git a/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.py b/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.py
index d22377d..9fd15c1 100644
--- a/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.py
+++ b/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.py
@@ -313,7 +313,6 @@
 				has_wdv_or_dd_non_yearly_pro_rata,
 				number_of_pending_depreciations,
 			)
-
 			if not has_pro_rata or (
 				n < (cint(final_number_of_depreciations) - 1) or final_number_of_depreciations == 2
 			):
@@ -338,6 +337,7 @@
 					depreciation_amount,
 					from_date,
 					date_of_disposal,
+					original_schedule_date=schedule_date,
 				)
 
 				if depreciation_amount > 0:
@@ -565,23 +565,27 @@
 		)
 
 
-def _get_pro_rata_amt(row, depreciation_amount, from_date, to_date, has_wdv_or_dd_non_yearly_pro_rata=False):
+def _get_pro_rata_amt(
+	row,
+	depreciation_amount,
+	from_date,
+	to_date,
+	has_wdv_or_dd_non_yearly_pro_rata=False,
+	original_schedule_date=None,
+):
 	days = date_diff(to_date, from_date)
 	months = month_diff(to_date, from_date)
 	if has_wdv_or_dd_non_yearly_pro_rata:
-		total_days = get_total_days(to_date, 12)
+		total_days = get_total_days(original_schedule_date or to_date, 12)
 	else:
-		total_days = get_total_days(to_date, row.frequency_of_depreciation)
-
+		total_days = get_total_days(original_schedule_date or to_date, row.frequency_of_depreciation)
 	return (depreciation_amount * flt(days)) / flt(total_days), days, months
 
 
 def get_total_days(date, frequency):
 	period_start_date = add_months(date, cint(frequency) * -1)
-
 	if is_last_day_of_the_month(date):
 		period_start_date = get_last_day(period_start_date)
-
 	return date_diff(date, period_start_date)
 
 
@@ -657,7 +661,7 @@
 					),
 					1,
 				),
-			)
+			) + 1
 
 			to_date = get_last_day(
 				add_months(row.depreciation_start_date, schedule_idx * row.frequency_of_depreciation)
@@ -679,24 +683,33 @@
 	# if the Depreciation Schedule is being prepared for the first time
 	else:
 		if row.daily_prorata_based:
-			daily_depr_amount = (
+			amount = (
 				flt(asset.gross_purchase_amount)
 				- flt(asset.opening_accumulated_depreciation)
 				- flt(row.expected_value_after_useful_life)
-			) / date_diff(
-				get_last_day(
-					add_months(
-						row.depreciation_start_date,
-						flt(row.total_number_of_depreciations - asset.number_of_depreciations_booked - 1)
-						* row.frequency_of_depreciation,
-					)
-				),
-				add_days(
-					get_last_day(add_months(row.depreciation_start_date, -1 * row.frequency_of_depreciation)),
-					1,
-				),
 			)
 
+			total_days = (
+				date_diff(
+					get_last_day(
+						add_months(
+							row.depreciation_start_date,
+							flt(row.total_number_of_depreciations - asset.number_of_depreciations_booked - 1)
+							* row.frequency_of_depreciation,
+						)
+					),
+					add_days(
+						get_last_day(
+							add_months(row.depreciation_start_date, -1 * row.frequency_of_depreciation)
+						),
+						1,
+					),
+				)
+				+ 1
+			)
+
+			daily_depr_amount = amount / total_days
+
 			to_date = get_last_day(
 				add_months(row.depreciation_start_date, schedule_idx * row.frequency_of_depreciation)
 			)
@@ -708,7 +721,6 @@
 				),
 				1,
 			)
-
 			return daily_depr_amount * (date_diff(to_date, from_date) + 1)
 		else:
 			return (
diff --git a/erpnext/buying/doctype/purchase_order/test_purchase_order.py b/erpnext/buying/doctype/purchase_order/test_purchase_order.py
index c390edd..15b7fa1 100644
--- a/erpnext/buying/doctype/purchase_order/test_purchase_order.py
+++ b/erpnext/buying/doctype/purchase_order/test_purchase_order.py
@@ -1127,10 +1127,17 @@
 		po = create_purchase_order()
 		self.assertEqual(frappe.db.get_value(po.doctype, po.name, "advance_payment_status"), "Not Initiated")
 
-		pr = make_payment_request(dt=po.doctype, dn=po.name, submit_doc=True, return_doc=True)
+		pr = make_payment_request(
+			dt=po.doctype, dn=po.name, submit_doc=True, return_doc=True, payment_request_type="Outward"
+		)
+
+		po.reload()
 		self.assertEqual(frappe.db.get_value(po.doctype, po.name, "advance_payment_status"), "Initiated")
 
 		pe = get_payment_entry(po.doctype, po.name).save().submit()
+
+		pr.reload()
+		self.assertEqual(pr.status, "Paid")
 		self.assertEqual(frappe.db.get_value(po.doctype, po.name, "advance_payment_status"), "Fully Paid")
 
 		pe.reload()
diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py
index d90e09e..c9e8e4e 100644
--- a/erpnext/controllers/accounts_controller.py
+++ b/erpnext/controllers/accounts_controller.py
@@ -1924,32 +1924,43 @@
 
 			self.db_set("advance_paid", advance_paid)
 
-		self.set_advance_payment_status(advance_paid, order_total)
+		self.set_advance_payment_status()
 
-	def set_advance_payment_status(self, advance_paid: float | None = None, order_total: float | None = None):
+	def set_advance_payment_status(self):
 		new_status = None
-		# if money is paid set the paid states
-		if advance_paid:
-			new_status = "Partially Paid" if advance_paid < order_total else "Fully Paid"
 
-		if not new_status:
-			prs = frappe.db.count(
-				"Payment Request",
-				{
-					"reference_doctype": self.doctype,
-					"reference_name": self.name,
-					"docstatus": 1,
-				},
-			)
-			if self.doctype in frappe.get_hooks("advance_payment_receivable_doctypes"):
-				new_status = "Requested" if prs else "Not Requested"
-			if self.doctype in frappe.get_hooks("advance_payment_payable_doctypes"):
-				new_status = "Initiated" if prs else "Not Initiated"
+		stati = frappe.get_list(
+			"Payment Request",
+			{
+				"reference_doctype": self.doctype,
+				"reference_name": self.name,
+				"docstatus": 1,
+			},
+			pluck="status",
+		)
+		if self.doctype in frappe.get_hooks("advance_payment_receivable_doctypes"):
+			if not stati:
+				new_status = "Not Requested"
+			elif "Requested" in stati or "Failed" in stati:
+				new_status = "Requested"
+			elif "Partially Paid" in stati:
+				new_status = "Partially Paid"
+			elif "Paid" in stati:
+				new_status = "Fully Paid"
+		if self.doctype in frappe.get_hooks("advance_payment_payable_doctypes"):
+			if not stati:
+				new_status = "Not Initiated"
+			elif "Initiated" in stati or "Failed" in stati or "Payment Ordered" in stati:
+				new_status = "Initiated"
+			elif "Partially Paid" in stati:
+				new_status = "Partially Paid"
+			elif "Paid" in stati:
+				new_status = "Fully Paid"
 
 		if new_status == self.advance_payment_status:
 			return
 
-		self.db_set("advance_payment_status", new_status)
+		self.db_set("advance_payment_status", new_status, update_modified=False)
 		self.set_status(update=True)
 		self.notify_update()
 
diff --git a/erpnext/controllers/tests/test_accounts_controller.py b/erpnext/controllers/tests/test_accounts_controller.py
index 80799c3..6218bd6 100644
--- a/erpnext/controllers/tests/test_accounts_controller.py
+++ b/erpnext/controllers/tests/test_accounts_controller.py
@@ -53,7 +53,8 @@
 	20 series - Sales Invoice against Journals
 	30 series - Sales Invoice against Credit Notes
 	40 series - Company default Cost center is unset
-	50 series = Journals against Journals
+	50 series - Journals against Journals
+	60 series - Journals against Payment Entries
 	90 series - Dimension inheritence
 	"""
 
@@ -1538,3 +1539,70 @@
 		exc_je_for_je = self.get_journals_for(journal_as_payment.doctype, journal_as_payment.name)
 		self.assertEqual(exc_je_for_si, [])
 		self.assertEqual(exc_je_for_je, [])
+
+	def test_60_payment_entry_against_journal(self):
+		# Invoices
+		exc_rate1 = 75
+		exc_rate2 = 77
+		amount = 1
+		je1 = self.create_journal_entry(
+			acc1=self.debit_usd,
+			acc1_exc_rate=exc_rate1,
+			acc2=self.cash,
+			acc1_amount=amount,
+			acc2_amount=(amount * 75),
+			acc2_exc_rate=1,
+		)
+		je1.accounts[0].party_type = "Customer"
+		je1.accounts[0].party = self.customer
+		je1 = je1.save().submit()
+
+		je2 = self.create_journal_entry(
+			acc1=self.debit_usd,
+			acc1_exc_rate=exc_rate2,
+			acc2=self.cash,
+			acc1_amount=amount,
+			acc2_amount=(amount * exc_rate2),
+			acc2_exc_rate=1,
+		)
+		je2.accounts[0].party_type = "Customer"
+		je2.accounts[0].party = self.customer
+		je2 = je2.save().submit()
+
+		# Payment
+		pe = self.create_payment_entry(amount=2, source_exc_rate=exc_rate1).save().submit()
+
+		pr = self.create_payment_reconciliation()
+		pr.receivable_payable_account = self.debit_usd
+		pr.get_unreconciled_entries()
+		self.assertEqual(len(pr.invoices), 2)
+		self.assertEqual(len(pr.payments), 1)
+		invoices = [x.as_dict() for x in pr.invoices]
+		payments = [x.as_dict() for x in pr.payments]
+		pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments}))
+		pr.reconcile()
+		self.assertEqual(len(pr.invoices), 0)
+		self.assertEqual(len(pr.payments), 0)
+
+		# There should be no outstanding in both currencies
+		self.assert_ledger_outstanding(je1.doctype, je1.name, 0.0, 0.0)
+		self.assert_ledger_outstanding(je2.doctype, je2.name, 0.0, 0.0)
+
+		# Exchange Gain/Loss Journal should've been created only for JE2
+		exc_je_for_je1 = self.get_journals_for(je1.doctype, je1.name)
+		exc_je_for_je2 = self.get_journals_for(je2.doctype, je2.name)
+		self.assertEqual(exc_je_for_je1, [])
+		self.assertEqual(len(exc_je_for_je2), 1)
+
+		# Cancel Payment
+		pe.reload()
+		pe.cancel()
+
+		self.assert_ledger_outstanding(je1.doctype, je1.name, (amount * exc_rate1), amount)
+		self.assert_ledger_outstanding(je2.doctype, je2.name, (amount * exc_rate2), amount)
+
+		# Exchange Gain/Loss Journal should've been cancelled
+		exc_je_for_je1 = self.get_journals_for(je1.doctype, je1.name)
+		exc_je_for_je2 = self.get_journals_for(je2.doctype, je2.name)
+		self.assertEqual(exc_je_for_je1, [])
+		self.assertEqual(exc_je_for_je2, [])
diff --git a/erpnext/hooks.py b/erpnext/hooks.py
index 013cfb1..b3cfb35 100644
--- a/erpnext/hooks.py
+++ b/erpnext/hooks.py
@@ -349,7 +349,6 @@
 	"Payment Entry": {
 		"on_submit": [
 			"erpnext.regional.create_transaction_log",
-			"erpnext.accounts.doctype.payment_request.payment_request.update_payment_req_status",
 			"erpnext.accounts.doctype.dunning.dunning.resolve_dunning",
 		],
 		"on_cancel": ["erpnext.accounts.doctype.dunning.dunning.resolve_dunning"],
diff --git a/erpnext/manufacturing/doctype/bom/bom.json b/erpnext/manufacturing/doctype/bom/bom.json
index 11ce46f..67de6a0 100644
--- a/erpnext/manufacturing/doctype/bom/bom.json
+++ b/erpnext/manufacturing/doctype/bom/bom.json
@@ -238,7 +238,7 @@
    "fieldname": "rm_cost_as_per",
    "fieldtype": "Select",
    "label": "Rate Of Materials Based On",
-   "options": "Valuation Rate\nLast Purchase Rate\nPrice List\nManual"
+   "options": "Valuation Rate\nLast Purchase Rate\nPrice List"
   },
   {
    "allow_on_submit": 1,
@@ -637,7 +637,7 @@
  "image_field": "image",
  "is_submittable": 1,
  "links": [],
- "modified": "2024-03-27 13:06:40.214929",
+ "modified": "2024-04-02 16:22:47.518411",
  "modified_by": "Administrator",
  "module": "Manufacturing",
  "name": "BOM",
diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py
index cb56172..40b4c4f 100644
--- a/erpnext/manufacturing/doctype/bom/bom.py
+++ b/erpnext/manufacturing/doctype/bom/bom.py
@@ -147,7 +147,7 @@
 		quality_inspection_template: DF.Link | None
 		quantity: DF.Float
 		raw_material_cost: DF.Currency
-		rm_cost_as_per: DF.Literal["Valuation Rate", "Last Purchase Rate", "Price List", "Manual"]
+		rm_cost_as_per: DF.Literal["Valuation Rate", "Last Purchase Rate", "Price List"]
 		route: DF.SmallText | None
 		routing: DF.Link | None
 		scrap_items: DF.Table[BOMScrapItem]
@@ -737,6 +737,7 @@
 
 	def calculate_rm_cost(self, save=False):
 		"""Fetch RM rate as per today's valuation rate and calculate totals"""
+
 		total_rm_cost = 0
 		base_total_rm_cost = 0
 
@@ -745,7 +746,7 @@
 				continue
 
 			old_rate = d.rate
-			if self.rm_cost_as_per != "Manual":
+			if not self.bom_creator:
 				d.rate = self.get_rm_rate(
 					{
 						"company": self.company,
@@ -1017,8 +1018,6 @@
 		item_doc = frappe.get_cached_doc("Item", args.get("item_code"))
 		price_list_data = get_price_list_rate(bom_args, item_doc)
 		rate = price_list_data.price_list_rate
-	elif bom_doc.rm_cost_as_per == "Manual":
-		return
 
 	return flt(rate)
 
diff --git a/erpnext/manufacturing/doctype/bom_creator/bom_creator.json b/erpnext/manufacturing/doctype/bom_creator/bom_creator.json
index de4d254..1e8237c 100644
--- a/erpnext/manufacturing/doctype/bom_creator/bom_creator.json
+++ b/erpnext/manufacturing/doctype/bom_creator/bom_creator.json
@@ -66,7 +66,7 @@
    "fieldname": "rm_cost_as_per",
    "fieldtype": "Select",
    "label": "Rate Of Materials Based On",
-   "options": "Valuation Rate\nLast Purchase Rate\nPrice List\nManual",
+   "options": "Valuation Rate\nLast Purchase Rate\nPrice List",
    "reqd": 1
   },
   {
@@ -288,7 +288,7 @@
    "link_fieldname": "bom_creator"
   }
  ],
- "modified": "2024-03-27 13:06:40.535884",
+ "modified": "2024-04-02 16:30:59.779190",
  "modified_by": "Administrator",
  "module": "Manufacturing",
  "name": "BOM Creator",
diff --git a/erpnext/manufacturing/doctype/bom_creator/bom_creator.py b/erpnext/manufacturing/doctype/bom_creator/bom_creator.py
index 160b3be..0158f7c 100644
--- a/erpnext/manufacturing/doctype/bom_creator/bom_creator.py
+++ b/erpnext/manufacturing/doctype/bom_creator/bom_creator.py
@@ -59,7 +59,7 @@
 		qty: DF.Float
 		raw_material_cost: DF.Currency
 		remarks: DF.TextEditor | None
-		rm_cost_as_per: DF.Literal["Valuation Rate", "Last Purchase Rate", "Price List", "Manual"]
+		rm_cost_as_per: DF.Literal["Valuation Rate", "Last Purchase Rate", "Price List"]
 		set_rate_based_on_warehouse: DF.Check
 		status: DF.Literal["Draft", "Submitted", "In Progress", "Completed", "Failed", "Cancelled"]
 		uom: DF.Link | None
@@ -141,9 +141,6 @@
 		self.submit()
 
 	def set_rate_for_items(self):
-		if self.rm_cost_as_per == "Manual":
-			return
-
 		amount = self.get_raw_material_cost()
 		self.raw_material_cost = amount
 
@@ -285,7 +282,6 @@
 				"allow_alternative_item": 1,
 				"bom_creator": self.name,
 				"bom_creator_item": bom_creator_item,
-				"rm_cost_as_per": "Manual",
 			}
 		)
 
diff --git a/erpnext/manufacturing/doctype/workstation/workstation.js b/erpnext/manufacturing/doctype/workstation/workstation.js
index 255383b..c3bf9ef 100644
--- a/erpnext/manufacturing/doctype/workstation/workstation.js
+++ b/erpnext/manufacturing/doctype/workstation/workstation.js
@@ -182,6 +182,7 @@
 							me.job_cards = [r.message];
 							me.prepare_timer();
 							me.update_job_card_details();
+							me.frm.reload_doc();
 						}
 					},
 				});
@@ -229,6 +230,7 @@
 							me.job_cards = [r.message];
 							me.prepare_timer();
 							me.update_job_card_details();
+							me.frm.reload_doc();
 						}
 					},
 				});
diff --git a/erpnext/selling/doctype/sales_order/test_sales_order.py b/erpnext/selling/doctype/sales_order/test_sales_order.py
index 2c9e887..d4a6b87 100644
--- a/erpnext/selling/doctype/sales_order/test_sales_order.py
+++ b/erpnext/selling/doctype/sales_order/test_sales_order.py
@@ -2,6 +2,7 @@
 # License: GNU General Public License v3. See license.txt
 
 import json
+from unittest.mock import patch
 
 import frappe
 import frappe.permissions
@@ -1956,10 +1957,48 @@
 				self.assertEqual(so.items[0].rate, scenario.get("expected_rate"))
 				self.assertEqual(so.packed_items[0].rate, scenario.get("expected_rate"))
 
-	def test_sales_order_advance_payment_status(self):
+	@patch(
+		# this also shadows one (1) call to _get_payment_gateway_controller
+		"erpnext.accounts.doctype.payment_request.payment_request.PaymentRequest.get_payment_url",
+		return_value=None,
+	)
+	def test_sales_order_advance_payment_status(self, mocked_get_payment_url):
 		from erpnext.accounts.doctype.payment_entry.test_payment_entry import get_payment_entry
 		from erpnext.accounts.doctype.payment_request.payment_request import make_payment_request
 
+		# Flow progressing to SI with payment entries "moved" from SO to SI
+		so = make_sales_order(qty=1, rate=100, do_not_submit=True)
+		# no-op; for optical consistency with how a webshop SO would look like
+		so.order_type = "Shopping Cart"
+		so.submit()
+		self.assertEqual(frappe.db.get_value(so.doctype, so.name, "advance_payment_status"), "Not Requested")
+
+		pr = make_payment_request(
+			dt=so.doctype, dn=so.name, order_type="Shopping Cart", submit_doc=True, return_doc=True
+		)
+		self.assertEqual(frappe.db.get_value(so.doctype, so.name, "advance_payment_status"), "Requested")
+
+		pe = pr.set_as_paid()
+		pr.reload()  # status updated
+		pe.reload()  # references moved to Sales Invoice
+		self.assertEqual(pr.status, "Paid")
+		self.assertEqual(pe.references[0].reference_doctype, "Sales Invoice")
+		self.assertEqual(frappe.db.get_value(so.doctype, so.name, "advance_payment_status"), "Fully Paid")
+
+		pe.cancel()
+		pr.reload()
+		self.assertEqual(pr.status, "Paid")  # TODO: this might be a bug
+		so.reload()  # reload
+		# regardless, since the references have already "handed-over" to SI,
+		# the SO keeps its historical state at the time of hand over
+		self.assertEqual(frappe.db.get_value(so.doctype, so.name, "advance_payment_status"), "Fully Paid")
+
+		pr.cancel()
+		self.assertEqual(
+			frappe.db.get_value(so.doctype, so.name, "advance_payment_status"), "Not Requested"
+		)  # TODO: this might be a bug; handover has happened
+
+		# Flow NOT progressing to SI with payment entries NOT "moved"
 		so = make_sales_order(qty=1, rate=100)
 		self.assertEqual(frappe.db.get_value(so.doctype, so.name, "advance_payment_status"), "Not Requested")
 
@@ -1971,11 +2010,15 @@
 
 		pe.reload()
 		pe.cancel()
-		self.assertEqual(frappe.db.get_value(so.doctype, so.name, "advance_payment_status"), "Requested")
+		self.assertEqual(
+			frappe.db.get_value(so.doctype, so.name, "advance_payment_status"), "Requested"
+		)  # here: reset
 
 		pr.reload()
 		pr.cancel()
-		self.assertEqual(frappe.db.get_value(so.doctype, so.name, "advance_payment_status"), "Not Requested")
+		self.assertEqual(
+			frappe.db.get_value(so.doctype, so.name, "advance_payment_status"), "Not Requested"
+		)  # here: reset
 
 	def test_pick_list_without_rejected_materials(self):
 		serial_and_batch_item = make_item(
diff --git a/erpnext/stock/doctype/closing_stock_balance/closing_stock_balance.py b/erpnext/stock/doctype/closing_stock_balance/closing_stock_balance.py
index 1cb6305..8aa49f7 100644
--- a/erpnext/stock/doctype/closing_stock_balance/closing_stock_balance.py
+++ b/erpnext/stock/doctype/closing_stock_balance/closing_stock_balance.py
@@ -65,7 +65,7 @@
 				& (
 					(table.from_date.between(self.from_date, self.to_date))
 					| (table.to_date.between(self.from_date, self.to_date))
-					| (table.from_date >= self.from_date and table.to_date >= self.to_date)
+					| ((table.from_date >= self.from_date) & (table.to_date >= self.to_date))
 				)
 			)
 		)
diff --git a/erpnext/stock/doctype/pick_list/pick_list.json b/erpnext/stock/doctype/pick_list/pick_list.json
index a5c0cb8..fe93239 100644
--- a/erpnext/stock/doctype/pick_list/pick_list.json
+++ b/erpnext/stock/doctype/pick_list/pick_list.json
@@ -18,6 +18,7 @@
   "parent_warehouse",
   "consider_rejected_warehouses",
   "get_item_locations",
+  "pick_manually",
   "section_break_6",
   "scan_barcode",
   "column_break_13",
@@ -192,11 +193,18 @@
    "fieldname": "consider_rejected_warehouses",
    "fieldtype": "Check",
    "label": "Consider Rejected Warehouses"
+  },
+  {
+   "default": "0",
+   "description": "If enabled then system won't override the picked qty / batches / serial numbers.",
+   "fieldname": "pick_manually",
+   "fieldtype": "Check",
+   "label": "Pick Manually"
   }
  ],
  "is_submittable": 1,
  "links": [],
- "modified": "2024-03-27 13:10:13.177072",
+ "modified": "2024-03-27 22:49:16.954637",
  "modified_by": "Administrator",
  "module": "Stock",
  "name": "Pick List",
@@ -264,7 +272,7 @@
    "write": 1
   }
  ],
- "sort_field": "creation",
+ "sort_field": "modified",
  "sort_order": "DESC",
  "states": [],
  "track_changes": 1
diff --git a/erpnext/stock/doctype/pick_list/pick_list.py b/erpnext/stock/doctype/pick_list/pick_list.py
index 56bdca5..9aaa08e 100644
--- a/erpnext/stock/doctype/pick_list/pick_list.py
+++ b/erpnext/stock/doctype/pick_list/pick_list.py
@@ -41,6 +41,7 @@
 
 		amended_from: DF.Link | None
 		company: DF.Link
+		consider_rejected_warehouses: DF.Check
 		customer: DF.Link | None
 		customer_name: DF.Data | None
 		for_qty: DF.Float
@@ -49,6 +50,7 @@
 		material_request: DF.Link | None
 		naming_series: DF.Literal["STO-PICK-.YYYY.-"]
 		parent_warehouse: DF.Link | None
+		pick_manually: DF.Check
 		prompt_qty: DF.Check
 		purpose: DF.Literal["Material Transfer for Manufacture", "Material Transfer", "Delivery"]
 		scan_barcode: DF.Data | None
@@ -70,7 +72,8 @@
 
 	def before_save(self):
 		self.update_status()
-		self.set_item_locations()
+		if not self.pick_manually:
+			self.set_item_locations()
 
 		if self.get("locations"):
 			self.validate_sales_order_percentage()
diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py
index 913051a..ccd7f64 100644
--- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py
+++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py
@@ -1021,7 +1021,9 @@
 @frappe.whitelist()
 def get_items(warehouse, posting_date, posting_time, company, item_code=None, ignore_empty_stock=False):
 	ignore_empty_stock = cint(ignore_empty_stock)
-	items = [frappe._dict({"item_code": item_code, "warehouse": warehouse})]
+	items = []
+	if item_code and warehouse:
+		items = get_item_and_warehouses(item_code, warehouse)
 
 	if not item_code:
 		items = get_items_for_stock_reco(warehouse, company)
@@ -1066,6 +1068,20 @@
 	return res
 
 
+def get_item_and_warehouses(item_code, warehouse):
+	from frappe.utils.nestedset import get_descendants_of
+
+	items = []
+	if frappe.get_cached_value("Warehouse", warehouse, "is_group"):
+		childrens = get_descendants_of("Warehouse", warehouse, ignore_permissions=True, order_by="lft")
+		for ch_warehouse in childrens:
+			items.append(frappe._dict({"item_code": item_code, "warehouse": ch_warehouse}))
+	else:
+		items = [frappe._dict({"item_code": item_code, "warehouse": warehouse})]
+
+	return items
+
+
 def get_items_for_stock_reco(warehouse, company):
 	lft, rgt = frappe.db.get_value("Warehouse", warehouse, ["lft", "rgt"])
 	items = frappe.db.sql(
@@ -1080,7 +1096,7 @@
 			and i.is_stock_item = 1
 			and i.has_variants = 0
 			and exists(
-				select name from `tabWarehouse` where lft >= {lft} and rgt <= {rgt} and name = bin.warehouse
+				select name from `tabWarehouse` where lft >= {lft} and rgt <= {rgt} and name = bin.warehouse and is_group = 0
 			)
 	""",
 		as_dict=1,
@@ -1095,7 +1111,7 @@
 		where
 			i.name = id.parent
 			and exists(
-				select name from `tabWarehouse` where lft >= %s and rgt <= %s and name=id.default_warehouse
+				select name from `tabWarehouse` where lft >= %s and rgt <= %s and name=id.default_warehouse and is_group = 0
 			)
 			and i.is_stock_item = 1
 			and i.has_variants = 0
@@ -1157,7 +1173,7 @@
 			frappe._dict(
 				{
 					"item_code": row[0],
-					"warehouse": warehouse,
+					"warehouse": row[3],
 					"qty": row[8],
 					"item_name": row[1],
 					"batch_no": row[4],
diff --git a/erpnext/stock/report/item_prices/item_prices.js b/erpnext/stock/report/item_prices/item_prices.js
index 868b503..6724c1a 100644
--- a/erpnext/stock/report/item_prices/item_prices.js
+++ b/erpnext/stock/report/item_prices/item_prices.js
@@ -9,9 +9,6 @@
 			fieldtype: "Select",
 			options: "Enabled Items only\nDisabled Items only\nAll Items",
 			default: "Enabled Items only",
-			on_change: function (query_report) {
-				query_report.trigger_refresh();
-			},
 		},
 	],
 };
diff --git a/erpnext/templates/includes/footer/footer_powered.html b/erpnext/templates/includes/footer/footer_powered.html
index faf5e92..8310063 100644
--- a/erpnext/templates/includes/footer/footer_powered.html
+++ b/erpnext/templates/includes/footer/footer_powered.html
@@ -1 +1 @@
-<a href="https://erpnext.com?source=website_footer" target="_blank" class="text-muted">Powered by ERPNext</a>
+{{ _("Powered by {0}").format('<a href="https://erpnext.com?source=website_footer" target="_blank" class="text-muted">ERPNext</a>') }}