diff --git a/erpnext/accounts/doctype/customs_tariff_tax/__init__.py b/erpnext/accounts/doctype/customs_tariff_tax/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/erpnext/accounts/doctype/customs_tariff_tax/customs_tariff_tax.json b/erpnext/accounts/doctype/customs_tariff_tax/customs_tariff_tax.json new file mode 100644 index 000000000000..abb41fbd3e4b --- /dev/null +++ b/erpnext/accounts/doctype/customs_tariff_tax/customs_tariff_tax.json @@ -0,0 +1,50 @@ +{ + "actions": [], + "allow_rename": 1, + "creation": "2024-07-18 14:18:35.943218", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "customs_tariff_number", + "account_head", + "amount" + ], + "fields": [ + { + "fieldname": "account_head", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Account Head", + "options": "Account", + "read_only": 1 + }, + { + "fieldname": "amount", + "fieldtype": "Currency", + "in_list_view": 1, + "label": "Amount", + "options": "currency" + }, + { + "fieldname": "customs_tariff_number", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Customs Tariff Number", + "options": "Customs Tariff Number", + "read_only": 1 + } + ], + "index_web_pages_for_search": 1, + "istable": 1, + "links": [], + "modified": "2024-08-20 18:22:06.460326", + "modified_by": "Administrator", + "module": "Accounts", + "name": "Customs Tariff Tax", + "owner": "Administrator", + "permissions": [], + "sort_field": "modified", + "sort_order": "DESC", + "states": [] +} \ No newline at end of file diff --git a/erpnext/accounts/doctype/customs_tariff_tax/customs_tariff_tax.py b/erpnext/accounts/doctype/customs_tariff_tax/customs_tariff_tax.py new file mode 100644 index 000000000000..a4b7b5ec5e4c --- /dev/null +++ b/erpnext/accounts/doctype/customs_tariff_tax/customs_tariff_tax.py @@ -0,0 +1,8 @@ +# Copyright (c) 2024, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +# import frappe +from frappe.model.document import Document + +class CustomsTariffTax(Document): + pass diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json index 95467bd54bee..d2d0b7c975ab 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json @@ -105,6 +105,8 @@ "shipping_rule", "section_break_51", "taxes", + "section_break_idyjv", + "customs_tariff_tax", "sec_tax_breakup", "other_charges_calculation", "tax_exclusive_totals_section", @@ -1878,13 +1880,24 @@ "options": "Company:company:default_currency", "print_hide": 1, "read_only": 1 + }, + { + "fieldname": "section_break_idyjv", + "fieldtype": "Section Break" + }, + { + "depends_on": "eval:(doc.taxes || []).filter(d => d.charge_type == \"On HS Code\").length", + "fieldname": "customs_tariff_tax", + "fieldtype": "Table", + "label": "Customs Tariff Tax", + "options": "Customs Tariff Tax" } ], "icon": "fa fa-file-text", "idx": 204, "is_submittable": 1, "links": [], - "modified": "2024-02-12 16:23:06.891238", + "modified": "2024-08-12 18:36:30.556478", "modified_by": "Administrator", "module": "Accounts", "name": "Purchase Invoice", 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 47cf067b7287..8b7097488660 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 @@ -65,7 +65,7 @@ "label": "Type", "oldfieldname": "charge_type", "oldfieldtype": "Select", - "options": "\nActual\nOn Net Total\nOn Previous Row Amount\nOn Previous Row Total\nOn Item Quantity\nWeighted Distribution\nManual", + "options": "\nActual\nOn Net Total\nOn Previous Row Amount\nOn Previous Row Total\nOn Item Quantity\nOn HS Code\nWeighted Distribution\nManual", "reqd": 1 }, { @@ -265,7 +265,7 @@ "idx": 1, "istable": 1, "links": [], - "modified": "2023-08-23 13:59:40.617093", + "modified": "2024-09-05 16:27:52.880423", "modified_by": "Administrator", "module": "Accounts", "name": "Purchase Taxes and Charges", diff --git a/erpnext/controllers/taxes_and_totals.py b/erpnext/controllers/taxes_and_totals.py index b4fea20c4c11..adb82f214140 100644 --- a/erpnext/controllers/taxes_and_totals.py +++ b/erpnext/controllers/taxes_and_totals.py @@ -586,6 +586,35 @@ def get_current_tax_amount(self, item, tax, item_tax_map, weighted_distrubution_ current_tax_amount = (tax_rate / 100.0) * taxable_amount elif tax.charge_type == "On Item Quantity": current_tax_amount = tax_rate * item.qty + elif tax.charge_type == "On HS Code": + items_net_total = [] + #totalling of items prices based on their HS codes + if len(self.doc.get("customs_tariff_tax", [])) > 0: + for items in self.doc.get("items", []): + index = -1 + for i, sel_item in enumerate(items_net_total): + if sel_item["customs_tariff_number"] == items.customs_tariff_number: + index = i + break + + if index != -1: + items_net_total[index]["total"] += items.amount + else: + items_net_total.append({ + "customs_tariff_number": items.customs_tariff_number, + "total": items.amount, + }) + + #tax distribution according to the item qty & HS code + for tariff_tax_table in self.doc.get("customs_tariff_tax", []): + if tariff_tax_table.account_head == tax.account_head and item.customs_tariff_number == tariff_tax_table.customs_tariff_number: + for i, sel_item in enumerate(items_net_total): + if sel_item["customs_tariff_number"] == item.customs_tariff_number: + HS_code_tax_amount = tariff_tax_table.amount + HS_code_net_total = items_net_total[i]["total"] + current_tax_amount = (HS_code_tax_amount / HS_code_net_total) * item.amount + break + self.set_item_wise_tax(item, tax, tax_rate, current_tax_amount) diff --git a/erpnext/public/js/controllers/accounts.js b/erpnext/public/js/controllers/accounts.js index b50fd15e81bd..5d900c7315e0 100644 --- a/erpnext/public/js/controllers/accounts.js +++ b/erpnext/public/js/controllers/accounts.js @@ -168,13 +168,16 @@ cur_frm.cscript.account_head = function(doc, cdt, cdn) { args: { account_head: d.account_head }, - callback: function(r) { + callback: (r) => { if (r.message) { frappe.model.set_value(cdt, cdn, "description", r.message.account_name); frappe.model.set_value(cdt, cdn, "exclude_from_item_tax_amount", cint(r.message.exclude_from_item_tax_amount)); if (["Actual", "Manual"].includes(d.charge_type)) { frappe.model.set_value(cdt, cdn, "rate", flt(r.message.tax_rate) || 0); + } else if (d.charge_type == "On HS Code") { + this.update_customs_tariff_table(); + frappe.model.set_value(cdt, cdn, "rate", 0); } else { frappe.model.set_value(cdt, cdn, "rate", 0); } @@ -184,6 +187,51 @@ cur_frm.cscript.account_head = function(doc, cdt, cdn) { } } +cur_frm.cscript.update_customs_tariff_table = function() { + let account_heads = (this.frm.doc.taxes || []).filter(tax => tax.charge_type === "On HS Code" && tax.account_head).map(tax => tax.account_head); + account_heads = [...new Set(account_heads)]; + + let customs_tariff_nos = (this.frm.doc.items || []).filter(d => d.customs_tariff_number).map(d => d.customs_tariff_number); + customs_tariff_nos = [...new Set(customs_tariff_nos)]; + + let item_account_tariff_nos = []; + for (let account_head of account_heads) { + for (let customs_tariff_number of customs_tariff_nos) { + item_account_tariff_nos.push({ + account_head: account_head, + customs_tariff_number: customs_tariff_number, + }); + } + } + + let ui_account_tariff_nos = []; + for (let d of this.frm.doc.customs_tariff_tax || []) { + if (d.customs_tariff_number && d.account_head) { + ui_account_tariff_nos.push({ + account_head: d.account_head, + customs_tariff_number: d.customs_tariff_number, + }); + } + } + + // Add missing + for (let item_data of item_account_tariff_nos) { + if (!ui_account_tariff_nos.find(ui_data => ui_data.account_head == item_data.account_head && ui_data.customs_tariff_number == item_data.customs_tariff_number)) { + let row = this.frm.add_child('customs_tariff_tax'); + row.account_head = item_data.account_head; + row.customs_tariff_number = item_data.customs_tariff_number + this.frm.refresh_field('customs_tariff_tax'); + } + } + + // Remove extra + this.frm.doc.customs_tariff_tax = (this.frm.doc.customs_tariff_tax || []).filter(ui_data => { + return item_account_tariff_nos.find(item_data => ui_data.account_head == item_data.account_head && ui_data.customs_tariff_number == item_data.customs_tariff_number); + }); + + this.frm.refresh_field("customs_tariff_tax"); +} + cur_frm.cscript.validate_taxes_and_charges = function(cdt, cdn) { var d = locals[cdt][cdn]; var msg = ""; diff --git a/erpnext/public/js/controllers/taxes_and_totals.js b/erpnext/public/js/controllers/taxes_and_totals.js index a199345914ca..212092b2d8fc 100644 --- a/erpnext/public/js/controllers/taxes_and_totals.js +++ b/erpnext/public/js/controllers/taxes_and_totals.js @@ -2,7 +2,7 @@ // License: GNU General Public License v3. See license.txt erpnext.taxes_and_totals_hooks = []; - + erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments { apply_pricing_rule_on_item(item) { let effective_item_rate = item.price_list_rate; @@ -694,6 +694,35 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments { current_tax_amount = (tax_rate / 100.0) * taxable_amount; } else if (tax.charge_type == "On Item Quantity") { current_tax_amount = tax_rate * item.qty; + } else if (tax.charge_type == "On HS Code") { + let items_net_total = []; + // totalling of items prices based on their HS codes + if ((this.frm.doc["customs_tariff_tax"] || []).length > 0) { + (this.frm.doc["items"] || []).forEach((item) => { + let index = items_net_total.findIndex(d => d.customs_tariff_number === item.customs_tariff_number); + + if (index !== -1) { + items_net_total[index].total += item.amount; + } else { + items_net_total.push({ + customs_tariff_number: item.customs_tariff_number, + total: item.amount, + }); + } + }); + } + + // tax distribution according to the item qty & HS code + $.each(this.frm.doc["customs_tariff_tax"] || [], function(j, tariff_tax_table) { + if (tariff_tax_table.account_head == tax.account_head && item.customs_tariff_number == tariff_tax_table.customs_tariff_number) { + let HS_code_tax_amount = tariff_tax_table.amount; + let HS_code_net_total = items_net_total[items_net_total.findIndex(d => d.customs_tariff_number == item.customs_tariff_number)].total + current_tax_amount = (HS_code_tax_amount / HS_code_net_total) * item.amount; + } + }); + this.frm.refresh_field("taxes"); + this.frm.refresh_field("total_taxes_and_charges"); + } this.set_item_wise_tax(item, tax, tax_rate, current_tax_amount); @@ -701,6 +730,10 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments { return current_tax_amount; } + amount = function(doc, cdt, cdn) { + this._calculate_taxes_and_totals(); + } + set_item_wise_tax(item, tax, tax_rate, current_tax_amount) { // store tax breakup for each item if (!item.item_tax_detail.hasOwnProperty(tax.name)) { @@ -825,6 +858,7 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments { this.frm.doc.total_taxes_and_charges = this.frm.doc.total_after_taxes - this.frm.doc.taxable_total - flt(this.frm.doc.rounding_adjustment); if (this.should_round_transaction_currency()) { this.frm.doc.total_taxes_and_charges = flt(this.frm.doc.total_taxes_and_charges, precision("total_taxes_and_charges")); + } this.set_in_company_currency(this.frm.doc, ["total_taxes_and_charges", "rounding_adjustment"], diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index 0ec2e87eb4d8..98fc97d9c703 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -143,8 +143,9 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe frappe.ui.form.on(this.frm.cscript.tax_table, { taxes_remove: function(frm, cdt, cdn) { - cur_frm.cscript.set_dynamic_labels(); - cur_frm.cscript.calculate_taxes_and_totals(); + frm.cscript.set_dynamic_labels(); + frm.cscript.update_customs_tariff_table(); + frm.cscript.calculate_taxes_and_totals(); } }); @@ -201,6 +202,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe }, items_remove: function (frm) { + frm.cscript.update_customs_tariff_table(); frm.cscript.calculate_taxes_and_totals(); }, @@ -775,7 +777,8 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe let key = item.name; me.apply_rule_on_other_items({key: item}); } - } + }, + () => me.update_customs_tariff_table(), ]); } } diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py index 16e2cddbe3a8..fd4a19dcb03b 100644 --- a/erpnext/stock/get_item_details.py +++ b/erpnext/stock/get_item_details.py @@ -263,6 +263,7 @@ def get_basic_details(args, item, overwrite_warehouse=True): "has_serial_no": item.has_serial_no, "has_batch_no": item.has_batch_no, "is_vehicle": item.is_vehicle, + "customs_tariff_number": item.customs_tariff_number, "batch_no": args.get("batch_no") if args.get("batch_no") and frappe.db.get_value("Batch", args.get("batch_no"), 'item') == item.name else "", "stock_uom": item.stock_uom, "uom": default_uom,