feat: Item Taxes based on net rate

This commit is contained in:
Deepesh Garg
2021-06-04 22:53:26 +05:30
parent 865663857c
commit 8a7e283926
6 changed files with 137 additions and 83 deletions

View File

@@ -54,6 +54,7 @@ class calculate_taxes_and_totals(object):
if item.item_code and item.get('item_tax_template'): if item.item_code and item.get('item_tax_template'):
item_doc = frappe.get_cached_doc("Item", item.item_code) item_doc = frappe.get_cached_doc("Item", item.item_code)
args = { args = {
'net_rate': item.net_rate or item.rate,
'tax_category': self.doc.get('tax_category'), 'tax_category': self.doc.get('tax_category'),
'posting_date': self.doc.get('posting_date'), 'posting_date': self.doc.get('posting_date'),
'bill_date': self.doc.get('bill_date'), 'bill_date': self.doc.get('bill_date'),
@@ -78,7 +79,8 @@ class calculate_taxes_and_totals(object):
taxes = _get_item_tax_template(args, item_taxes + item_group_taxes, for_validate=True) taxes = _get_item_tax_template(args, item_taxes + item_group_taxes, for_validate=True)
if item.item_tax_template not in taxes: if item.item_tax_template not in taxes:
frappe.throw(_("Row {0}: Invalid Item Tax Template for item {1}").format( item.item_tax_template = taxes[0]
frappe.msgprint(_("Row {0}: Item Tax template updated as per validity and rate applied").format(
item.idx, frappe.bold(item.item_code) item.idx, frappe.bold(item.item_code)
)) ))

View File

@@ -12,7 +12,7 @@ erpnext.taxes_and_totals = erpnext.payments.extend({
if (in_list(["Sales Order", "Quotation"], item.parenttype) && item.blanket_order_rate) { if (in_list(["Sales Order", "Quotation"], item.parenttype) && item.blanket_order_rate) {
effective_item_rate = item.blanket_order_rate; effective_item_rate = item.blanket_order_rate;
} }
if(item.margin_type == "Percentage"){ if(item.margin_type == "Percentage") {
item.rate_with_margin = flt(effective_item_rate) item.rate_with_margin = flt(effective_item_rate)
+ flt(effective_item_rate) * ( flt(item.margin_rate_or_amount) / 100); + flt(effective_item_rate) * ( flt(item.margin_rate_or_amount) / 100);
} else { } else {
@@ -73,15 +73,18 @@ erpnext.taxes_and_totals = erpnext.payments.extend({
}, },
_calculate_taxes_and_totals: function() { _calculate_taxes_and_totals: function() {
this.validate_conversion_rate(); frappe.run_serially([
this.calculate_item_values(); () => this.validate_conversion_rate(),
this.initialize_taxes(); () => this.calculate_item_values(),
this.determine_exclusive_rate(); () => this.update_item_tax_map(),
this.calculate_net_total(); () => this.initialize_taxes(),
this.calculate_taxes(); () => this.determine_exclusive_rate(),
this.manipulate_grand_total_for_inclusive_tax(); () => this.calculate_net_total(),
this.calculate_totals(); () => this.calculate_taxes(),
this._cleanup(); () => this.manipulate_grand_total_for_inclusive_tax(),
() => this.calculate_totals(),
() => this._cleanup()
]);
}, },
validate_conversion_rate: function() { validate_conversion_rate: function() {
@@ -263,6 +266,68 @@ erpnext.taxes_and_totals = erpnext.payments.extend({
frappe.model.round_floats_in(this.frm.doc, ["total", "base_total", "net_total", "base_net_total"]); frappe.model.round_floats_in(this.frm.doc, ["total", "base_total", "net_total", "base_net_total"]);
}, },
update_item_tax_map: function() {
let me = this;
let item_codes = [];
let item_rates = {};
$.each(this.frm.doc.items || [], function(i, item) {
if(item.item_code) {
// Use combination of name and item code in case same item is added multiple times
item_codes.push([item.item_code, item.name]);
item_rates[item.name] = item.net_rate;
}
});
if(item_codes.length) {
return this.frm.call({
method: "erpnext.stock.get_item_details.get_item_tax_info",
args: {
company: me.frm.doc.company,
tax_category: cstr(me.frm.doc.tax_category),
item_codes: item_codes,
item_rates: item_rates
},
callback: function(r) {
if(!r.exc) {
$.each(me.frm.doc.items || [], function(i, item) {
if(item.name && r.message.hasOwnProperty(item.name)) {
item.item_tax_template = r.message[item.name].item_tax_template;
item.item_tax_rate = r.message[item.name].item_tax_rate;
me.add_taxes_from_item_tax_template(item.item_tax_rate);
}
else {
item.item_tax_template = "";
item.item_tax_rate = "{}";
}
});
}
this.frm.refresh_fields();
}
});
}
},
add_taxes_from_item_tax_template: function(item_tax_map) {
let me = this;
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);
}
$.each(item_tax_map, function(tax, rate) {
let found = (me.frm.doc.taxes || []).find(d => d.account_head === tax);
if(!found) {
let child = frappe.model.add_child(me.frm.doc, "taxes");
child.charge_type = "On Net Total";
child.account_head = tax;
child.rate = 0;
}
});
}
},
calculate_taxes: function() { calculate_taxes: function() {
var me = this; var me = this;
this.frm.doc.rounding_adjustment = 0; this.frm.doc.rounding_adjustment = 0;
@@ -406,6 +471,11 @@ erpnext.taxes_and_totals = erpnext.payments.extend({
let tax_detail = tax.item_wise_tax_detail; let tax_detail = tax.item_wise_tax_detail;
let key = item.item_code || item.item_name; let key = item.item_code || item.item_name;
if(typeof (tax_detail) == "string") {
tax.item_wise_tax_detail = JSON.parse(tax.item_wise_tax_detail);
tax_detail = tax.item_wise_tax_detail;
}
let item_wise_tax_amount = current_tax_amount * this.frm.doc.conversion_rate; let item_wise_tax_amount = current_tax_amount * this.frm.doc.conversion_rate;
if (tax_detail && tax_detail[key]) if (tax_detail && tax_detail[key])
item_wise_tax_amount += tax_detail[key][1]; item_wise_tax_amount += tax_detail[key][1];

View File

@@ -6,6 +6,7 @@ frappe.provide('erpnext.accounts.dimensions');
erpnext.TransactionController = erpnext.taxes_and_totals.extend({ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
setup: function() { setup: function() {
this._super(); this._super();
let me = this;
frappe.flags.hide_serial_batch_dialog = true; frappe.flags.hide_serial_batch_dialog = true;
frappe.ui.form.on(this.frm.doctype + " Item", "rate", function(frm, cdt, cdn) { frappe.ui.form.on(this.frm.doctype + " Item", "rate", function(frm, cdt, cdn) {
var item = frappe.get_doc(cdt, cdn); var item = frappe.get_doc(cdt, cdn);
@@ -43,8 +44,6 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
cur_frm.cscript.calculate_stock_uom_rate(frm, cdt, cdn); cur_frm.cscript.calculate_stock_uom_rate(frm, cdt, cdn);
}); });
frappe.ui.form.on(this.frm.cscript.tax_table, "rate", function(frm, cdt, cdn) { frappe.ui.form.on(this.frm.cscript.tax_table, "rate", function(frm, cdt, cdn) {
cur_frm.cscript.calculate_taxes_and_totals(); cur_frm.cscript.calculate_taxes_and_totals();
}); });
@@ -121,7 +120,6 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
} }
}); });
var me = this;
if(this.frm.fields_dict["items"].grid.get_field('batch_no')) { if(this.frm.fields_dict["items"].grid.get_field('batch_no')) {
this.frm.set_query("batch_no", "items", function(doc, cdt, cdn) { this.frm.set_query("batch_no", "items", function(doc, cdt, cdn) {
return me.set_query_for_batch(doc, cdt, cdn); return me.set_query_for_batch(doc, cdt, cdn);
@@ -556,6 +554,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
name: me.frm.doc.name, name: me.frm.doc.name,
project: item.project || me.frm.doc.project, project: item.project || me.frm.doc.project,
qty: item.qty || 1, qty: item.qty || 1,
net_rate: item.rate,
stock_qty: item.stock_qty, stock_qty: item.stock_qty,
conversion_factor: item.conversion_factor, conversion_factor: item.conversion_factor,
weight_per_unit: item.weight_per_unit, weight_per_unit: item.weight_per_unit,
@@ -712,26 +711,6 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
}); });
}, },
add_taxes_from_item_tax_template: function(item_tax_map) {
let me = this;
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);
}
$.each(item_tax_map, function(tax, rate) {
let found = (me.frm.doc.taxes || []).find(d => d.account_head === tax);
if(!found) {
let child = frappe.model.add_child(me.frm.doc, "taxes");
child.charge_type = "On Net Total";
child.account_head = tax;
child.rate = 0;
}
});
}
},
serial_no: function(doc, cdt, cdn) { serial_no: function(doc, cdt, cdn) {
var me = this; var me = this;
var item = frappe.get_doc(cdt, cdn); var item = frappe.get_doc(cdt, cdn);
@@ -835,9 +814,9 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
frappe.run_serially([ frappe.run_serially([
() => me.frm.script_manager.trigger("currency"), () => me.frm.script_manager.trigger("currency"),
() => me.update_item_tax_map(),
() => me.apply_default_taxes(), () => me.apply_default_taxes(),
() => me.apply_pricing_rule() () => me.apply_pricing_rule(),
() => me.calculate_taxes_and_totals()
]); ]);
} }
} }
@@ -1816,7 +1795,6 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
callback: function(r) { callback: function(r) {
if(!r.exc) { if(!r.exc) {
item.item_tax_rate = r.message; item.item_tax_rate = r.message;
me.add_taxes_from_item_tax_template(item.item_tax_rate);
me.calculate_taxes_and_totals(); me.calculate_taxes_and_totals();
} }
} }
@@ -1827,43 +1805,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
} }
}, },
update_item_tax_map: function() {
var me = this;
var item_codes = [];
$.each(this.frm.doc.items || [], function(i, item) {
if(item.item_code) {
item_codes.push(item.item_code);
}
});
if(item_codes.length) {
return this.frm.call({
method: "erpnext.stock.get_item_details.get_item_tax_info",
args: {
company: me.frm.doc.company,
tax_category: cstr(me.frm.doc.tax_category),
item_codes: item_codes
},
callback: function(r) {
if(!r.exc) {
$.each(me.frm.doc.items || [], function(i, item) {
if(item.item_code && r.message.hasOwnProperty(item.item_code)) {
if (!item.item_tax_template) {
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 = "{}";
}
});
me.calculate_taxes_and_totals();
}
}
});
}
},
is_recurring: function() { is_recurring: function() {
// set default values for recurring documents // set default values for recurring documents

View File

@@ -128,6 +128,7 @@ class Item(WebsiteGenerator):
self.validate_auto_reorder_enabled_in_stock_settings() self.validate_auto_reorder_enabled_in_stock_settings()
self.cant_change() self.cant_change()
self.update_show_in_website() self.update_show_in_website()
self.validate_item_tax_net_rate_range()
if not self.get("__islocal"): if not self.get("__islocal"):
self.old_item_group = frappe.db.get_value(self.doctype, self.name, "item_group") self.old_item_group = frappe.db.get_value(self.doctype, self.name, "item_group")
@@ -490,6 +491,11 @@ class Item(WebsiteGenerator):
if self.disabled: if self.disabled:
self.show_in_website = False self.show_in_website = False
def validate_item_tax_net_rate_range(self):
for tax in self.get('taxes'):
if tax.maximum_net_rate < tax.minimum_net_rate:
frappe.throw(_("Row #{0}: Maximum Net Rate cannot be greater than Minimum Net Rate"))
def update_template_tables(self): def update_template_tables(self):
template = frappe.get_doc("Item", self.variant_of) template = frappe.get_doc("Item", self.variant_of)

View File

@@ -1,4 +1,5 @@
{ {
"actions": [],
"creation": "2013-02-22 01:28:01", "creation": "2013-02-22 01:28:01",
"doctype": "DocType", "doctype": "DocType",
"editable_grid": 1, "editable_grid": 1,
@@ -6,7 +7,9 @@
"field_order": [ "field_order": [
"item_tax_template", "item_tax_template",
"tax_category", "tax_category",
"valid_from" "valid_from",
"minimum_net_rate",
"maximum_net_rate"
], ],
"fields": [ "fields": [
{ {
@@ -33,11 +36,24 @@
"fieldtype": "Date", "fieldtype": "Date",
"in_list_view": 1, "in_list_view": 1,
"label": "Valid From" "label": "Valid From"
},
{
"fieldname": "maximum_net_rate",
"fieldtype": "Float",
"in_list_view": 1,
"label": "Maximum Net Rate"
},
{
"fieldname": "minimum_net_rate",
"fieldtype": "Float",
"in_list_view": 1,
"label": "Minimum Net Rate"
} }
], ],
"idx": 1, "idx": 1,
"istable": 1, "istable": 1,
"modified": "2020-06-25 01:40:28.859752", "links": [],
"modified": "2021-06-03 13:20:06.982303",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Stock", "module": "Stock",
"name": "Item Tax", "name": "Item Tax",

View File

@@ -436,18 +436,22 @@ def get_barcode_data(items_list):
return itemwise_barcode return itemwise_barcode
@frappe.whitelist() @frappe.whitelist()
def get_item_tax_info(company, tax_category, item_codes): def get_item_tax_info(company, tax_category, item_codes, item_rates=None):
out = {} out = {}
if isinstance(item_codes, string_types): if isinstance(item_codes, string_types):
item_codes = json.loads(item_codes) item_codes = json.loads(item_codes)
if isinstance(item_rates, string_types):
item_rates = json.loads(item_rates)
for item_code in item_codes: for item_code in item_codes:
if not item_code or item_code in out: if not item_code or item_code[1] in out:
continue continue
out[item_code] = {} out[item_code[1]] = {}
item = frappe.get_cached_doc("Item", item_code) item = frappe.get_cached_doc("Item", item_code[0])
get_item_tax_template({"company": company, "tax_category": tax_category}, item, out[item_code]) args = {"company": company, "tax_category": tax_category, "net_rate": item_rates[item_code[1]]}
out[item_code]["item_tax_rate"] = get_item_tax_map(company, out[item_code].get("item_tax_template"), as_json=True) get_item_tax_template(args, 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)
return out return out
@@ -478,12 +482,13 @@ def _get_item_tax_template(args, taxes, out=None, for_validate=False):
for tax in taxes: for tax in taxes:
tax_company = frappe.get_value("Item Tax Template", tax.item_tax_template, 'company') tax_company = frappe.get_value("Item Tax Template", tax.item_tax_template, 'company')
if tax.valid_from and tax_company == args['company']: if (tax.valid_from or tax.maximum_net_rate) and tax_company == args['company']:
# In purchase Invoice first preference will be given to supplier invoice date # In purchase Invoice first preference will be given to supplier invoice date
# if supplier date is not present then posting date # if supplier date is not present then posting date
validation_date = args.get('transaction_date') or args.get('bill_date') or args.get('posting_date') validation_date = args.get('transaction_date') or args.get('bill_date') or args.get('posting_date')
if getdate(tax.valid_from) <= getdate(validation_date): if getdate(tax.valid_from) <= getdate(validation_date) \
and is_within_valid_range(args, tax):
taxes_with_validity.append(tax) taxes_with_validity.append(tax)
else: else:
if tax_company == args['company']: if tax_company == args['company']:
@@ -502,12 +507,25 @@ def _get_item_tax_template(args, taxes, out=None, for_validate=False):
if not taxes_with_validity and (not taxes_with_no_validity): if not taxes_with_validity and (not taxes_with_no_validity):
return None return None
# do not change if already a valid template
if args.get('item_tax_template') in taxes:
return arg.get('item_tax_template')
for tax in taxes: for tax in taxes:
if cstr(tax.tax_category) == cstr(args.get("tax_category")): if cstr(tax.tax_category) == cstr(args.get("tax_category")):
out["item_tax_template"] = tax.item_tax_template out["item_tax_template"] = tax.item_tax_template
return tax.item_tax_template return tax.item_tax_template
return None return None
def is_within_valid_range(args, tax):
if not flt(tax.maximum_net_rate):
# No range specified, just ignore
return True
elif flt(tax.minimum_net_rate) <= flt(args.get('net_rate')) <= flt(tax.maximum_net_rate):
return True
return False
@frappe.whitelist() @frappe.whitelist()
def get_item_tax_map(company, item_tax_template, as_json=True): def get_item_tax_map(company, item_tax_template, as_json=True):
item_tax_map = {} item_tax_map = {}