From 25852879f6068006a44b173461dc0007d2ecfe00 Mon Sep 17 00:00:00 2001 From: ljain112 Date: Fri, 13 Mar 2026 11:53:08 +0530 Subject: [PATCH] fix: correct item valuation when "Deduct" is used in Purchase Invoice and Receipt. (cherry picked from commit e68f149d3afa469d92ab6dc20a34ff8981ea4fdd) # Conflicts: # erpnext/controllers/buying_controller.py --- erpnext/controllers/buying_controller.py | 51 ++++++++++++++++ .../purchase_receipt/test_purchase_receipt.py | 59 +++++++++++++++++++ 2 files changed, 110 insertions(+) diff --git a/erpnext/controllers/buying_controller.py b/erpnext/controllers/buying_controller.py index 06a8f25b186..53a26536cf7 100644 --- a/erpnext/controllers/buying_controller.py +++ b/erpnext/controllers/buying_controller.py @@ -401,6 +401,57 @@ class BuyingController(SubcontractingController): update_regional_item_valuation_rate(self) +<<<<<<< HEAD +======= + def get_tax_details(self): + tax_accounts = [] + total_valuation_amount = 0.0 + total_actual_tax_amount = 0.0 + + for d in self.get("taxes"): + if d.category not in ["Valuation", "Valuation and Total"]: + continue + + amount = flt(d.base_tax_amount_after_discount_amount) * ( + -1 if d.get("add_deduct_tax") == "Deduct" else 1 + ) + + if d.charge_type == "On Net Total": + total_valuation_amount += amount + tax_accounts.append(d.account_head) + else: + total_actual_tax_amount += amount + + return tax_accounts, total_valuation_amount, total_actual_tax_amount + + def get_item_tax_amount(self, item, tax_accounts): + item_tax_amount = 0.0 + if item.item_tax_rate: + tax_details = json.loads(item.item_tax_rate) + for account, rate in tax_details.items(): + if account not in tax_accounts: + continue + + net_rate = item.base_net_amount + if item.sales_incoming_rate: + net_rate = item.qty * item.sales_incoming_rate + + item_tax_amount += flt(net_rate) * flt(rate) / 100 + + return item_tax_amount + + def get_item_actual_tax_amount( + self, item, actual_tax_amount, stock_and_asset_items_amount, stock_and_asset_items_qty + ): + item_proportion = ( + flt(item.base_net_amount) / stock_and_asset_items_amount + if stock_and_asset_items_amount + else flt(item.qty) / stock_and_asset_items_qty + ) + + return flt(item_proportion * actual_tax_amount, self.precision("item_tax_amount", item)) + +>>>>>>> e68f149d3a (fix: correct item valuation when "Deduct" is used in Purchase Invoice and Receipt.) def set_incoming_rate(self): """ Override item rate with incoming rate for internal stock transfer diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py index d2e0397a7ce..8508031578d 100644 --- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py @@ -1217,6 +1217,65 @@ class TestPurchaseReceipt(FrappeTestCase): pr.cancel() + def test_item_valuation_with_deduct_valuation_and_total_tax(self): + pr = make_purchase_receipt( + company="_Test Company with perpetual inventory", + warehouse="Stores - TCP1", + supplier_warehouse="Work In Progress - TCP1", + qty=5, + rate=100, + do_not_save=1, + ) + + pr.append( + "taxes", + { + "charge_type": "Actual", + "add_deduct_tax": "Deduct", + "account_head": "_Test Account Shipping Charges - TCP1", + "category": "Valuation and Total", + "cost_center": "Main - TCP1", + "description": "Valuation Discount", + "tax_amount": 20, + }, + ) + + pr.insert() + + self.assertAlmostEqual(pr.items[0].item_tax_amount, -20.0, places=2) + self.assertAlmostEqual(pr.items[0].valuation_rate, 96.0, places=2) + + pr.delete() + + pr = make_purchase_receipt( + company="_Test Company with perpetual inventory", + warehouse="Stores - TCP1", + supplier_warehouse="Work In Progress - TCP1", + qty=5, + rate=100, + do_not_save=1, + ) + + pr.append( + "taxes", + { + "charge_type": "On Net Total", + "add_deduct_tax": "Deduct", + "account_head": "_Test Account Shipping Charges - TCP1", + "category": "Valuation and Total", + "cost_center": "Main - TCP1", + "description": "Valuation Discount", + "rate": 10, + }, + ) + + pr.insert() + + self.assertAlmostEqual(pr.items[0].item_tax_amount, -50.0, places=2) + self.assertAlmostEqual(pr.items[0].valuation_rate, 90.0, places=2) + + pr.delete() + def test_po_to_pi_and_po_to_pr_worflow_full(self): """Test following behaviour: - Create PO