From 8b22d9d95ecaf2a9ce96316638a856472d1bc359 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Wed, 3 Dec 2025 17:42:06 +0530 Subject: [PATCH] fix: LCV is not changing the valuation of the repacked item (cherry picked from commit ccbbc60585ef255e6fcb1acff4a0f0f94212ac43) # Conflicts: # erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py --- .../purchase_receipt/test_purchase_receipt.py | 201 ++++++++++++++++++ erpnext/stock/stock_ledger.py | 33 ++- 2 files changed, 233 insertions(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py index 959c58b2380..7f18d66a15a 100644 --- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py @@ -4448,6 +4448,207 @@ class TestPurchaseReceipt(FrappeTestCase): self.assertEqual(srbnb_cost, 1000) +<<<<<<< HEAD +======= + def test_purchase_expense_account(self): + item = "Test Item with Purchase Expense Account" + make_item(item, {"is_stock_item": 1}) + company = "_Test Company with perpetual inventory" + + expense_account = "_Test Account Purchase Expense - TCP1" + expense_contra_account = "_Test Account Purchase Contra Expense - TCP1" + if not frappe.db.exists("Account", expense_account): + frappe.get_doc( + { + "doctype": "Account", + "account_name": "_Test Account Purchase Expense", + "parent_account": "Stock Expenses - TCP1", + "company": company, + "is_group": 0, + "root_type": "Expense", + } + ).insert() + + if not frappe.db.exists("Account", expense_contra_account): + frappe.get_doc( + { + "doctype": "Account", + "account_name": "_Test Account Purchase Contra Expense", + "parent_account": "Stock Expenses - TCP1", + "company": company, + "is_group": 0, + "root_type": "Expense", + } + ).insert() + + item_doc = frappe.get_doc("Item", item) + item_doc.append( + "item_defaults", + { + "company": company, + "default_warehouse": "Stores - TCP1", + "purchase_expense_account": expense_account, + "purchase_expense_contra_account": expense_contra_account, + }, + ) + + item_doc.save() + + pr = make_purchase_receipt( + item_code=item, + qty=10, + rate=100, + company=company, + warehouse="Stores - TCP1", + ) + + gl_entries = get_gl_entries(pr.doctype, pr.name) + accounts = [d.account for d in gl_entries] + self.assertTrue(expense_account in accounts) + self.assertTrue(expense_contra_account in accounts) + + for row in gl_entries: + if row.account == expense_account: + self.assertEqual(row.debit, 1000) + if row.account == expense_contra_account: + self.assertEqual(row.credit, 1000) + + def test_repost_gl_entries(self): + from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry + + item = "Test Item for Repost GL Entries" + make_item(item, {"is_stock_item": 1}) + company = "_Test Company with perpetual inventory" + + account = "Reposting Adjustment - TCP1" + if not frappe.db.exists("Account", account): + frappe.get_doc( + { + "doctype": "Account", + "account_name": "Reposting Adjustment", + "parent_account": "Stock Expenses - TCP1", + "company": company, + "is_group": 0, + "account_type": "Expense Account", + } + ).insert() + + se = make_stock_entry( + item_code=item, + qty=10, + rate=100, + company=company, + target="Stores - TCP1", + ) + + gl_entries = get_gl_entries(se.doctype, se.name) + for row in gl_entries: + self.assertTrue(row.account in ["Stock In Hand - TCP1", "Stock Adjustment - TCP1"]) + + se.items[0].db_set("expense_account", account) + se.reload() + + repost_doc = frappe.get_doc( + { + "doctype": "Repost Item Valuation", + "based_on": "Transaction", + "voucher_type": se.doctype, + "voucher_no": se.name, + "posting_date": se.posting_date, + "posting_time": se.posting_time, + "company": se.company, + "repost_only_accounting_ledgers": 1, + } + ) + + repost_doc.submit() + + gl_entries = get_gl_entries(se.doctype, se.name) + for row in gl_entries: + self.assertTrue(row.account in ["Stock In Hand - TCP1", account]) + + def test_lcv_for_repack_entry(self): + from erpnext.stock.doctype.landed_cost_voucher.test_landed_cost_voucher import ( + create_landed_cost_voucher, + ) + from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry + + for item in [ + "Potatoes Raw Material Item", + "Fries Finished Goods Item", + ]: + create_item(item) + + pr = make_purchase_receipt( + item_code="Potatoes Raw Material Item", + warehouse="_Test Warehouse - _TC", + qty=100, + rate=50, + ) + + wh1 = create_warehouse("WH A1", company=pr.company) + wh2 = create_warehouse("WH A2", company=pr.company) + + ste = make_stock_entry( + purpose="Repack", + source="_Test Warehouse - _TC", + item_code="Potatoes Raw Material Item", + qty=100, + company=pr.company, + do_not_save=1, + ) + + ste.append( + "items", + { + "item_code": "Fries Finished Goods Item", + "qty": 50, + "t_warehouse": wh1, + }, + ) + + ste.append( + "items", + { + "item_code": "Fries Finished Goods Item", + "qty": 50, + "t_warehouse": wh2, + }, + ) + + ste.insert() + ste.submit() + ste.reload() + + for row in ste.items: + if row.t_warehouse: + self.assertEqual(row.valuation_rate, 50) + + sles = frappe.get_all( + "Stock Ledger Entry", + filters={"voucher_type": ste.doctype, "voucher_no": ste.name, "actual_qty": (">", 0)}, + pluck="stock_value_difference", + ) + + self.assertEqual(sles, [2500.0, 2500.0]) + + create_landed_cost_voucher("Purchase Receipt", pr.name, pr.company, charges=2000 * -1) + + ste.reload() + + for row in ste.items: + if row.t_warehouse: + self.assertEqual(row.valuation_rate, 30) + + sles = frappe.get_all( + "Stock Ledger Entry", + filters={"voucher_type": ste.doctype, "voucher_no": ste.name, "actual_qty": (">", 0)}, + pluck="stock_value_difference", + ) + + self.assertEqual(sles, [1500.0, 1500.0]) + +>>>>>>> ccbbc60585 (fix: LCV is not changing the valuation of the repacked item) def prepare_data_for_internal_transfer(): from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_internal_supplier diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index 8b61ca56983..3aff3d35ffa 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -647,10 +647,32 @@ class update_entries_after: if sle.dependant_sle_voucher_detail_no: entries_to_fix = self.get_dependent_entries_to_fix(entries_to_fix, sle) + if sle.voucher_type == "Stock Entry" and is_repack_entry(sle.voucher_no): + # for repack entries, we need to repost both source and target warehouses + self.update_distinct_item_warehouses_for_repack(sle) if self.exceptions: self.raise_exceptions() + def update_distinct_item_warehouses_for_repack(self, sle): + sles = ( + frappe.get_all( + "Stock Ledger Entry", + filters={ + "voucher_type": "Stock Entry", + "voucher_no": sle.voucher_no, + "actual_qty": (">", 0), + "is_cancelled": 0, + "voucher_detail_no": ("!=", sle.dependant_sle_voucher_detail_no), + }, + fields=["*"], + ) + or [] + ) + + for dependant_sle in sles: + self.update_distinct_item_warehouses(dependant_sle) + def has_stock_reco_with_serial_batch(self, sle): if ( sle.voucher_type == "Stock Reconciliation" @@ -1156,7 +1178,11 @@ class update_entries_after: def get_dynamic_incoming_outgoing_rate(self, sle): # Get updated incoming/outgoing rate from transaction - if sle.recalculate_rate or self.has_landed_cost_based_on_pi(sle): + if ( + sle.recalculate_rate + or self.has_landed_cost_based_on_pi(sle) + or (sle.voucher_type == "Stock Entry" and sle.actual_qty > 0 and is_repack_entry(sle.voucher_no)) + ): rate = self.get_incoming_outgoing_rate_from_transaction(sle) if flt(sle.actual_qty) >= 0: @@ -2454,3 +2480,8 @@ def get_incoming_rate_for_serial_and_batch(item_code, row, sn_obj): incoming_rate = abs(flt(sn_obj.batch_avg_rate.get(row.batch_no))) return incoming_rate + + +@frappe.request_cache +def is_repack_entry(stock_entry_id): + return frappe.get_cached_value("Stock Entry", stock_entry_id, "purpose") == "Repack"