Merge pull request #32563 from rohitwaghchaure/fixed-source-stock-reposting-for-purchase-flow

fix: consider sales rate as incoming rate for transit warehouse in purchase flow
diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
index 2b633cb..5dbe7eb 100644
--- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
+++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
@@ -705,6 +705,10 @@
 							)
 						)
 
+						credit_amount = item.base_net_amount
+						if self.is_internal_supplier and item.valuation_rate:
+							credit_amount = flt(item.valuation_rate * item.stock_qty)
+
 						# Intentionally passed negative debit amount to avoid incorrect GL Entry validation
 						gl_entries.append(
 							self.get_gl_dict(
@@ -714,7 +718,7 @@
 									"cost_center": item.cost_center,
 									"project": item.project or self.project,
 									"remarks": self.get("remarks") or _("Accounting Entry for Stock"),
-									"debit": -1 * flt(item.base_net_amount, item.precision("base_net_amount")),
+									"debit": -1 * flt(credit_amount, item.precision("base_net_amount")),
 								},
 								warehouse_account[item.from_warehouse]["account_currency"],
 								item=item,
diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
index ce44ae3..52e221c 100644
--- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
+++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
@@ -3306,6 +3306,7 @@
 			"item_name": args.item_name or "_Test Item",
 			"description": args.description or "_Test Item",
 			"warehouse": args.warehouse or "_Test Warehouse - _TC",
+			"target_warehouse": args.target_warehouse,
 			"qty": args.qty or 1,
 			"uom": args.uom or "Nos",
 			"stock_uom": args.uom or "Nos",
diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py
index 6269724..1f807b8 100644
--- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py
+++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py
@@ -2,6 +2,9 @@
 # License: GNU General Public License v3. See license.txt
 
 
+from cmath import pi
+from turtle import update
+
 import frappe
 from frappe.tests.utils import FrappeTestCase, change_settings
 from frappe.utils import add_days, cint, cstr, flt, today
@@ -14,6 +17,7 @@
 from erpnext.stock.doctype.purchase_receipt.purchase_receipt import make_purchase_invoice
 from erpnext.stock.doctype.serial_no.serial_no import SerialNoDuplicateError, get_serial_nos
 from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse
+from erpnext.stock.get_item_details import update_stock
 from erpnext.stock.stock_ledger import SerialNoExistsInFutureTransaction
 
 
@@ -1199,6 +1203,8 @@
 		self.assertEqual(pr1.items[0].rate, 100)
 		pr1.submit()
 
+		self.assertEqual(pr1.is_internal_supplier, 1)
+
 		# Backdated purchase receipt entry, the valuation rate should be updated for DN1 and PR1
 		make_purchase_receipt(
 			item_code=item_doc.name,
@@ -1241,6 +1247,234 @@
 
 		self.assertEqual(query[0].value, 0)
 
+	def test_backdated_transaction_for_internal_transfer_in_trasit_warehouse_for_purchase_receipt(
+		self,
+	):
+		from erpnext.stock.doctype.delivery_note.delivery_note import make_inter_company_purchase_receipt
+		from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note
+
+		prepare_data_for_internal_transfer()
+		customer = "_Test Internal Customer 2"
+		company = "_Test Company with perpetual inventory"
+
+		from_warehouse = create_warehouse("_Test Internal From Warehouse New", company=company)
+		to_warehouse = create_warehouse("_Test Internal To Warehouse New", company=company)
+		item_doc = create_item("Test Internal Transfer Item")
+
+		target_warehouse = create_warehouse("_Test Internal GIT Warehouse New", company=company)
+
+		make_purchase_receipt(
+			item_code=item_doc.name,
+			company=company,
+			posting_date=add_days(today(), -1),
+			warehouse=from_warehouse,
+			qty=1,
+			rate=100,
+		)
+
+		# Keep stock in advance and make sure that systen won't pick this stock while reposting backdated transaction
+		for i in range(1, 4):
+			make_purchase_receipt(
+				item_code=item_doc.name,
+				company=company,
+				posting_date=add_days(today(), -1 * i),
+				warehouse=target_warehouse,
+				qty=1,
+				rate=320 * i,
+			)
+
+		dn1 = create_delivery_note(
+			item_code=item_doc.name,
+			company=company,
+			customer=customer,
+			cost_center="Main - TCP1",
+			expense_account="Cost of Goods Sold - TCP1",
+			qty=1,
+			rate=500,
+			warehouse=from_warehouse,
+			target_warehouse=target_warehouse,
+		)
+
+		self.assertEqual(dn1.items[0].rate, 100)
+
+		pr1 = make_inter_company_purchase_receipt(dn1.name)
+		pr1.items[0].warehouse = to_warehouse
+		self.assertEqual(pr1.items[0].rate, 100)
+		pr1.submit()
+
+		stk_ledger = frappe.db.get_value(
+			"Stock Ledger Entry",
+			{"voucher_type": "Purchase Receipt", "voucher_no": pr1.name, "warehouse": target_warehouse},
+			["stock_value_difference", "outgoing_rate"],
+			as_dict=True,
+		)
+
+		self.assertEqual(abs(stk_ledger.stock_value_difference), 100)
+		self.assertEqual(stk_ledger.outgoing_rate, 100)
+
+		# Backdated purchase receipt entry, the valuation rate should be updated for DN1 and PR1
+		make_purchase_receipt(
+			item_code=item_doc.name,
+			company=company,
+			posting_date=add_days(today(), -2),
+			warehouse=from_warehouse,
+			qty=1,
+			rate=200,
+		)
+
+		dn_value = frappe.db.get_value(
+			"Stock Ledger Entry",
+			{"voucher_type": "Delivery Note", "voucher_no": dn1.name, "warehouse": target_warehouse},
+			"stock_value_difference",
+		)
+
+		self.assertEqual(abs(dn_value), 200.00)
+
+		pr_value = frappe.db.get_value(
+			"Stock Ledger Entry",
+			{"voucher_type": "Purchase Receipt", "voucher_no": pr1.name, "warehouse": to_warehouse},
+			"stock_value_difference",
+		)
+
+		self.assertEqual(abs(pr_value), 200.00)
+		pr1.load_from_db()
+
+		self.assertEqual(pr1.items[0].valuation_rate, 200)
+		self.assertEqual(pr1.items[0].rate, 100)
+
+		Gl = frappe.qb.DocType("GL Entry")
+
+		query = (
+			frappe.qb.from_(Gl)
+			.select(
+				(fn.Sum(Gl.debit) - fn.Sum(Gl.credit)).as_("value"),
+			)
+			.where((Gl.voucher_type == pr1.doctype) & (Gl.voucher_no == pr1.name))
+		).run(as_dict=True)
+
+		self.assertEqual(query[0].value, 0)
+
+	def test_backdated_transaction_for_internal_transfer_in_trasit_warehouse_for_purchase_invoice(
+		self,
+	):
+		from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import (
+			make_purchase_invoice as make_purchase_invoice_for_si,
+		)
+		from erpnext.accounts.doctype.sales_invoice.sales_invoice import (
+			make_inter_company_purchase_invoice,
+		)
+		from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
+
+		prepare_data_for_internal_transfer()
+		customer = "_Test Internal Customer 2"
+		company = "_Test Company with perpetual inventory"
+
+		from_warehouse = create_warehouse("_Test Internal From Warehouse New", company=company)
+		to_warehouse = create_warehouse("_Test Internal To Warehouse New", company=company)
+		item_doc = create_item("Test Internal Transfer Item")
+
+		target_warehouse = create_warehouse("_Test Internal GIT Warehouse New", company=company)
+
+		make_purchase_invoice_for_si(
+			item_code=item_doc.name,
+			company=company,
+			posting_date=add_days(today(), -1),
+			warehouse=from_warehouse,
+			qty=1,
+			update_stock=1,
+			expense_account="Cost of Goods Sold - TCP1",
+			cost_center="Main - TCP1",
+			rate=100,
+		)
+
+		# Keep stock in advance and make sure that systen won't pick this stock while reposting backdated transaction
+		for i in range(1, 4):
+			make_purchase_invoice_for_si(
+				item_code=item_doc.name,
+				company=company,
+				posting_date=add_days(today(), -1 * i),
+				warehouse=target_warehouse,
+				update_stock=1,
+				qty=1,
+				expense_account="Cost of Goods Sold - TCP1",
+				cost_center="Main - TCP1",
+				rate=320 * i,
+			)
+
+		si1 = create_sales_invoice(
+			item_code=item_doc.name,
+			company=company,
+			customer=customer,
+			cost_center="Main - TCP1",
+			income_account="Sales - TCP1",
+			qty=1,
+			rate=500,
+			update_stock=1,
+			warehouse=from_warehouse,
+			target_warehouse=target_warehouse,
+		)
+
+		self.assertEqual(si1.items[0].rate, 100)
+
+		pi1 = make_inter_company_purchase_invoice(si1.name)
+		pi1.items[0].warehouse = to_warehouse
+		self.assertEqual(pi1.items[0].rate, 100)
+		pi1.update_stock = 1
+		pi1.save()
+		pi1.submit()
+
+		stk_ledger = frappe.db.get_value(
+			"Stock Ledger Entry",
+			{"voucher_type": pi1.doctype, "voucher_no": pi1.name, "warehouse": target_warehouse},
+			["stock_value_difference", "outgoing_rate"],
+			as_dict=True,
+		)
+
+		self.assertEqual(abs(stk_ledger.stock_value_difference), 100)
+		self.assertEqual(stk_ledger.outgoing_rate, 100)
+
+		# Backdated purchase receipt entry, the valuation rate should be updated for si1 and pi1
+		make_purchase_receipt(
+			item_code=item_doc.name,
+			company=company,
+			posting_date=add_days(today(), -2),
+			warehouse=from_warehouse,
+			qty=1,
+			rate=200,
+		)
+
+		si_value = frappe.db.get_value(
+			"Stock Ledger Entry",
+			{"voucher_type": si1.doctype, "voucher_no": si1.name, "warehouse": target_warehouse},
+			"stock_value_difference",
+		)
+
+		self.assertEqual(abs(si_value), 200.00)
+
+		pi_value = frappe.db.get_value(
+			"Stock Ledger Entry",
+			{"voucher_type": pi1.doctype, "voucher_no": pi1.name, "warehouse": to_warehouse},
+			"stock_value_difference",
+		)
+
+		self.assertEqual(abs(pi_value), 200.00)
+		pi1.load_from_db()
+
+		self.assertEqual(pi1.items[0].valuation_rate, 200)
+		self.assertEqual(pi1.items[0].rate, 100)
+
+		Gl = frappe.qb.DocType("GL Entry")
+
+		query = (
+			frappe.qb.from_(Gl)
+			.select(
+				(fn.Sum(Gl.debit) - fn.Sum(Gl.credit)).as_("value"),
+			)
+			.where((Gl.voucher_type == pi1.doctype) & (Gl.voucher_no == pi1.name))
+		).run(as_dict=True)
+
+		self.assertEqual(query[0].value, 0)
+
 	def test_batch_expiry_for_purchase_receipt(self):
 		from erpnext.controllers.sales_and_purchase_return import make_return_doc
 
diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py
index 9ca40c3..cdf6e89 100644
--- a/erpnext/stock/stock_ledger.py
+++ b/erpnext/stock/stock_ledger.py
@@ -542,6 +542,14 @@
 		if not self.args.get("sle_id"):
 			self.get_dynamic_incoming_outgoing_rate(sle)
 
+		if (
+			sle.voucher_type in ["Purchase Receipt", "Purchase Invoice"]
+			and sle.voucher_detail_no
+			and sle.actual_qty < 0
+			and frappe.get_cached_value(sle.voucher_type, sle.voucher_no, "is_internal_supplier")
+		):
+			sle.outgoing_rate = get_incoming_rate_for_inter_company_transfer(sle)
+
 		if get_serial_nos(sle.serial_no):
 			self.get_serialized_values(sle)
 			self.wh_data.qty_after_transaction += flt(sle.actual_qty)
@@ -589,6 +597,7 @@
 		sle.stock_queue = json.dumps(self.wh_data.stock_queue)
 		sle.stock_value_difference = stock_value_difference
 		sle.doctype = "Stock Ledger Entry"
+
 		frappe.get_doc(sle).db_update()
 
 		if not self.args.get("sle_id"):
@@ -652,22 +661,7 @@
 				and sle.voucher_detail_no
 				and frappe.get_cached_value(sle.voucher_type, sle.voucher_no, "is_internal_supplier")
 			):
-				field = (
-					"delivery_note_item" if sle.voucher_type == "Purchase Receipt" else "sales_invoice_item"
-				)
-				doctype = (
-					"Delivery Note Item" if sle.voucher_type == "Purchase Receipt" else "Sales Invoice Item"
-				)
-				refernce_name = frappe.get_cached_value(
-					sle.voucher_type + " Item", sle.voucher_detail_no, field
-				)
-
-				if refernce_name:
-					rate = frappe.get_cached_value(
-						doctype,
-						refernce_name,
-						"incoming_rate",
-					)
+				rate = get_incoming_rate_for_inter_company_transfer(sle)
 			else:
 				if sle.voucher_type in ("Purchase Receipt", "Purchase Invoice"):
 					rate_field = "valuation_rate"
@@ -748,14 +742,12 @@
 
 	def update_rate_on_purchase_receipt(self, sle, outgoing_rate):
 		if frappe.db.exists(sle.voucher_type + " Item", sle.voucher_detail_no):
-			frappe.db.set_value(
-				sle.voucher_type + " Item",
-				sle.voucher_detail_no,
-				{
-					"base_net_rate": outgoing_rate,
-					"valuation_rate": outgoing_rate,
-				},
-			)
+			if sle.voucher_type in ["Purchase Receipt", "Purchase Invoice"] and frappe.get_cached_value(
+				sle.voucher_type, sle.voucher_no, "is_internal_supplier"
+			):
+				frappe.db.set_value(
+					f"{sle.voucher_type} Item", sle.voucher_detail_no, "valuation_rate", sle.outgoing_rate
+				)
 		else:
 			frappe.db.set_value(
 				"Purchase Receipt Item Supplied", sle.voucher_detail_no, "rate", outgoing_rate
@@ -1546,3 +1538,25 @@
 	if item_code and cint(frappe.db.get_value("Item", item_code, "allow_negative_stock", cache=True)):
 		return True
 	return False
+
+
+def get_incoming_rate_for_inter_company_transfer(sle) -> float:
+	"""
+	For inter company transfer, incoming rate is the average of the outgoing rate
+	"""
+	rate = 0.0
+
+	field = "delivery_note_item" if sle.voucher_type == "Purchase Receipt" else "sales_invoice_item"
+
+	doctype = "Delivery Note Item" if sle.voucher_type == "Purchase Receipt" else "Sales Invoice Item"
+
+	reference_name = frappe.get_cached_value(sle.voucher_type + " Item", sle.voucher_detail_no, field)
+
+	if reference_name:
+		rate = frappe.get_cached_value(
+			doctype,
+			reference_name,
+			"incoming_rate",
+		)
+
+	return rate