refactor: Advance taxes and charges calculation

This commit is contained in:
Deepesh Garg 2021-06-08 16:35:47 +05:30
parent 6f2dacc60c
commit e4fcc5562f
3 changed files with 255 additions and 81 deletions

View File

@ -19,6 +19,7 @@
"section_break_8",
"rate",
"section_break_9",
"currency",
"tax_amount",
"total",
"allocated_amount",
@ -172,12 +173,20 @@
"fieldtype": "Currency",
"label": "Allocated Amount (Company Currency)",
"options": "Company:company:default_currency"
},
{
"fetch_from": "account_head.account_currency",
"fieldname": "currency",
"fieldtype": "Link",
"label": "Account Currency",
"options": "Currency",
"read_only": 1
}
],
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2021-05-31 02:03:57.444647",
"modified": "2021-06-08 07:04:44.287002",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Advance Taxes and Charges",

View File

@ -1071,7 +1071,7 @@ frappe.ui.form.on('Payment Entry', {
}
me.frm.add_child("taxes", tax);
}
frm.trigger('calculate_taxes');
frm.events.apply_taxes(frm);
frm.events.set_unallocated_amount(frm);
}
}
@ -1079,52 +1079,112 @@ frappe.ui.form.on('Payment Entry', {
});
},
apply_taxes: function(frm) {
frm.events.initialize_taxes(frm);
frm.events.determine_exclusive_rate(frm);
frm.events.calculate_taxes(frm);
},
initialize_taxes: function(frm) {
$.each(frm.doc["taxes"] || [], function(i, tax) {
tax.item_wise_tax_detail = {};
let tax_fields = ["total", "tax_fraction_for_current_item",
"grand_total_fraction_for_current_item"];
if (cstr(tax.charge_type) != "Actual") {
tax_fields.push("tax_amount");
}
$.each(tax_fields, function(i, fieldname) { tax[fieldname] = 0.0; });
frm.doc.paid_amount_after_tax = frm.doc.paid_amount;
});
},
determine_exclusive_rate: function(frm) {
let has_inclusive_tax = false;
$.each(frm.doc["taxes"] || [], function(i, row) {
if(cint(row.included_in_paid_amount)) has_inclusive_tax = true;
});
if(has_inclusive_tax==false) return;
let cumulated_tax_fraction = 0.0;
$.each(frm.doc["taxes"] || [], function(i, tax) {
let current_tax_fraction = frm.events.get_current_tax_fraction(frm, tax);
tax.tax_fraction_for_current_item = current_tax_fraction[0];
if(i==0) {
tax.grand_total_fraction_for_current_item = 1 + tax.tax_fraction_for_current_item;
} else {
tax.grand_total_fraction_for_current_item =
me.frm.doc["taxes"][i-1].grand_total_fraction_for_current_item +
tax.tax_fraction_for_current_item;
}
cumulated_tax_fraction += tax.tax_fraction_for_current_item;
frm.doc.paid_amount_after_tax = flt(frm.doc.paid_amount/(1+cumulated_tax_fraction))
});
},
get_current_tax_fraction: function(frm, tax) {
let current_tax_fraction = 0.0;
if(cint(tax.included_in_paid_amount)) {
let tax_rate = tax.rate;
if (tax.charge_type == "Actual") {
current_tax_fraction = tax.tax_amount/frm.doc.paid_amount_after_tax;
} else if(tax.charge_type == "On Paid Amount") {
current_tax_fraction = (tax_rate / 100.0);
} else if(tax.charge_type == "On Previous Row Amount") {
current_tax_fraction = (tax_rate / 100.0) *
frm.doc["taxes"][cint(tax.row_id) - 1].tax_fraction_for_current_item;
} else if(tax.charge_type == "On Previous Row Total") {
current_tax_fraction = (tax_rate / 100.0) *
frm.doc["taxes"][cint(tax.row_id) - 1].grand_total_fraction_for_current_item;
}
}
if(tax.add_deduct_tax && tax.add_deduct_tax == "Deduct") {
current_tax_fraction *= -1;
inclusive_tax_amount_per_qty *= -1;
}
return current_tax_fraction;
},
calculate_taxes: function(frm) {
frm.doc.total_taxes_and_charges = 0.0;
frm.doc.base_total_taxes_and_charges = 0.0;
$.each(me.frm.doc["taxes"] || [], function(i, tax) {
let tax_rate = tax.rate;
let current_tax_amount = 0.0;
let actual_tax_dict = {};
// To set row_id by default as previous row.
if(["On Previous Row Amount", "On Previous Row Total"].includes(tax.charge_type)) {
if (tax.idx === 1) {
frappe.throw(__("Cannot select charge type as 'On Previous Row Amount' or 'On Previous Row Total' for first row"));
}
if (!tax.row_id) {
tax.row_id = tax.idx - 1;
}
// maintain actual tax rate based on idx
$.each(frm.doc["taxes"] || [], function(i, tax) {
if (tax.charge_type == "Actual") {
actual_tax_dict[tax.idx] = flt(tax.tax_amount, precision("tax_amount", tax));
}
});
if(tax.charge_type == "Actual") {
current_tax_amount = flt(tax.tax_amount, precision("tax_amount", tax));
} else if(tax.charge_type == "On Paid Amount") {
current_tax_amount = (tax_rate / 100.0) * frm.doc.paid_amount;
} else if(tax.charge_type == "On Previous Row Amount") {
current_tax_amount = (tax_rate / 100.0) *
frm.doc["taxes"][cint(tax.row_id) - 1].tax_amount;
$.each(me.frm.doc["taxes"] || [], function(i, tax) {
let current_tax_amount = frm.events.get_current_tax_amount(frm, tax);
} else if(tax.charge_type == "On Previous Row Total") {
current_tax_amount = (tax_rate / 100.0) *
frm.doc["taxes"][cint(tax.row_id) - 1].total;
// Adjust divisional loss to the last item
if (tax.charge_type == "Actual") {
actual_tax_dict[tax.idx] -= current_tax_amount;
if (i == frm.doc["taxes"].length - 1) {
current_tax_amount += actual_tax_dict[tax.idx];
}
}
tax.tax_amount = current_tax_amount;
tax.base_tax_amount = tax.tax_amount * frm.doc.source_exchange_rate;
current_tax_amount *= (tax.add_deduct_tax == "Deduct") ? -1.0 : 1.0;
let applicable_tax_amount = 0
if (!tax.included_in_paid_amount) {
applicable_tax_amount = current_tax_amount
}
if(i==0) {
tax.total = flt(frm.doc.paid_amount + applicable_tax_amount, precision("total", tax));
tax.total = flt(frm.doc.paid_amount_after_tax + current_tax_amount, precision("total", tax));
} else {
tax.total = flt(frm.doc["taxes"][i-1].total + applicable_tax_amount, precision("total", tax));
tax.total = flt(frm.doc["taxes"][i-1].total + current_tax_amount, precision("total", tax));
}
tax.base_total = tax.total * frm.doc.source_exchange_rate;
@ -1136,6 +1196,36 @@ frappe.ui.form.on('Payment Entry', {
frm.refresh_field('base_total_taxes_and_charges');
});
},
get_current_tax_amount: function(frm, tax) {
let tax_rate = tax.rate;
let current_tax_amount = 0.0;
// To set row_id by default as previous row.
if(["On Previous Row Amount", "On Previous Row Total"].includes(tax.charge_type)) {
if (tax.idx === 1) {
frappe.throw(
__("Cannot select charge type as 'On Previous Row Amount' or 'On Previous Row Total' for first row"));
}
if (!tax.row_id) {
tax.row_id = tax.idx - 1;
}
}
if(tax.charge_type == "Actual") {
current_tax_amount = flt(tax.tax_amount, precision("tax_amount", tax))
} else if(tax.charge_type == "On Paid Amount") {
current_tax_amount = flt((tax_rate / 100.0) * frm.doc.paid_amount_after_tax);
} else if(tax.charge_type == "On Previous Row Amount") {
current_tax_amount = flt((tax_rate / 100.0) *
frm.doc["taxes"][cint(tax.row_id) - 1].tax_amount);
} else if(tax.charge_type == "On Previous Row Total") {
current_tax_amount = flt((tax_rate / 100.0) *
frm.doc["taxes"][cint(tax.row_id) - 1].total);
}
return current_tax_amount;
},
});
@ -1184,27 +1274,27 @@ frappe.ui.form.on('Payment Entry Reference', {
frappe.ui.form.on('Advance Taxes and Charges', {
rate: function(frm) {
frm.events.calculate_taxes(frm);
frm.events.apply_taxes(frm);
frm.events.set_unallocated_amount(frm);
},
tax_amount : function(frm) {
frm.events.calculate_taxes(frm);
frm.events.apply_taxes(frm);
frm.events.set_unallocated_amount(frm);
},
row_id: function(frm) {
frm.events.calculate_taxes(frm);
frm.events.apply_taxes(frm);
frm.events.set_unallocated_amount(frm);
},
taxes_remove: function(frm) {
frm.events.calculate_taxes(frm);
frm.events.apply_taxes(frm);
frm.events.set_unallocated_amount(frm);
},
included_in_paid_amount: function(frm) {
frm.events.calculate_taxes(frm);
frm.events.apply_taxes(frm);
frm.events.set_unallocated_amount(frm);
}
})

View File

@ -54,7 +54,7 @@ class PaymentEntry(AccountsController):
self.validate_mandatory()
self.validate_reference_documents()
self.set_tax_withholding()
self.calculate_taxes()
self.apply_taxes()
self.set_amounts()
self.clear_unallocated_reference_document_rows()
self.validate_payment_against_negative_invoice()
@ -65,7 +65,6 @@ class PaymentEntry(AccountsController):
self.validate_allocated_amount()
self.validate_paid_invoices()
self.ensure_supplier_is_not_blocked()
self.set_advance_tax_account()
self.set_status()
def on_submit(self):
@ -311,14 +310,6 @@ class PaymentEntry(AccountsController):
+ "<br><br>" + _("If this is undesirable please cancel the corresponding Payment Entry."),
title=_("Warning"), indicator="orange")
def set_advance_tax_account(self):
if self.get('taxes') and not self.advance_tax_account:
unrealized_profit_loss_account = frappe.db.get_value('Company', self.company, 'unrealized_profit_loss_account')
if not unrealized_profit_loss_account:
frappe.throw(_("Please select advance tax account or add Unrealized Profit / Loss Account in company master"))
self.advance_tax_account = unrealized_profit_loss_account
def validate_journal_entry(self):
for d in self.get("references"):
if d.allocated_amount and d.reference_doctype == "Journal Entry":
@ -461,6 +452,11 @@ class PaymentEntry(AccountsController):
for d in to_remove:
self.remove(d)
def apply_taxes(self):
self.initialize_taxes()
self.determine_exclusive_rate()
self.calculate_taxes()
def set_amounts(self):
self.set_received_amount()
self.set_amounts_in_company_currency()
@ -715,12 +711,12 @@ class PaymentEntry(AccountsController):
if account_currency != self.company_currency:
frappe.throw(_("Currency for {0} must be {1}").format(d.account_head, self.company_currency))
if self.payment_type == 'Pay':
if (self.payment_type == 'Pay' and self.advance_tax_account) or self.payment_type == 'Receive':
dr_or_cr = "debit" if d.add_deduct_tax == "Add" else "credit"
rev_dr_cr = "credit" if d.add_deduct_tax == "Add" else "debit"
elif self.payment_type == 'Receive':
elif (self.payment_type == 'Receive' and self.advance_tax_account) or self.payment_type == 'Pay':
dr_or_cr = "credit" if d.add_deduct_tax == "Add" else "debit"
rev_dr_cr = "debit" if d.add_deduct_tax == "Add" else "credit"
payment_or_advance_account = self.get_party_account_for_taxes()
gl_entries.append(
self.get_gl_dict({
@ -733,15 +729,16 @@ class PaymentEntry(AccountsController):
"cost_center": d.cost_center
}, account_currency, item=d))
#Intentionally use -1 to get net values in party account
gl_entries.append(
self.get_gl_dict({
"account": self.advance_tax_account,
"account": payment_or_advance_account,
"against": self.party if self.payment_type=="Receive" else self.paid_from,
rev_dr_cr: d.base_tax_amount,
rev_dr_cr + "_in_account_currency": d.base_tax_amount
dr_or_cr: -1 * d.base_tax_amount,
dr_or_cr + "_in_account_currency": -1*d.base_tax_amount
if account_currency==self.company_currency
else d.tax_amount,
"cost_center": d.cost_center or self.cost_center
"cost_center": self.cost_center,
}, account_currency, item=d))
def add_deductions_gl_entries(self, gl_entries):
@ -762,6 +759,14 @@ class PaymentEntry(AccountsController):
}, item=d)
)
def get_party_account_for_taxes(self):
if self.advance_tax_account:
return self.advance_tax_account
elif self.payment_type == 'Pay':
return self.paid_from
elif self.payment_type == 'Receive':
return self.paid_to
def update_advance_paid(self):
if self.payment_type in ("Receive", "Pay") and self.party:
for d in self.get("references"):
@ -808,32 +813,52 @@ class PaymentEntry(AccountsController):
self.append('deductions', row)
self.set_unallocated_amount()
def initialize_taxes(self):
for tax in self.get("taxes"):
tax_fields = ["total", "tax_fraction_for_current_item", "grand_total_fraction_for_current_item"]
if tax.charge_type != "Actual":
tax_fields.append("tax_amount")
for fieldname in tax_fields:
tax.set(fieldname, 0.0)
self.paid_amount_after_tax = self.paid_amount
def determine_exclusive_rate(self):
if not any((cint(tax.included_in_paid_amount) for tax in self.get("taxes"))):
return
cumulated_tax_fraction = 0
total_inclusive_tax_amount_per_qty = 0
for i, tax in enumerate(self.get("taxes")):
tax.tax_fraction_for_current_item = self.get_current_tax_fraction(tax)
if i==0:
tax.grand_total_fraction_for_current_item = 1 + tax.tax_fraction_for_current_item
else:
tax.grand_total_fraction_for_current_item = \
self.get("taxes")[i-1].grand_total_fraction_for_current_item \
+ tax.tax_fraction_for_current_item
cumulated_tax_fraction += tax.tax_fraction_for_current_item
self.paid_amount_after_tax = flt(self.paid_amount/(1+cumulated_tax_fraction))
def calculate_taxes(self):
self.total_taxes_and_charges = 0.0
self.base_total_taxes_and_charges = 0.0
actual_tax_dict = dict([[tax.idx, flt(tax.tax_amount, tax.precision("tax_amount"))]
for tax in self.get("taxes") if tax.charge_type == "Actual"])
for i, tax in enumerate(self.get('taxes')):
tax_rate = tax.rate
# To set row_id by default as previous row.
if tax.charge_type in ["On Previous Row Amount", "On Previous Row Total"]:
if tax.idx == 1:
frappe.throw(_("Cannot select charge type as 'On Previous Row Amount' or 'On Previous Row Total' for first row"))
if not tax.row_id:
tax.row_id = tax.idx - 1
current_tax_amount = self.get_current_tax_amount(tax)
if tax.charge_type == "Actual":
current_tax_amount = flt(tax.tax_amount, self.precision("tax_amount", tax))
elif tax.charge_type == "On Paid Amount":
current_tax_amount = (tax_rate / 100.0) * self.paid_amount
elif tax.charge_type == "On Previous Row Amount":
current_tax_amount = (tax_rate / 100.0) * \
self.get('taxes')[cint(tax.row_id) - 1].tax_amount
elif tax.charge_type == "On Previous Row Total":
current_tax_amount = (tax_rate / 100.0) * \
self.get('taxes')[cint(tax.row_id) - 1].total
actual_tax_dict[tax.idx] -= current_tax_amount
if i == len(self.get("taxes")) - 1:
current_tax_amount += actual_tax_dict[tax.idx]
tax.tax_amount = current_tax_amount
tax.base_tax_amount = tax.tax_amount * self.source_exchange_rate
@ -843,21 +868,68 @@ class PaymentEntry(AccountsController):
else:
current_tax_amount *= 1.0
if not tax.included_in_paid_amount:
applicable_tax = current_tax_amount
else:
applicable_tax = 0
if i == 0:
tax.total = flt(self.paid_amount + applicable_tax, self.precision("total", tax))
tax.total = flt(self.paid_amount_after_tax + current_tax_amount, self.precision("total", tax))
else:
tax.total = flt(self.get('taxes')[i-1].total + applicable_tax, self.precision("total", tax))
tax.total = flt(self.get('taxes')[i-1].total + current_tax_amount, self.precision("total", tax))
tax.base_total = tax.total * self.source_exchange_rate
self.total_taxes_and_charges += current_tax_amount
self.base_total_taxes_and_charges += current_tax_amount * self.source_exchange_rate
if self.get('taxes'):
self.paid_amount_after_tax = self.get('taxes')[-1].base_total
def get_current_tax_amount(self, tax):
tax_rate = tax.rate
# To set row_id by default as previous row.
if tax.charge_type in ["On Previous Row Amount", "On Previous Row Total"]:
if tax.idx == 1:
frappe.throw(_("Cannot select charge type as 'On Previous Row Amount' or 'On Previous Row Total' for first row"))
if not tax.row_id:
tax.row_id = tax.idx - 1
if tax.charge_type == "Actual":
current_tax_amount = flt(tax.tax_amount, self.precision("tax_amount", tax))
elif tax.charge_type == "On Paid Amount":
current_tax_amount = (tax_rate / 100.0) * self.paid_amount_after_tax
elif tax.charge_type == "On Previous Row Amount":
current_tax_amount = (tax_rate / 100.0) * \
self.get('taxes')[cint(tax.row_id) - 1].tax_amount
elif tax.charge_type == "On Previous Row Total":
current_tax_amount = (tax_rate / 100.0) * \
self.get('taxes')[cint(tax.row_id) - 1].total
return current_tax_amount
def get_current_tax_fraction(self, tax):
current_tax_fraction = 0
if cint(tax.included_in_paid_amount):
tax_rate = tax.rate
if tax.charge_type == 'Actual':
current_tax_fraction = tax.tax_amount/self.paid_amount_after_tax
elif tax.charge_type == "On Paid Amount":
current_tax_fraction = tax_rate / 100.0
elif tax.charge_type == "On Previous Row Amount":
current_tax_fraction = (tax_rate / 100.0) * \
self.get("taxes")[cint(tax.row_id) - 1].tax_fraction_for_current_item
elif tax.charge_type == "On Previous Row Total":
current_tax_fraction = (tax_rate / 100.0) * \
self.get("taxes")[cint(tax.row_id) - 1].grand_total_fraction_for_current_item
if getattr(tax, "add_deduct_tax", None) and tax.add_deduct_tax == "Deduct":
current_tax_fraction *= -1.0
return current_tax_fraction
@frappe.whitelist()
def get_outstanding_reference_documents(args):
@ -1426,6 +1498,9 @@ def get_payment_entry(dt, dn, party_amount=None, bank_account=None, bank_amount=
pe.apply_tax_withholding_amount = 1
pe.tax_withholding_category = doc.tax_withholding_category
if not pe.advance_tax_account:
pe.advance_tax_account = frappe.db.get_value('Company', pe.company, 'unrealized_profit_loss_account')
return pe
def get_bank_cash_account(doc, bank_account):