Merge pull request #30834 from ankush/transfer_precision

fix: precision loss when transferring 
diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py
index 2a7354d..890ac47 100644
--- a/erpnext/stock/doctype/stock_entry/stock_entry.py
+++ b/erpnext/stock/doctype/stock_entry/stock_entry.py
@@ -672,7 +672,8 @@
 					batch_no=d.batch_no,
 				)
 
-			d.basic_rate = flt(d.basic_rate, d.precision("basic_rate"))
+			# do not round off basic rate to avoid precision loss
+			d.basic_rate = flt(d.basic_rate)
 			if d.is_process_loss:
 				d.basic_rate = flt(0.0)
 			d.basic_amount = flt(flt(d.transfer_qty) * flt(d.basic_rate), d.precision("basic_amount"))
@@ -720,7 +721,7 @@
 				total_fg_qty = sum([flt(d.transfer_qty) for d in self.items if d.is_finished_item])
 				return flt(outgoing_items_cost / total_fg_qty)
 
-	def get_basic_rate_for_manufactured_item(self, finished_item_qty, outgoing_items_cost=0):
+	def get_basic_rate_for_manufactured_item(self, finished_item_qty, outgoing_items_cost=0) -> float:
 		scrap_items_cost = sum([flt(d.basic_amount) for d in self.get("items") if d.is_scrap_item])
 
 		# Get raw materials cost from BOM if multiple material consumption entries
@@ -760,10 +761,8 @@
 		for d in self.get("items"):
 			if d.transfer_qty:
 				d.amount = flt(flt(d.basic_amount) + flt(d.additional_cost), d.precision("amount"))
-				d.valuation_rate = flt(
-					flt(d.basic_rate) + (flt(d.additional_cost) / flt(d.transfer_qty)),
-					d.precision("valuation_rate"),
-				)
+				# Do not round off valuation rate to avoid precision loss
+				d.valuation_rate = flt(d.basic_rate) + (flt(d.additional_cost) / flt(d.transfer_qty))
 
 	def set_total_incoming_outgoing_value(self):
 		self.total_incoming_value = self.total_outgoing_value = 0.0
diff --git a/erpnext/stock/doctype/stock_ledger_entry/test_stock_ledger_entry.py b/erpnext/stock/doctype/stock_ledger_entry/test_stock_ledger_entry.py
index 4e2fc83..eb1e0fc 100644
--- a/erpnext/stock/doctype/stock_ledger_entry/test_stock_ledger_entry.py
+++ b/erpnext/stock/doctype/stock_ledger_entry/test_stock_ledger_entry.py
@@ -8,9 +8,8 @@
 from frappe.core.page.permission_manager.permission_manager import reset
 from frappe.custom.doctype.property_setter.property_setter import make_property_setter
 from frappe.query_builder.functions import CombineDatetime
-from frappe.tests.utils import FrappeTestCase
-from frappe.utils import add_days, today
-from frappe.utils.data import add_to_date
+from frappe.tests.utils import FrappeTestCase, change_settings
+from frappe.utils import add_days, add_to_date, flt, today
 
 from erpnext.accounts.doctype.gl_entry.gl_entry import rename_gle_sle_docs
 from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note
@@ -1219,6 +1218,41 @@
 		except Exception as e:
 			self.fail("Double processing of qty for clashing timestamp.")
 
+	@change_settings("System Settings", {"float_precision": 3, "currency_precision": 2})
+	def test_transfer_invariants(self):
+		"""Extact stock value should be transferred."""
+
+		item = make_item(
+			properties={
+				"valuation_method": "Moving Average",
+				"stock_uom": "Kg",
+			}
+		).name
+		source_warehouse = "Stores - TCP1"
+		target_warehouse = "Finished Goods - TCP1"
+
+		make_purchase_receipt(
+			item=item,
+			warehouse=source_warehouse,
+			qty=20,
+			conversion_factor=1000,
+			uom="Tonne",
+			rate=156_526.0,
+			company="_Test Company with perpetual inventory",
+		)
+		transfer = make_stock_entry(
+			item=item, from_warehouse=source_warehouse, to_warehouse=target_warehouse, qty=1_728.0
+		)
+
+		filters = {"voucher_no": transfer.name, "voucher_type": transfer.doctype, "is_cancelled": 0}
+		sles = frappe.get_all(
+			"Stock Ledger Entry",
+			fields=["*"],
+			filters=filters,
+			order_by="timestamp(posting_date, posting_time), creation",
+		)
+		self.assertEqual(abs(sles[0].stock_value_difference), sles[1].stock_value_difference)
+
 
 def create_repack_entry(**args):
 	args = frappe._dict(args)