From 6bcb0067428793f90c183bce9385752a0c044500 Mon Sep 17 00:00:00 2001 From: David Date: Wed, 20 Nov 2024 02:30:11 +0100 Subject: [PATCH] fix: tax_item_rate calculation --- .../advance_taxes_and_charges.json | 15 ++++++-- .../advance_taxes_and_charges.py | 1 + .../test_pos_invoice_merge_log.py | 23 ++++++------ .../purchase_taxes_and_charges.json | 13 ++++++- .../purchase_taxes_and_charges.py | 1 + .../sales_taxes_and_charges.json | 13 ++++++- .../sales_taxes_and_charges.py | 1 + erpnext/controllers/accounts_controller.py | 11 +++--- erpnext/controllers/taxes_and_totals.py | 4 +-- .../tests/test_item_wise_tax_details.py | 2 +- .../public/js/controllers/taxes_and_totals.js | 1 + erpnext/public/js/controllers/transaction.js | 5 +-- erpnext/stock/get_item_details.py | 36 ++++++++++--------- 13 files changed, 85 insertions(+), 41 deletions(-) diff --git a/erpnext/accounts/doctype/advance_taxes_and_charges/advance_taxes_and_charges.json b/erpnext/accounts/doctype/advance_taxes_and_charges/advance_taxes_and_charges.json index d4d49abca8e5..85489bd169a1 100644 --- a/erpnext/accounts/doctype/advance_taxes_and_charges/advance_taxes_and_charges.json +++ b/erpnext/accounts/doctype/advance_taxes_and_charges/advance_taxes_and_charges.json @@ -13,6 +13,7 @@ "col_break_1", "description", "included_in_paid_amount", + "set_by_item_tax_template", "accounting_dimensions_section", "cost_center", "dimension_col_break", @@ -194,12 +195,22 @@ "oldfieldtype": "Currency", "options": "Company:company:default_currency", "read_only": 1 + }, + { + "default": "0", + "fieldname": "set_by_item_tax_template", + "fieldtype": "Check", + "hidden": 1, + "label": "Set by Item Tax Template", + "print_hide": 1, + "read_only": 1, + "report_hide": 1 } ], "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2024-09-24 06:51:07.417348", + "modified": "2024-11-22 19:16:22.346267", "modified_by": "Administrator", "module": "Accounts", "name": "Advance Taxes and Charges", @@ -208,4 +219,4 @@ "sort_field": "creation", "sort_order": "ASC", "states": [] -} +} \ No newline at end of file diff --git a/erpnext/accounts/doctype/advance_taxes_and_charges/advance_taxes_and_charges.py b/erpnext/accounts/doctype/advance_taxes_and_charges/advance_taxes_and_charges.py index f4bd4a6a3c1d..70012da76c37 100644 --- a/erpnext/accounts/doctype/advance_taxes_and_charges/advance_taxes_and_charges.py +++ b/erpnext/accounts/doctype/advance_taxes_and_charges/advance_taxes_and_charges.py @@ -34,6 +34,7 @@ class AdvanceTaxesandCharges(Document): parenttype: DF.Data rate: DF.Float row_id: DF.Data | None + set_by_item_tax_template: DF.Check tax_amount: DF.Currency total: DF.Currency # end: auto-generated types diff --git a/erpnext/accounts/doctype/pos_invoice_merge_log/test_pos_invoice_merge_log.py b/erpnext/accounts/doctype/pos_invoice_merge_log/test_pos_invoice_merge_log.py index 2285a9a28931..e64c9e979b3a 100644 --- a/erpnext/accounts/doctype/pos_invoice_merge_log/test_pos_invoice_merge_log.py +++ b/erpnext/accounts/doctype/pos_invoice_merge_log/test_pos_invoice_merge_log.py @@ -157,16 +157,19 @@ def test_consolidated_invoice_item_taxes(self): consolidated_invoice = frappe.get_doc("Sales Invoice", inv.consolidated_invoice) item_wise_tax_detail = json.loads(consolidated_invoice.get("taxes")[0].item_wise_tax_detail) - - tax_data = item_wise_tax_detail.get("_Test Item") - self.assertEqual(tax_data.get("tax_rate"), 9) - self.assertEqual(tax_data.get("tax_amount"), 9) - self.assertEqual(tax_data.get("net_amount"), 100) - - tax_data = item_wise_tax_detail.get("_Test Item 2") - self.assertEqual(tax_data.get("tax_rate"), 5) - self.assertEqual(tax_data.get("tax_amount"), 5) - self.assertEqual(tax_data.get("net_amount"), 100) + expected_item_wise_tax_detail = { + "_Test Item": { + "tax_rate": 9, + "tax_amount": 9, + "net_amount": 100, + }, + "_Test Item 2": { + "tax_rate": 5, + "tax_amount": 5, + "net_amount": 100, + }, + } + self.assertEqual(item_wise_tax_detail, expected_item_wise_tax_detail) finally: frappe.set_user("Administrator") frappe.db.sql("delete from `tabPOS Profile`") diff --git a/erpnext/accounts/doctype/purchase_taxes_and_charges/purchase_taxes_and_charges.json b/erpnext/accounts/doctype/purchase_taxes_and_charges/purchase_taxes_and_charges.json index 66b36eca2e25..3817bb60f6b0 100644 --- a/erpnext/accounts/doctype/purchase_taxes_and_charges/purchase_taxes_and_charges.json +++ b/erpnext/accounts/doctype/purchase_taxes_and_charges/purchase_taxes_and_charges.json @@ -17,6 +17,7 @@ "account_head", "description", "is_tax_withholding_account", + "set_by_item_tax_template", "section_break_10", "rate", "accounting_dimensions_section", @@ -254,12 +255,22 @@ "options": "Company:company:default_currency", "print_hide": 1, "read_only": 1 + }, + { + "default": "0", + "fieldname": "set_by_item_tax_template", + "fieldtype": "Check", + "hidden": 1, + "label": "Set by Item Tax Template", + "print_hide": 1, + "read_only": 1, + "report_hide": 1 } ], "idx": 1, "istable": 1, "links": [], - "modified": "2024-09-24 06:47:25.129901", + "modified": "2024-11-22 19:17:02.377473", "modified_by": "Administrator", "module": "Accounts", "name": "Purchase Taxes and Charges", diff --git a/erpnext/accounts/doctype/purchase_taxes_and_charges/purchase_taxes_and_charges.py b/erpnext/accounts/doctype/purchase_taxes_and_charges/purchase_taxes_and_charges.py index 161fb9b14640..5b6062b32c92 100644 --- a/erpnext/accounts/doctype/purchase_taxes_and_charges/purchase_taxes_and_charges.py +++ b/erpnext/accounts/doctype/purchase_taxes_and_charges/purchase_taxes_and_charges.py @@ -42,6 +42,7 @@ class PurchaseTaxesandCharges(Document): parenttype: DF.Data rate: DF.Float row_id: DF.Data | None + set_by_item_tax_template: DF.Check tax_amount: DF.Currency tax_amount_after_discount_amount: DF.Currency total: DF.Currency diff --git a/erpnext/accounts/doctype/sales_taxes_and_charges/sales_taxes_and_charges.json b/erpnext/accounts/doctype/sales_taxes_and_charges/sales_taxes_and_charges.json index 182fe329ec76..4e6df6087411 100644 --- a/erpnext/accounts/doctype/sales_taxes_and_charges/sales_taxes_and_charges.json +++ b/erpnext/accounts/doctype/sales_taxes_and_charges/sales_taxes_and_charges.json @@ -13,6 +13,7 @@ "description", "included_in_print_rate", "included_in_paid_amount", + "set_by_item_tax_template", "accounting_dimensions_section", "cost_center", "dimension_col_break", @@ -232,13 +233,23 @@ "oldfieldtype": "Currency", "options": "Company:company:default_currency", "read_only": 1 + }, + { + "default": "0", + "fieldname": "set_by_item_tax_template", + "fieldtype": "Check", + "hidden": 1, + "label": "Set by Item Tax Template", + "print_hide": 1, + "read_only": 1, + "report_hide": 1 } ], "idx": 1, "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2024-09-24 06:49:32.034074", + "modified": "2024-11-22 19:17:31.898467", "modified_by": "Administrator", "module": "Accounts", "name": "Sales Taxes and Charges", diff --git a/erpnext/accounts/doctype/sales_taxes_and_charges/sales_taxes_and_charges.py b/erpnext/accounts/doctype/sales_taxes_and_charges/sales_taxes_and_charges.py index 04da2fa7fe8e..b6896e9b6c1d 100644 --- a/erpnext/accounts/doctype/sales_taxes_and_charges/sales_taxes_and_charges.py +++ b/erpnext/accounts/doctype/sales_taxes_and_charges/sales_taxes_and_charges.py @@ -40,6 +40,7 @@ class SalesTaxesandCharges(Document): parenttype: DF.Data rate: DF.Float row_id: DF.Data | None + set_by_item_tax_template: DF.Check tax_amount: DF.Currency tax_amount_after_discount_amount: DF.Currency total: DF.Currency diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 7db099f5da11..268684ef6f88 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -961,6 +961,7 @@ def append_taxes_from_item_tax_template(self): "account_head": account_head, "rate": 0, "description": account_head, + "set_by_item_tax_template": 1, }, ) @@ -3176,10 +3177,11 @@ def set_child_tax_template_and_map(item, child_item, parent_doc): ) child_item.item_tax_template = _get_item_tax_template(ctx, item.taxes) - 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 - ) + child_item.item_tax_rate = get_item_tax_map( + doc=parent_doc, + tax_template=child_item.item_tax_template, + as_json=True, + ) def add_taxes_from_tax_template(child_item, parent_doc, db_insert=True): @@ -3202,6 +3204,7 @@ def add_taxes_from_tax_template(child_item, parent_doc, db_insert=True): "charge_type": "On Net Total", "account_head": tax_type, "rate": tax_rate, + "set_by_item_tax_template": 1, } ) if parent_doc.doctype == "Purchase Order": diff --git a/erpnext/controllers/taxes_and_totals.py b/erpnext/controllers/taxes_and_totals.py index aa417d0ef157..a14cde116af3 100644 --- a/erpnext/controllers/taxes_and_totals.py +++ b/erpnext/controllers/taxes_and_totals.py @@ -501,9 +501,7 @@ def get_current_tax_and_net_amount(self, item, tax, item_tax_map): ) elif tax.charge_type == "On Net Total": - if not item_tax_map: - current_net_amount = item.net_amount - elif tax.account_head in item_tax_map: + if tax.account_head in item_tax_map: current_net_amount = item.net_amount current_tax_amount = (tax_rate / 100.0) * item.net_amount elif tax.charge_type == "On Previous Row Amount": diff --git a/erpnext/controllers/tests/test_item_wise_tax_details.py b/erpnext/controllers/tests/test_item_wise_tax_details.py index 6bd08d618849..e535aad69337 100644 --- a/erpnext/controllers/tests/test_item_wise_tax_details.py +++ b/erpnext/controllers/tests/test_item_wise_tax_details.py @@ -79,7 +79,7 @@ def test_item_wise_tax_detail(self): "rate": 50, }, ) - + self.doc.set_missing_item_details() calculate_taxes_and_totals(self.doc) expected_values = { diff --git a/erpnext/public/js/controllers/taxes_and_totals.js b/erpnext/public/js/controllers/taxes_and_totals.js index a7ef90522de5..f94a2dd2cf6e 100644 --- a/erpnext/public/js/controllers/taxes_and_totals.js +++ b/erpnext/public/js/controllers/taxes_and_totals.js @@ -336,6 +336,7 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments { child.charge_type = "On Net Total"; child.account_head = tax; child.rate = 0; + child.set_by_item_tax_template = true; } }); } diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index 36ce3ba70a79..e1031743ad6f 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -751,6 +751,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe child.charge_type = "On Net Total"; child.account_head = tax; child.rate = 0; + child.set_by_item_tax_template = true; } }); } @@ -2076,7 +2077,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe return this.frm.call({ method: "erpnext.stock.get_item_details.get_item_tax_info", args: { - company: me.frm.doc.company, + doc: me.frm.doc, tax_category: cstr(me.frm.doc.tax_category), item_codes: item_codes, item_rates: item_rates, @@ -2107,7 +2108,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe return this.frm.call({ method: "erpnext.stock.get_item_details.get_item_tax_map", args: { - company: me.frm.doc.company, + doc: me.frm.doc, item_tax_template: item.item_tax_template, as_json: true }, diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py index 64cd9569481d..8809e0e3d25d 100644 --- a/erpnext/stock/get_item_details.py +++ b/erpnext/stock/get_item_details.py @@ -98,8 +98,8 @@ def get_item_details( get_item_tax_template(ctx, item, out) out.item_tax_rate = get_item_tax_map( - ctx.company, - out.item_tax_template or ctx.item_tax_template, + doc=doc or ctx, + tax_template=out.item_tax_template or ctx.item_tax_template, as_json=True, ) @@ -528,7 +528,7 @@ def get_barcode_data(items_list=None, item_code=None): @frappe.whitelist() -def get_item_tax_info(company, tax_category, item_codes, item_rates=None, item_tax_templates=None): +def get_item_tax_info(doc, tax_category, item_codes, item_rates=None, item_tax_templates=None): out = {} if item_tax_templates is None: @@ -537,14 +537,10 @@ def get_item_tax_info(company, tax_category, item_codes, item_rates=None, item_t if item_rates is None: item_rates = {} - if isinstance(item_codes, str): - item_codes = json.loads(item_codes) - - if isinstance(item_rates, str): - item_rates = json.loads(item_rates) - - if isinstance(item_tax_templates, str): - item_tax_templates = json.loads(item_tax_templates) + doc = parse_json(doc) + item_codes = parse_json(item_codes) + item_rates = parse_json(item_rates) + item_tax_templates = parse_json(item_tax_templates) for item_code in item_codes: if not item_code or item_code[1] in out or not item_tax_templates.get(item_code[1]): @@ -553,7 +549,7 @@ def get_item_tax_info(company, tax_category, item_codes, item_rates=None, item_t out[item_code[1]] = ItemDetails() item = frappe.get_cached_doc("Item", item_code[0]) ctx: ItemDetailsCtx = { - "company": company, + "company": doc.company, "tax_category": tax_category, "base_net_rate": item_rates.get(item_code[1]), } @@ -563,7 +559,9 @@ def get_item_tax_info(company, tax_category, item_codes, item_rates=None, item_t get_item_tax_template(ctx, item, out[item_code[1]]) out[item_code[1]]["item_tax_rate"] = get_item_tax_map( - company, out[item_code[1]].get("item_tax_template"), as_json=True + doc=doc, + tax_template=out[item_code[1]].get("item_tax_template"), + as_json=True, ) return out @@ -689,12 +687,16 @@ def is_within_valid_range(ctx: ItemDetailsCtx, tax) -> bool: @frappe.whitelist() -def get_item_tax_map(company, item_tax_template, as_json=True): +def get_item_tax_map(*, doc: str | dict | Document, tax_template: str | None = None, as_json=True): + doc = parse_json(doc) item_tax_map = {} - if item_tax_template: - template = frappe.get_cached_doc("Item Tax Template", item_tax_template) + for t in (t for t in (doc.get("taxes") or []) if not t.set_by_item_tax_template): + item_tax_map[t.account_head] = t.rate + + if tax_template: + template = frappe.get_cached_doc("Item Tax Template", tax_template) for d in template.taxes: - if frappe.get_cached_value("Account", d.tax_type, "company") == company: + if frappe.get_cached_value("Account", d.tax_type, "company") == doc.get("company"): item_tax_map[d.tax_type] = d.tax_rate return json.dumps(item_tax_map) if as_json else item_tax_map