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'):
item_doc = frappe.get_cached_doc("Item", item.item_code)
args = {
'net_rate': item.net_rate or item.rate,
'tax_category': self.doc.get('tax_category'),
'posting_date': self.doc.get('posting_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)
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)
))

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) {
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)
+ flt(effective_item_rate) * ( flt(item.margin_rate_or_amount) / 100);
} else {
@ -73,15 +73,18 @@ erpnext.taxes_and_totals = erpnext.payments.extend({
},
_calculate_taxes_and_totals: function() {
this.validate_conversion_rate();
this.calculate_item_values();
this.initialize_taxes();
this.determine_exclusive_rate();
this.calculate_net_total();
this.calculate_taxes();
this.manipulate_grand_total_for_inclusive_tax();
this.calculate_totals();
this._cleanup();
frappe.run_serially([
() => this.validate_conversion_rate(),
() => this.calculate_item_values(),
() => this.update_item_tax_map(),
() => this.initialize_taxes(),
() => this.determine_exclusive_rate(),
() => this.calculate_net_total(),
() => this.calculate_taxes(),
() => this.manipulate_grand_total_for_inclusive_tax(),
() => this.calculate_totals(),
() => this._cleanup()
]);
},
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"]);
},
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() {
var me = this;
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 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;
if (tax_detail && tax_detail[key])
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({
setup: function() {
this._super();
let me = this;
frappe.flags.hide_serial_batch_dialog = true;
frappe.ui.form.on(this.frm.doctype + " Item", "rate", function(frm, 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);
});
frappe.ui.form.on(this.frm.cscript.tax_table, "rate", function(frm, cdt, cdn) {
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')) {
this.frm.set_query("batch_no", "items", function(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,
project: item.project || me.frm.doc.project,
qty: item.qty || 1,
net_rate: item.rate,
stock_qty: item.stock_qty,
conversion_factor: item.conversion_factor,
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) {
var me = this;
var item = frappe.get_doc(cdt, cdn);
@ -835,9 +814,9 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
frappe.run_serially([
() => me.frm.script_manager.trigger("currency"),
() => me.update_item_tax_map(),
() => 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) {
if(!r.exc) {
item.item_tax_rate = r.message;
me.add_taxes_from_item_tax_template(item.item_tax_rate);
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() {
// set default values for recurring documents

View File

@ -128,6 +128,7 @@ class Item(WebsiteGenerator):
self.validate_auto_reorder_enabled_in_stock_settings()
self.cant_change()
self.update_show_in_website()
self.validate_item_tax_net_rate_range()
if not self.get("__islocal"):
self.old_item_group = frappe.db.get_value(self.doctype, self.name, "item_group")
@ -490,6 +491,11 @@ class Item(WebsiteGenerator):
if self.disabled:
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):
template = frappe.get_doc("Item", self.variant_of)

View File

@ -1,4 +1,5 @@
{
"actions": [],
"creation": "2013-02-22 01:28:01",
"doctype": "DocType",
"editable_grid": 1,
@ -6,7 +7,9 @@
"field_order": [
"item_tax_template",
"tax_category",
"valid_from"
"valid_from",
"minimum_net_rate",
"maximum_net_rate"
],
"fields": [
{
@ -33,11 +36,24 @@
"fieldtype": "Date",
"in_list_view": 1,
"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,
"istable": 1,
"modified": "2020-06-25 01:40:28.859752",
"links": [],
"modified": "2021-06-03 13:20:06.982303",
"modified_by": "Administrator",
"module": "Stock",
"name": "Item Tax",

View File

@ -436,18 +436,22 @@ def get_barcode_data(items_list):
return itemwise_barcode
@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 = {}
if isinstance(item_codes, string_types):
item_codes = json.loads(item_codes)
if isinstance(item_rates, string_types):
item_rates = json.loads(item_rates)
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
out[item_code] = {}
item = frappe.get_cached_doc("Item", item_code)
get_item_tax_template({"company": company, "tax_category": tax_category}, item, out[item_code])
out[item_code]["item_tax_rate"] = get_item_tax_map(company, out[item_code].get("item_tax_template"), as_json=True)
out[item_code[1]] = {}
item = frappe.get_cached_doc("Item", item_code[0])
args = {"company": company, "tax_category": tax_category, "net_rate": item_rates[item_code[1]]}
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
@ -478,12 +482,13 @@ def _get_item_tax_template(args, taxes, out=None, for_validate=False):
for tax in taxes:
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
# 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')
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)
else:
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):
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:
if cstr(tax.tax_category) == cstr(args.get("tax_category")):
out["item_tax_template"] = tax.item_tax_template
return tax.item_tax_template
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()
def get_item_tax_map(company, item_tax_template, as_json=True):
item_tax_map = {}