From fc80a50640d9f31bd3524ed1c21b355925218445 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Mon, 16 May 2022 17:02:47 +0530 Subject: [PATCH] fix: precision loss when transferring (backport #30834) (#31032) * fix: stock transfer value when precision differs (cherry picked from commit b1c90e9949c2bfccb224b44859da4dc64932f3fe) # Conflicts: # erpnext/stock/doctype/stock_ledger_entry/test_stock_ledger_entry.py * fix: Merge conflicts * chore: Remove unused `flt` (sider) Co-authored-by: Ankush Menat Co-authored-by: Marica --- .../stock/doctype/stock_entry/stock_entry.py | 11 +++--- .../test_stock_ledger_entry.py | 38 ++++++++++++++++++- 2 files changed, 41 insertions(+), 8 deletions(-) diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index 8ff11ee9b80..de9af084abe 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -671,7 +671,8 @@ class StockEntry(StockController): raise_error_if_no_rate=raise_error_if_no_rate, ) - 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")) @@ -718,7 +719,7 @@ class StockEntry(StockController): 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 @@ -758,10 +759,8 @@ class StockEntry(StockController): 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 4d8ee777e58..8298313d4bd 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 @@ -7,8 +7,7 @@ import frappe from frappe.core.page.permission_manager.permission_manager import reset from frappe.custom.doctype.property_setter.property_setter import make_property_setter from frappe.tests.utils import FrappeTestCase, change_settings -from frappe.utils import add_days, today -from frappe.utils.data import add_to_date +from frappe.utils import add_days, add_to_date, 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 @@ -719,6 +718,41 @@ class TestStockLedgerEntry(FrappeTestCase): 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)