From fd531a6b5e17a43e611ab322f2880751592241f7 Mon Sep 17 00:00:00 2001 From: Saif Ur Rehman Date: Sat, 29 Dec 2018 01:49:11 +0500 Subject: [PATCH] feat: Tax Category based on Address --- erpnext/accounts/custom/address.json | 58 +++++++++++++++++++ .../accounts_settings/accounts_settings.json | 37 +++++++++++- .../accounts_settings/accounts_settings.py | 4 +- erpnext/accounts/party.py | 20 +++++-- erpnext/public/js/controllers/buying.js | 1 + erpnext/public/js/controllers/transaction.js | 11 ++-- erpnext/public/js/utils/party.js | 39 ++++++++++++- erpnext/selling/sales_common.js | 2 + 8 files changed, 158 insertions(+), 14 deletions(-) create mode 100644 erpnext/accounts/custom/address.json diff --git a/erpnext/accounts/custom/address.json b/erpnext/accounts/custom/address.json new file mode 100644 index 0000000000..08f972d13b --- /dev/null +++ b/erpnext/accounts/custom/address.json @@ -0,0 +1,58 @@ +{ + "custom_fields": [ + { + "_assign": null, + "_comments": null, + "_liked_by": null, + "_user_tags": null, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "collapsible_depends_on": null, + "columns": 0, + "creation": "2018-12-28 22:29:21.828090", + "default": null, + "depends_on": null, + "description": null, + "docstatus": 0, + "dt": "Address", + "fetch_from": null, + "fieldname": "tax_category", + "fieldtype": "Link", + "hidden": 0, + "idx": 14, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "insert_after": "fax", + "label": "Tax Category", + "modified": "2018-12-28 22:29:21.828090", + "modified_by": "Administrator", + "name": "Address-tax_category", + "no_copy": 0, + "options": "Tax Category", + "owner": "Administrator", + "parent": null, + "parentfield": null, + "parenttype": null, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "print_width": null, + "read_only": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "translatable": 0, + "unique": 0, + "width": null + } + ], + "custom_perms": [], + "doctype": "Address", + "property_setters": [], + "sync_on_migrate": 1 +} \ No newline at end of file diff --git a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json index 4597eae899..305093ba62 100644 --- a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json +++ b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json @@ -112,6 +112,41 @@ "translatable": 0, "unique": 0 }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "default": "Billing Address", + "description": "Address used to determine Tax Category in transactions.", + "fieldname": "determine_address_tax_category_from", + "fieldtype": "Select", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Determine Address Tax Category From", + "length": 0, + "no_copy": 0, + "options": "Billing Address\nShipping Address", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, { "allow_bulk_edit": 0, "allow_in_quick_entry": 0, @@ -674,7 +709,7 @@ "issingle": 1, "istable": 0, "max_attachments": 0, - "modified": "2018-12-28 04:30:38.835015", + "modified": "2018-12-28 23:18:29.863177", "modified_by": "Administrator", "module": "Accounts", "name": "Accounts Settings", diff --git a/erpnext/accounts/doctype/accounts_settings/accounts_settings.py b/erpnext/accounts/doctype/accounts_settings/accounts_settings.py index 81841780d7..3222aeb085 100644 --- a/erpnext/accounts/doctype/accounts_settings/accounts_settings.py +++ b/erpnext/accounts/doctype/accounts_settings/accounts_settings.py @@ -15,7 +15,9 @@ class AccountsSettings(Document): frappe.clear_cache() def validate(self): - frappe.db.set_default("add_taxes_from_item_tax_template", self.get("add_taxes_from_item_tax_template", "")) + for f in ["add_taxes_from_item_tax_template"]: + frappe.db.set_default(f, self.get(f, "")) + self.validate_stale_days() self.enable_payment_schedule_in_print() self.enable_fields_for_cost_center_settings() diff --git a/erpnext/accounts/party.py b/erpnext/accounts/party.py index 90fc3d143c..b22d3d43d4 100644 --- a/erpnext/accounts/party.py +++ b/erpnext/accounts/party.py @@ -8,7 +8,7 @@ from frappe import _, msgprint, scrub from frappe.defaults import get_user_permissions from frappe.model.utils import get_fetch_values from frappe.utils import (add_days, getdate, formatdate, date_diff, - add_years, get_timestamp, nowdate, flt, add_months, get_last_day) + add_years, get_timestamp, nowdate, flt, cstr, add_months, get_last_day) from frappe.contacts.doctype.address.address import (get_address_display, get_default_address, get_company_address) from frappe.contacts.doctype.contact.contact import get_contact_details, get_default_contact @@ -16,7 +16,7 @@ from erpnext.exceptions import PartyFrozen, PartyDisabled, InvalidAccountCurrenc from erpnext.accounts.utils import get_fiscal_year from erpnext import get_default_currency, get_company_currency -from six import iteritems +from six import iteritems, string_types class DuplicatePartyAccountError(frappe.ValidationError): pass @@ -49,7 +49,8 @@ def _get_party_details(party=None, account=None, party_type="Customer", company= set_other_values(out, party, party_type) set_price_list(out, party, party_type, price_list) - out["tax_category"] = get_tax_category(party, party_address, shipping_address) + out["tax_category"] = get_address_tax_category(party.get("tax_category"), + party_address, shipping_address if party_type != "Supplier" else party_address) out["taxes_and_charges"] = set_taxes(party.name, party_type, posting_date, company, out.customer_group, out.supplier_group, out.tax_category, party_address, shipping_address) @@ -355,8 +356,17 @@ def validate_due_date(posting_date, due_date, party_type, party, company=None, b frappe.throw(_("Due / Reference Date cannot be after {0}") .format(formatdate(default_due_date))) -def get_tax_category(party, billing_address, shipping_address): - return party.get("tax_category") +@frappe.whitelist() +def get_address_tax_category(tax_category, billing_address=None, shipping_address=None): + addr_tax_category_from = frappe.db.get_single_value("Accounts Settings", "determine_address_tax_category_from") + if addr_tax_category_from == "Shipping Address": + if shipping_address: + tax_category = frappe.db.get_value("Address", shipping_address, "tax_category") or tax_category + else: + if billing_address: + tax_category = frappe.db.get_value("Address", billing_address, "tax_category") or tax_category + + return cstr(tax_category) @frappe.whitelist() def set_taxes(party, party_type, posting_date, company, customer_group=None, supplier_group=None, tax_category=None, diff --git a/erpnext/public/js/controllers/buying.js b/erpnext/public/js/controllers/buying.js index cefdbe7a32..1e2b57488e 100644 --- a/erpnext/public/js/controllers/buying.js +++ b/erpnext/public/js/controllers/buying.js @@ -105,6 +105,7 @@ erpnext.buying.BuyingController = erpnext.TransactionController.extend({ supplier_address: function() { erpnext.utils.get_address_display(this.frm); + erpnext.utils.set_taxes_from_address(this.frm, "supplier_address", "supplier_address", "supplier_address"); }, buying_price_list: function() { diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index 744a8614ea..d95ca04584 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -452,9 +452,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ frappe.run_serially([ () => { var d = locals[cdt][cdn]; - if(d.item_tax_template && d.item_tax_rate) { - me.add_taxes_from_item_tax_template(d.item_tax_rate); - } + me.add_taxes_from_item_tax_template(d.item_tax_rate); }, () => me.frm.script_manager.trigger("price_list_rate", cdt, cdn), () => me.toggle_conversion_factor(item), @@ -482,7 +480,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ add_taxes_from_item_tax_template: function(item_tax_map) { let me = this; - if(cint(frappe.defaults.get_default("add_taxes_from_item_tax_template"))) { + if(item_tax_map && cint(frappe.defaults.get_default("add_taxes_from_item_tax_template"))) { if(typeof (item_tax_map) == "string") { item_tax_map = JSON.parse(item_tax_map); } @@ -1312,6 +1310,8 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ tax_category: function() { var me = this; + if(me.frm.updating_party_details) return; + var item_codes = []; $.each(this.frm.doc.items || [], function(i, item) { if(item.item_code) { @@ -1334,6 +1334,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ if(item.item_code && r.message.hasOwnProperty(item.item_code)) { item.item_tax_template = r.message[item.item_code].item_tax_template; item.item_tax_rate = r.message[item.item_code].item_tax_rate; + me.add_taxes_from_item_tax_template(item.item_tax_rate); } else { item.item_tax_template = ""; item.item_tax_rate = "{}"; @@ -1351,6 +1352,8 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ item_tax_template: function(doc, cdt, cdn) { var me = this; + if(me.frm.updating_party_details) return; + var item = frappe.get_doc(cdt, cdn); if(item.item_tax_template) { diff --git a/erpnext/public/js/utils/party.js b/erpnext/public/js/utils/party.js index 747f141c02..05269311e2 100644 --- a/erpnext/public/js/utils/party.js +++ b/erpnext/public/js/utils/party.js @@ -92,9 +92,6 @@ erpnext.utils.get_address_display = function(frm, address_field, display_field, if(r.message) { frm.set_value(display_field, r.message) } - if(frappe.meta.get_docfield(frm.doc.doctype, "taxes") && !is_your_company_address) { - erpnext.utils.set_taxes(frm, address_field); - } } }) } else { @@ -102,6 +99,42 @@ erpnext.utils.get_address_display = function(frm, address_field, display_field, } }; +erpnext.utils.set_taxes_from_address = function(frm, triggered_from_field, billing_address_field, shipping_address_field) { + if(frm.updating_party_details) return; + + if(frappe.meta.get_docfield(frm.doc.doctype, "taxes")) { + if(!erpnext.utils.validate_mandatory(frm, "Lead/Customer/Supplier", + frm.doc.customer || frm.doc.supplier || frm.doc.lead, triggered_from_field)) { + return; + } + + if(!erpnext.utils.validate_mandatory(frm, "Posting/Transaction Date", + frm.doc.posting_date || frm.doc.transaction_date, triggered_from_field)) { + return; + } + } else { + return; + } + + frappe.call({ + method: "erpnext.accounts.party.get_address_tax_category", + args: { + "tax_category": frm.doc.tax_category, + "billing_address": frm.doc[billing_address_field], + "shipping_address": frm.doc[shipping_address_field] + }, + callback: function(r) { + if(!r.exc){ + if(frm.doc.tax_category != r.message) { + frm.set_value("tax_category", r.message); + } else { + erpnext.utils.set_taxes(frm, triggered_from_field); + } + } + } + }); +}; + erpnext.utils.set_taxes = function(frm, triggered_from_field) { if(frappe.meta.get_docfield(frm.doc.doctype, "taxes")) { if(!erpnext.utils.validate_mandatory(frm, "Lead/Customer/Supplier", diff --git a/erpnext/selling/sales_common.js b/erpnext/selling/sales_common.js index c431df403a..3a3ba76eb4 100644 --- a/erpnext/selling/sales_common.js +++ b/erpnext/selling/sales_common.js @@ -103,10 +103,12 @@ erpnext.selling.SellingController = erpnext.TransactionController.extend({ customer_address: function() { erpnext.utils.get_address_display(this.frm, "customer_address"); + erpnext.utils.set_taxes_from_address(this.frm, "customer_address", "customer_address", "shipping_address_name"); }, shipping_address_name: function() { erpnext.utils.get_address_display(this.frm, "shipping_address_name", "shipping_address"); + erpnext.utils.set_taxes_from_address(this.frm, "shipping_address_name", "customer_address", "shipping_address_name"); }, sales_partner: function() {