diff --git a/erpnext/controllers/buying_controller.py b/erpnext/controllers/buying_controller.py index a3c30a78d76..98799296854 100644 --- a/erpnext/controllers/buying_controller.py +++ b/erpnext/controllers/buying_controller.py @@ -417,20 +417,21 @@ class BuyingController(SubcontractingController): stock_and_asset_items_qty, stock_and_asset_items_amount = 0, 0 last_item_idx = 1 for d in self.get("items"): - if d.item_code and d.item_code in stock_and_asset_items: + if d.item_code: stock_and_asset_items_qty += flt(d.qty) stock_and_asset_items_amount += flt(d.base_net_amount) last_item_idx = d.idx tax_accounts, total_valuation_amount, total_actual_tax_amount = self.get_tax_details() + remaining_amount = total_actual_tax_amount for i, item in enumerate(self.get("items")): if item.item_code and (item.qty or item.get("rejected_qty")): item_tax_amount, actual_tax_amount = 0.0, 0.0 if i == (last_item_idx - 1): item_tax_amount = total_valuation_amount - actual_tax_amount = total_actual_tax_amount + actual_tax_amount = remaining_amount else: # calculate item tax amount item_tax_amount = self.get_item_tax_amount(item, tax_accounts) @@ -443,7 +444,8 @@ class BuyingController(SubcontractingController): stock_and_asset_items_amount, stock_and_asset_items_qty, ) - total_actual_tax_amount -= actual_tax_amount + + remaining_amount -= actual_tax_amount # This code is required here to calculate the correct valuation for stock items if item.item_code not in stock_and_asset_items: diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py index 96280d00a05..4500a9fbe79 100644 --- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py @@ -1333,6 +1333,73 @@ class TestPurchaseReceipt(ERPNextTestSuite): pr.delete() + def test_valuation_tax_distribution_with_non_stock_item(self): + """A "Valuation and Total" tax is distributed across all items by net amount, but only + stock/asset items can carry valuation. For a document with 2 stock items + 1 service + item (each net 100) and a 30 valuation tax, each item's share is 10; only the two stock + items capitalize their share (20 total), so the non-stock item's 10 share must not be + capitalized onto the stock items.""" + company = "_Test Company with perpetual inventory" + warehouse = "Stores - TCP1" + + stock_item1 = make_item(properties={"is_stock_item": 1}).name + stock_item2 = make_item(properties={"is_stock_item": 1}).name + service_item = make_item(properties={"is_stock_item": 0}).name + + pr = frappe.new_doc("Purchase Receipt") + pr.company = company + pr.supplier = "_Test Supplier" + pr.currency = "INR" + # Order matters: stock, service, stock (service item in the middle) + for code in (stock_item1, service_item, stock_item2): + pr.append( + "items", + { + "item_code": code, + "qty": 1, + "rate": 100, + "warehouse": warehouse, + "cost_center": "Main - TCP1", + "expense_account": "Cost of Goods Sold - TCP1", + }, + ) + + pr.append( + "taxes", + { + "charge_type": "Actual", + "account_head": "_Test Account Shipping Charges - TCP1", + "category": "Valuation and Total", + "cost_center": "Main - TCP1", + "description": "Valuation Tax", + "tax_amount": 30, + }, + ) + + pr.insert() + + # 30 tax / 300 net = 10 per item. The two stock items capitalize 10 each; the service + # item's 10 share is excluded from valuation (not dumped onto the stock items). + self.assertAlmostEqual(pr.items[0].item_tax_amount, 10.0, places=2) + self.assertAlmostEqual(pr.items[1].item_tax_amount, 0.0, places=2) + self.assertAlmostEqual(pr.items[2].item_tax_amount, 10.0, places=2) + self.assertAlmostEqual(pr.items[0].valuation_rate, 110.0, places=2) + self.assertAlmostEqual(pr.items[2].valuation_rate, 110.0, places=2) + + pr.submit() + + gl_entries = get_gl_entries("Purchase Receipt", pr.name, skip_cancelled=True, as_dict=True) + gl_map = {row.account: row for row in gl_entries} + + warehouse_account = get_warehouse_account_map(company) + stock_account = warehouse_account[warehouse]["account"] + + # Stock asset = 200 (goods) + 20 (stock items' share of the valuation tax) + self.assertAlmostEqual(gl_map[stock_account].debit, 220.0, places=2) + self.assertAlmostEqual(gl_map["Stock Received But Not Billed - TCP1"].credit, 200.0, places=2) + # Only the stock items' share (20) is capitalized; the service item's 10 is excluded + self.assertAlmostEqual(gl_map["_Test Account Shipping Charges - TCP1"].credit, 20.0, places=2) + def test_po_to_pi_and_po_to_pr_worflow_full(self): """Test following behaviour: - Create PO