From 3b1be2b1ded0f300af5a2edb83ff85b3b98fac6d Mon Sep 17 00:00:00 2001 From: Marica Date: Thu, 22 Oct 2020 17:04:31 +0530 Subject: [PATCH] fix: Add Taxes if missing via Update Items (#23702) * fix: Add Taxes if missing via Update Items * chore: PO Test for adding tax row via Update Items * chore: SO Test for adding tax row via Update Items --- .../purchase_order/test_purchase_order.py | 50 ++++++++-- erpnext/controllers/accounts_controller.py | 27 ++++++ .../doctype/sales_order/test_sales_order.py | 93 ++++++++++++++++++- erpnext/stock/get_item_details.py | 5 + 4 files changed, 167 insertions(+), 8 deletions(-) diff --git a/erpnext/buying/doctype/purchase_order/test_purchase_order.py b/erpnext/buying/doctype/purchase_order/test_purchase_order.py index 7c8ae6cfb8..0cf3d24478 100644 --- a/erpnext/buying/doctype/purchase_order/test_purchase_order.py +++ b/erpnext/buying/doctype/purchase_order/test_purchase_order.py @@ -203,9 +203,39 @@ class TestPurchaseOrder(unittest.TestCase): frappe.set_user("Administrator") def test_update_child_with_tax_template(self): + """ + Test Action: Create a PO with one item having its tax account head already in the PO. + Add the same item + new item with tax template via Update Items. + Expected result: First Item's tax row is updated. New tax row is added for second Item. + """ + if not frappe.db.exists("Item", "Test Item with Tax"): + make_item("Test Item with Tax", { + 'is_stock_item': 1, + }) + + if not frappe.db.exists("Item Tax Template", {"title": 'Test Update Items Template'}): + frappe.get_doc({ + 'doctype': 'Item Tax Template', + 'title': 'Test Update Items Template', + 'company': '_Test Company', + 'taxes': [ + { + 'tax_type': "_Test Account Service Tax - _TC", + 'tax_rate': 10, + } + ] + }).insert() + + new_item_with_tax = frappe.get_doc("Item", "Test Item with Tax") + + new_item_with_tax.append("taxes", { + "item_tax_template": "Test Update Items Template", + "valid_from": nowdate() + }) + new_item_with_tax.save() + tax_template = "_Test Account Excise Duty @ 10" item = "_Test Item Home Desktop 100" - if not frappe.db.exists("Item Tax", {"parent":item, "item_tax_template":tax_template}): item_doc = frappe.get_doc("Item", item) item_doc.append("taxes", { @@ -237,17 +267,25 @@ class TestPurchaseOrder(unittest.TestCase): items = json.dumps([ {'item_code' : item, 'rate' : 500, 'qty' : 1, 'docname': po.items[0].name}, - {'item_code' : item, 'rate' : 100, 'qty' : 1} # added item + {'item_code' : item, 'rate' : 100, 'qty' : 1}, # added item whose tax account head already exists in PO + {'item_code' : new_item_with_tax.name, 'rate' : 100, 'qty' : 1} # added item whose tax account head is missing in PO ]) update_child_qty_rate('Purchase Order', items, po.name) po.reload() - self.assertEqual(po.taxes[0].tax_amount, 60) - self.assertEqual(po.taxes[0].total, 660) + self.assertEqual(po.taxes[0].tax_amount, 70) + self.assertEqual(po.taxes[0].total, 770) + self.assertEqual(po.taxes[1].account_head, "_Test Account Service Tax - _TC") + self.assertEqual(po.taxes[1].tax_amount, 70) + self.assertEqual(po.taxes[1].total, 840) + # teardown frappe.db.sql("""UPDATE `tabItem Tax` set valid_from = NULL - where parent = %(item)s and item_tax_template = %(tax)s""", - {"item": item, "tax": tax_template}) + where parent = %(item)s and item_tax_template = %(tax)s""", {"item": item, "tax": tax_template}) + po.cancel() + po.delete() + new_item_with_tax.delete() + frappe.get_doc("Item Tax Template", "Test Update Items Template").delete() def test_update_child_uom_conv_factor_change(self): po = create_purchase_order(item_code="_Test FG Item", is_subcontracted="Yes") diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 1818cb6102..fc32977658 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -1168,6 +1168,31 @@ def set_child_tax_template_and_map(item, child_item, parent_doc): if child_item.get("item_tax_template"): child_item.item_tax_rate = get_item_tax_map(parent_doc.get('company'), child_item.item_tax_template, as_json=True) +def add_taxes_from_tax_template(child_item, parent_doc): + add_taxes_from_item_tax_template = frappe.db.get_single_value("Accounts Settings", "add_taxes_from_item_tax_template") + + if child_item.get("item_tax_rate") and add_taxes_from_item_tax_template: + tax_map = json.loads(child_item.get("item_tax_rate")) + for tax_type in tax_map: + tax_rate = flt(tax_map[tax_type]) + taxes = parent_doc.get('taxes') or [] + # add new row for tax head only if missing + found = any(tax.account_head == tax_type for tax in taxes) + if not found: + tax_row = parent_doc.append("taxes", {}) + tax_row.update({ + "description" : str(tax_type).split(' - ')[0], + "charge_type" : "On Net Total", + "account_head" : tax_type, + "rate" : tax_rate + }) + if parent_doc.doctype == "Purchase Order": + tax_row.update({ + "category" : "Total", + "add_deduct_tax" : "Add" + }) + tax_row.db_insert() + def set_sales_order_defaults(parent_doctype, parent_doctype_name, child_docname, trans_item): """ Returns a Sales Order Item child item containing the default values @@ -1183,6 +1208,7 @@ def set_sales_order_defaults(parent_doctype, parent_doctype_name, child_docname, conversion_factor = flt(get_conversion_factor(item.item_code, child_item.uom).get("conversion_factor")) child_item.conversion_factor = flt(trans_item.get('conversion_factor')) or conversion_factor set_child_tax_template_and_map(item, child_item, p_doc) + add_taxes_from_tax_template(child_item, p_doc) child_item.warehouse = get_item_warehouse(item, p_doc, overwrite_warehouse=True) if not child_item.warehouse: frappe.throw(_("Cannot find {} for item {}. Please set the same in Item Master or Stock Settings.") @@ -1207,6 +1233,7 @@ def set_purchase_order_defaults(parent_doctype, parent_doctype_name, child_docna child_item.base_rate = 1 # Initiallize value will update in parent validation child_item.base_amount = 1 # Initiallize value will update in parent validation set_child_tax_template_and_map(item, child_item, p_doc) + add_taxes_from_tax_template(child_item, p_doc) return child_item def validate_and_delete_children(parent, data): diff --git a/erpnext/selling/doctype/sales_order/test_sales_order.py b/erpnext/selling/doctype/sales_order/test_sales_order.py index 1ce36dd8bf..2f5f979bdf 100644 --- a/erpnext/selling/doctype/sales_order/test_sales_order.py +++ b/erpnext/selling/doctype/sales_order/test_sales_order.py @@ -403,7 +403,7 @@ class TestSalesOrder(unittest.TestCase): trans_item = json.dumps([{'item_code' : '_Test Item', 'rate' : 200, 'qty' : 2, 'docname': so.items[0].name}]) self.assertRaises(frappe.ValidationError, update_child_qty_rate,'Sales Order', trans_item, so.name) - + def test_update_child_with_precision(self): from frappe.model.meta import get_field_precision from frappe.custom.doctype.property_setter.property_setter import make_property_setter @@ -437,7 +437,7 @@ class TestSalesOrder(unittest.TestCase): self.assertRaises(frappe.ValidationError, update_child_qty_rate,'Sales Order', trans_item, so.name) test_user.remove_roles("Accounts User") frappe.set_user("Administrator") - + def test_update_child_qty_rate_with_workflow(self): from frappe.model.workflow import apply_workflow @@ -506,6 +506,95 @@ class TestSalesOrder(unittest.TestCase): so.reload() self.assertEqual(so.packed_items[0].qty, 8) + def test_update_child_with_tax_template(self): + """ + Test Action: Create a SO with one item having its tax account head already in the SO. + Add the same item + new item with tax template via Update Items. + Expected result: First Item's tax row is updated. New tax row is added for second Item. + """ + if not frappe.db.exists("Item", "Test Item with Tax"): + make_item("Test Item with Tax", { + 'is_stock_item': 1, + }) + + if not frappe.db.exists("Item Tax Template", {"title": 'Test Update Items Template'}): + frappe.get_doc({ + 'doctype': 'Item Tax Template', + 'title': 'Test Update Items Template', + 'company': '_Test Company', + 'taxes': [ + { + 'tax_type': "_Test Account Service Tax - _TC", + 'tax_rate': 10, + } + ] + }).insert() + + new_item_with_tax = frappe.get_doc("Item", "Test Item with Tax") + + new_item_with_tax.append("taxes", { + "item_tax_template": "Test Update Items Template", + "valid_from": nowdate() + }) + new_item_with_tax.save() + + tax_template = "_Test Account Excise Duty @ 10" + item = "_Test Item Home Desktop 100" + if not frappe.db.exists("Item Tax", {"parent":item, "item_tax_template":tax_template}): + item_doc = frappe.get_doc("Item", item) + item_doc.append("taxes", { + "item_tax_template": tax_template, + "valid_from": nowdate() + }) + item_doc.save() + else: + # update valid from + frappe.db.sql("""UPDATE `tabItem Tax` set valid_from = CURDATE() + where parent = %(item)s and item_tax_template = %(tax)s""", + {"item": item, "tax": tax_template}) + + so = make_sales_order(item_code=item, qty=1, do_not_save=1) + + so.append("taxes", { + "account_head": "_Test Account Excise Duty - _TC", + "charge_type": "On Net Total", + "cost_center": "_Test Cost Center - _TC", + "description": "Excise Duty", + "doctype": "Sales Taxes and Charges", + "rate": 10 + }) + so.insert() + so.submit() + + self.assertEqual(so.taxes[0].tax_amount, 10) + self.assertEqual(so.taxes[0].total, 110) + + old_stock_settings_value = frappe.db.get_single_value("Stock Settings", "default_warehouse") + frappe.db.set_value("Stock Settings", None, "default_warehouse", "_Test Warehouse - _TC") + + items = json.dumps([ + {'item_code' : item, 'rate' : 100, 'qty' : 1, 'docname': so.items[0].name}, + {'item_code' : item, 'rate' : 200, 'qty' : 1}, # added item whose tax account head already exists in PO + {'item_code' : new_item_with_tax.name, 'rate' : 100, 'qty' : 1} # added item whose tax account head is missing in PO + ]) + update_child_qty_rate('Sales Order', items, so.name) + + so.reload() + self.assertEqual(so.taxes[0].tax_amount, 40) + self.assertEqual(so.taxes[0].total, 440) + self.assertEqual(so.taxes[1].account_head, "_Test Account Service Tax - _TC") + self.assertEqual(so.taxes[1].tax_amount, 40) + self.assertEqual(so.taxes[1].total, 480) + + # teardown + frappe.db.sql("""UPDATE `tabItem Tax` set valid_from = NULL + where parent = %(item)s and item_tax_template = %(tax)s""", {"item": item, "tax": tax_template}) + so.cancel() + so.delete() + new_item_with_tax.delete() + frappe.get_doc("Item Tax Template", "Test Update Items Template").delete() + frappe.db.set_value("Stock Settings", None, "default_warehouse", old_stock_settings_value) + def test_warehouse_user(self): frappe.permissions.add_user_permission("Warehouse", "_Test Warehouse 1 - _TC", "test@example.com") frappe.permissions.add_user_permission("Warehouse", "_Test Warehouse 2 - _TC1", "test2@example.com") diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py index 1a7c15ebca..8d8dcb74c3 100644 --- a/erpnext/stock/get_item_details.py +++ b/erpnext/stock/get_item_details.py @@ -398,6 +398,11 @@ def get_item_warehouse(item, args, overwrite_warehouse, defaults={}): else: warehouse = args.get('warehouse') + if not warehouse: + default_warehouse = frappe.db.get_single_value("Stock Settings", "default_warehouse") + if frappe.db.get_value("Warehouse", default_warehouse, "company") == args.company: + return default_warehouse + return warehouse def update_barcode_value(out):