From ac9a4fe03c40409af44f2f101d75968991a5f579 Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Tue, 11 Jun 2019 14:25:44 +0530 Subject: [PATCH 1/8] feat: add tax and charges in expense claim --- .../hr/doctype/expense_claim/expense_claim.js | 62 +++++++++++ .../doctype/expense_claim/expense_claim.json | 22 +++- .../hr/doctype/expense_claim/expense_claim.py | 33 +++++- .../expense_taxes_and_charges/__init__.py | 0 .../expense_taxes_and_charges.json | 103 ++++++++++++++++++ .../expense_taxes_and_charges.py | 10 ++ 6 files changed, 227 insertions(+), 3 deletions(-) create mode 100644 erpnext/hr/doctype/expense_taxes_and_charges/__init__.py create mode 100644 erpnext/hr/doctype/expense_taxes_and_charges/expense_taxes_and_charges.json create mode 100644 erpnext/hr/doctype/expense_taxes_and_charges/expense_taxes_and_charges.py diff --git a/erpnext/hr/doctype/expense_claim/expense_claim.js b/erpnext/hr/doctype/expense_claim/expense_claim.js index 7c6abc7f94..0ff70cb680 100644 --- a/erpnext/hr/doctype/expense_claim/expense_claim.js +++ b/erpnext/hr/doctype/expense_claim/expense_claim.js @@ -157,6 +157,14 @@ frappe.ui.form.on("Expense Claim", { } }; }); + // frm.set_query("taxes", "account_head", function(doc) { + // return { + // filters: [ + // ['docstatus', '=', 1], + // ['company', '=', doc.company] + // ] + // }; + // }); }, onload: function(frm) { @@ -259,6 +267,18 @@ frappe.ui.form.on("Expense Claim", { frm.events.get_advances(frm); }, + get_taxes: function(frm) { + if(frm.doc.taxes) { + frappe.call({ + method: "calculate_taxes", + doc: frm.doc, + callback: (r) => { + refresh_field("taxes"); + } + }); + } + }, + get_advances: function(frm) { frappe.model.clear_table(frm.doc, "advances"); if (frm.doc.employee) { @@ -298,6 +318,7 @@ frappe.ui.form.on("Expense Claim Detail", { sanctioned_amount: function(frm, cdt, cdn) { var doc = frm.doc; cur_frm.cscript.calculate_total(doc,cdt,cdn); + frm.trigger("get_taxes"); } }); @@ -332,6 +353,47 @@ frappe.ui.form.on("Expense Claim Advance", { } }); +frappe.ui.form.on("Expense Taxes and Charges", { + account_head: function(frm, cdt, cdn) { + var child = locals[cdt][cdn]; + if(child.account_head && !child.description && !child.rate) { + // set description from account head + child.description = child.account_head.split(' - ').slice(0, -1).join(' - '); + + // set the tax rate from account head + frappe.db.get_value("Account", child.account_head, "tax_rate").then((r) => { + if(r.message) { + frappe.model.set_value(cdt, cdn, 'rate', r.message.tax_rate); + } + }); + refresh_field("taxes"); + } + }, + + calculate_total: function(frm, cdt, cdn) { + var child = locals[cdt][cdn]; + child.total = flt(frm.doc.total_sanctioned_amount) + flt(child.tax_amount); + + refresh_field("taxes"); + }, + + rate: function(frm, cdt, cdn) { + var child = locals[cdt][cdn]; + if(!child.amount) { + child.tax_amount = flt(frm.doc.total_sanctioned_amount) * (flt(child.rate)/100); + refresh_field("taxes"); + } + frm.trigger("calculate_total", cdt, cdn) + }, + + tax_amount: function(frm, cdt, cdn) { + var child = locals[cdt][cdn]; + child.rate = flt(child.tax_amount/frm.doc.total_sanctioned_amount) * 100; + frm.trigger("calculate_total", cdt, cdn) + refresh_field("taxes"); + } +}); + cur_frm.fields_dict['task'].get_query = function(doc) { return { filters:{ diff --git a/erpnext/hr/doctype/expense_claim/expense_claim.json b/erpnext/hr/doctype/expense_claim/expense_claim.json index 6e04644036..007e646815 100644 --- a/erpnext/hr/doctype/expense_claim/expense_claim.json +++ b/erpnext/hr/doctype/expense_claim/expense_claim.json @@ -4,6 +4,7 @@ "creation": "2013-01-10 16:34:14", "doctype": "DocType", "document_type": "Setup", + "engine": "InnoDB", "field_order": [ "naming_series", "employee", @@ -18,6 +19,9 @@ "expense_details", "expenses", "sb1", + "taxes", + "net_total", + "section_break_16", "posting_date", "vehicle_log", "task", @@ -315,12 +319,28 @@ { "fieldname": "dimension_col_break", "fieldtype": "Column Break" + }, + { + "fieldname": "taxes", + "fieldtype": "Table", + "label": "Expense Taxes and Charges", + "options": "Expense Taxes and Charges" + }, + { + "fieldname": "section_break_16", + "fieldtype": "Section Break" + }, + { + "fieldname": "net_total", + "fieldtype": "Currency", + "label": "Net Total", + "read_only": 1 } ], "icon": "fa fa-money", "idx": 1, "is_submittable": 1, - "modified": "2019-05-25 22:53:31.682151", + "modified": "2019-06-11 13:21:42.386420", "modified_by": "Administrator", "module": "HR", "name": "Expense Claim", diff --git a/erpnext/hr/doctype/expense_claim/expense_claim.py b/erpnext/hr/doctype/expense_claim/expense_claim.py index d6b0eca70e..c5b6ebe56b 100644 --- a/erpnext/hr/doctype/expense_claim/expense_claim.py +++ b/erpnext/hr/doctype/expense_claim/expense_claim.py @@ -12,6 +12,7 @@ from erpnext.accounts.general_ledger import make_gl_entries from erpnext.accounts.doctype.sales_invoice.sales_invoice import get_bank_cash_account from erpnext.controllers.accounts_controller import AccountsController from frappe.utils.csvutils import getlink +from erpnext.accounts.utils import get_account_currency class InvalidExpenseApproverError(frappe.ValidationError): pass class ExpenseApproverIdentityError(frappe.ValidationError): pass @@ -29,6 +30,7 @@ class ExpenseClaim(AccountsController): self.set_expense_account(validate=True) self.set_payable_account() self.set_cost_center() + self.calculate_taxes() self.set_status() if self.task and not self.project: self.project = frappe.db.get_value("Task", self.task, "project") @@ -93,7 +95,7 @@ class ExpenseClaim(AccountsController): elif self.project: frappe.get_doc("Project", self.project).update_project() - def make_gl_entries(self, cancel = False): + def make_gl_entries(self, cancel=False): if flt(self.total_sanctioned_amount) > 0: gl_entries = self.get_gl_entries() make_gl_entries(gl_entries, cancel) @@ -102,7 +104,7 @@ class ExpenseClaim(AccountsController): gl_entry = [] self.validate_account_details() - payable_amount = flt(self.total_sanctioned_amount) - flt(self.total_advance_amount) + payable_amount = flt(self.net_total) - flt(self.total_advance_amount) # payable entry if payable_amount: @@ -170,8 +172,26 @@ class ExpenseClaim(AccountsController): }) ) + gl_entry = self.make_tax_gl_entries(gl_entry) + return gl_entry + def make_tax_gl_entries(self, gl_entries): + # tax table gl entries + for tax in self.get("taxes"): + account_currency = get_account_currency(tax.account_head) + gl_entries.append( + self.get_gl_dict({ + "account": tax.account_head, + "debit": tax.tax_amount, + "against": self.employee, + "cost_center": self.cost_center, + "against_voucher_type": self.doctype, + "against_voucher": self.name + }, account_currency) + ) + return gl_entries + def validate_account_details(self): if not self.cost_center: frappe.throw(_("Cost center is required to book an expense claim")) @@ -193,6 +213,15 @@ class ExpenseClaim(AccountsController): self.total_claimed_amount += flt(d.claim_amount) self.total_sanctioned_amount += flt(d.sanctioned_amount) + def calculate_taxes(self): + for tax in self.taxes: + if tax.rate: + tax.tax_amount = flt(self.total_sanctioned_amount) * flt(tax.rate/100) + if tax.tax_amount: + tax.rate = flt(tax.tax_amount)/flt(self.total_sanctioned_amount) * 100 + tax.total = flt(tax.tax_amount) + flt(self.total_sanctioned_amount) + self.net_total += tax.total + def update_task(self): task = frappe.get_doc("Task", self.task) task.update_total_expense_claim() diff --git a/erpnext/hr/doctype/expense_taxes_and_charges/__init__.py b/erpnext/hr/doctype/expense_taxes_and_charges/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/hr/doctype/expense_taxes_and_charges/expense_taxes_and_charges.json b/erpnext/hr/doctype/expense_taxes_and_charges/expense_taxes_and_charges.json new file mode 100644 index 0000000000..8caf0a975a --- /dev/null +++ b/erpnext/hr/doctype/expense_taxes_and_charges/expense_taxes_and_charges.json @@ -0,0 +1,103 @@ +{ + "autoname": "hash", + "creation": "2019-06-03 11:42:33.123976", + "doctype": "DocType", + "document_type": "Setup", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "account_head", + "cost_center", + "col_break1", + "rate", + "description", + "section_break_6", + "tax_amount", + "column_break_8", + "total" + ], + "fields": [ + { + "fieldname": "col_break1", + "fieldtype": "Column Break" + }, + { + "columns": 2, + "fieldname": "account_head", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Account Head", + "oldfieldname": "account_head", + "oldfieldtype": "Link", + "options": "Account", + "reqd": 1 + }, + { + "default": ":Company", + "fieldname": "cost_center", + "fieldtype": "Link", + "label": "Cost Center", + "oldfieldname": "cost_center", + "oldfieldtype": "Link", + "options": "Cost Center" + }, + { + "fieldname": "description", + "fieldtype": "Small Text", + "label": "Description", + "oldfieldname": "description", + "oldfieldtype": "Small Text", + "print_width": "300px", + "reqd": 1, + "width": "300px" + }, + { + "columns": 2, + "fieldname": "rate", + "fieldtype": "Float", + "in_list_view": 1, + "label": "Rate", + "oldfieldname": "rate", + "oldfieldtype": "Currency" + }, + { + "columns": 2, + "fieldname": "tax_amount", + "fieldtype": "Currency", + "in_list_view": 1, + "label": "Amount", + "oldfieldname": "tax_amount", + "oldfieldtype": "Currency", + "options": "currency" + }, + { + "columns": 2, + "fieldname": "total", + "fieldtype": "Currency", + "in_list_view": 1, + "label": "Total", + "oldfieldname": "total", + "oldfieldtype": "Currency", + "options": "currency", + "read_only": 1 + }, + { + "fieldname": "section_break_6", + "fieldtype": "Section Break" + }, + { + "fieldname": "column_break_8", + "fieldtype": "Column Break" + } + ], + "istable": 1, + "modified": "2019-06-11 14:19:34.780611", + "modified_by": "Administrator", + "module": "HR", + "name": "Expense Taxes and Charges", + "owner": "Administrator", + "permissions": [], + "sort_field": "modified", + "sort_order": "ASC", + "track_changes": 1 +} \ No newline at end of file diff --git a/erpnext/hr/doctype/expense_taxes_and_charges/expense_taxes_and_charges.py b/erpnext/hr/doctype/expense_taxes_and_charges/expense_taxes_and_charges.py new file mode 100644 index 0000000000..4103bef1ff --- /dev/null +++ b/erpnext/hr/doctype/expense_taxes_and_charges/expense_taxes_and_charges.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +# import frappe +from frappe.model.document import Document + +class ExpenseTaxesandCharges(Document): + pass From 444313bdfcd2b1b06e2590c8db9d9c5cf827cb0d Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Wed, 12 Jun 2019 12:52:02 +0530 Subject: [PATCH 2/8] feat: change the claim amount in expenses to amount --- erpnext/demo/user/hr.py | 4 ++-- .../hr/doctype/expense_claim/expense_claim.js | 6 +++--- .../doctype/expense_claim/expense_claim.json | 21 ++++++++++++------- .../hr/doctype/expense_claim/expense_claim.py | 9 ++++---- .../expense_claim/test_expense_claim.js | 2 +- .../expense_claim/test_expense_claim.py | 6 +++--- .../expense_claim_detail.json | 6 +++--- erpnext/hr/doctype/vehicle_log/vehicle_log.py | 8 +++---- 8 files changed, 34 insertions(+), 28 deletions(-) diff --git a/erpnext/demo/user/hr.py b/erpnext/demo/user/hr.py index 79f3c19287..0211bc8a90 100644 --- a/erpnext/demo/user/hr.py +++ b/erpnext/demo/user/hr.py @@ -97,7 +97,7 @@ def get_expenses(): "expense_date": frappe.flags.current_date, "expense_type": expense_type.name, "default_account": expense_type.default_account or "Miscellaneous Expenses - WPL", - "claim_amount": claim_amount, + "amount": claim_amount, "sanctioned_amount": claim_amount }) @@ -107,7 +107,7 @@ def update_sanctioned_amount(expense_claim): for expense in expense_claim.expenses: sanctioned_amount = random.randint(1,20)*10 - if sanctioned_amount < expense.claim_amount: + if sanctioned_amount < expense.amount: expense.sanctioned_amount = sanctioned_amount def get_timesheet_based_salary_slip_employee(): diff --git a/erpnext/hr/doctype/expense_claim/expense_claim.js b/erpnext/hr/doctype/expense_claim/expense_claim.js index 0ff70cb680..6425b95d7e 100644 --- a/erpnext/hr/doctype/expense_claim/expense_claim.js +++ b/erpnext/hr/doctype/expense_claim/expense_claim.js @@ -109,7 +109,7 @@ cur_frm.cscript.calculate_total = function(doc){ doc.total_claimed_amount = 0; doc.total_sanctioned_amount = 0; $.each((doc.expenses || []), function(i, d) { - doc.total_claimed_amount += d.claim_amount; + doc.total_claimed_amount += d.amount; doc.total_sanctioned_amount += d.sanctioned_amount; }); @@ -308,10 +308,10 @@ frappe.ui.form.on("Expense Claim", { }); frappe.ui.form.on("Expense Claim Detail", { - claim_amount: function(frm, cdt, cdn) { + amount: function(frm, cdt, cdn) { var child = locals[cdt][cdn]; var doc = frm.doc; - frappe.model.set_value(cdt, cdn, 'sanctioned_amount', child.claim_amount); + frappe.model.set_value(cdt, cdn, 'sanctioned_amount', child.amount); cur_frm.cscript.calculate_total(doc,cdt,cdn); }, diff --git a/erpnext/hr/doctype/expense_claim/expense_claim.json b/erpnext/hr/doctype/expense_claim/expense_claim.json index 007e646815..409bdc8615 100644 --- a/erpnext/hr/doctype/expense_claim/expense_claim.json +++ b/erpnext/hr/doctype/expense_claim/expense_claim.json @@ -13,20 +13,21 @@ "column_break_5", "expense_approver", "approval_status", - "total_claimed_amount", - "total_sanctioned_amount", "is_paid", "expense_details", "expenses", "sb1", "taxes", - "net_total", + "transactions_section", + "total_tax_amount", + "total_amount_reimbursed", + "total_sanctioned_amount", + "total_claimed_amount", "section_break_16", "posting_date", "vehicle_log", "task", "cb1", - "total_amount_reimbursed", "remark", "title", "email_id", @@ -331,16 +332,22 @@ "fieldtype": "Section Break" }, { - "fieldname": "net_total", + "fieldname": "transactions_section", + "fieldtype": "Section Break", + "label": "Transactions" + }, + { + "fieldname": "total_tax_amount", "fieldtype": "Currency", - "label": "Net Total", + "label": "Total Tax Amount", + "options": "Company:company:default_currency", "read_only": 1 } ], "icon": "fa fa-money", "idx": 1, "is_submittable": 1, - "modified": "2019-06-11 13:21:42.386420", + "modified": "2019-06-12 12:32:13.775009", "modified_by": "Administrator", "module": "HR", "name": "Expense Claim", diff --git a/erpnext/hr/doctype/expense_claim/expense_claim.py b/erpnext/hr/doctype/expense_claim/expense_claim.py index c5b6ebe56b..07e711939c 100644 --- a/erpnext/hr/doctype/expense_claim/expense_claim.py +++ b/erpnext/hr/doctype/expense_claim/expense_claim.py @@ -172,11 +172,11 @@ class ExpenseClaim(AccountsController): }) ) - gl_entry = self.make_tax_gl_entries(gl_entry) + self.add_tax_gl_entries(gl_entry) return gl_entry - def make_tax_gl_entries(self, gl_entries): + def add_tax_gl_entries(self, gl_entries): # tax table gl entries for tax in self.get("taxes"): account_currency = get_account_currency(tax.account_head) @@ -190,7 +190,6 @@ class ExpenseClaim(AccountsController): "against_voucher": self.name }, account_currency) ) - return gl_entries def validate_account_details(self): if not self.cost_center: @@ -210,7 +209,7 @@ class ExpenseClaim(AccountsController): if self.approval_status == 'Rejected': d.sanctioned_amount = 0.0 - self.total_claimed_amount += flt(d.claim_amount) + self.total_claimed_amount += flt(d.amount) self.total_sanctioned_amount += flt(d.sanctioned_amount) def calculate_taxes(self): @@ -253,7 +252,7 @@ class ExpenseClaim(AccountsController): def validate_sanctioned_amount(self): for d in self.get('expenses'): - if flt(d.sanctioned_amount) > flt(d.claim_amount): + if flt(d.sanctioned_amount) > flt(d.amount): frappe.throw(_("Sanctioned Amount cannot be greater than Claim Amount in Row {0}.").format(d.idx)) def set_expense_account(self, validate=False): diff --git a/erpnext/hr/doctype/expense_claim/test_expense_claim.js b/erpnext/hr/doctype/expense_claim/test_expense_claim.js index 070474e10f..d0c43d3be4 100644 --- a/erpnext/hr/doctype/expense_claim/test_expense_claim.js +++ b/erpnext/hr/doctype/expense_claim/test_expense_claim.js @@ -17,7 +17,7 @@ QUnit.test("Test: Expense Claim [HR]", function (assert) { d.expense_date = '2017-08-01', d.expense_type = 'Test Expense Type 1', d.description = 'This is just to test Expense Claim', - d.claim_amount = 2000, + d.amount = 2000, d.sanctioned_amount=2000, refresh_field('expenses'); }, diff --git a/erpnext/hr/doctype/expense_claim/test_expense_claim.py b/erpnext/hr/doctype/expense_claim/test_expense_claim.py index 075bc63345..6fc2a83ebd 100644 --- a/erpnext/hr/doctype/expense_claim/test_expense_claim.py +++ b/erpnext/hr/doctype/expense_claim/test_expense_claim.py @@ -89,7 +89,7 @@ class TestExpenseClaim(unittest.TestCase): "payable_account": payable_account, "approval_status": "Rejected", "expenses": - [{ "expense_type": "Travel", "default_account": "Travel Expenses - WP", "claim_amount": 300, "sanctioned_amount": 200 }] + [{ "expense_type": "Travel", "default_account": "Travel Expenses - WP", "amount": 300, "sanctioned_amount": 200 }] }) expense_claim.submit() @@ -102,7 +102,7 @@ class TestExpenseClaim(unittest.TestCase): def get_payable_account(company): return frappe.get_cached_value('Company', company, 'default_payable_account') -def make_expense_claim(payable_account,claim_amount, sanctioned_amount, company, account, project=None, task_name=None): +def make_expense_claim(payable_account,amount, sanctioned_amount, company, account, project=None, task_name=None): expense_claim = frappe.get_doc({ "doctype": "Expense Claim", "employee": "_T-Employee-00001", @@ -110,7 +110,7 @@ def make_expense_claim(payable_account,claim_amount, sanctioned_amount, company, "approval_status": "Approved", "company": company, "expenses": - [{ "expense_type": "Travel", "default_account": account, "claim_amount": claim_amount, "sanctioned_amount": sanctioned_amount }] + [{ "expense_type": "Travel", "default_account": account, "amount": amount, "sanctioned_amount": sanctioned_amount }] }) if project: expense_claim.project = project diff --git a/erpnext/hr/doctype/expense_claim_detail/expense_claim_detail.json b/erpnext/hr/doctype/expense_claim_detail/expense_claim_detail.json index d4e70575e9..b23fb6af0f 100644 --- a/erpnext/hr/doctype/expense_claim_detail/expense_claim_detail.json +++ b/erpnext/hr/doctype/expense_claim_detail/expense_claim_detail.json @@ -253,7 +253,7 @@ "bold": 0, "collapsible": 0, "columns": 0, - "fieldname": "claim_amount", + "fieldname": "amount", "fieldtype": "Currency", "hidden": 0, "ignore_user_permissions": 0, @@ -262,7 +262,7 @@ "in_global_search": 0, "in_list_view": 1, "in_standard_filter": 0, - "label": "Claim Amount", + "label": "Amount", "length": 0, "no_copy": 0, "oldfieldname": "claim_amount", @@ -360,7 +360,7 @@ "issingle": 0, "istable": 1, "max_attachments": 0, - "modified": "2019-02-24 08:41:36.122565", + "modified": "2019-06-10 08:41:36.122565", "modified_by": "Administrator", "module": "HR", "name": "Expense Claim Detail", diff --git a/erpnext/hr/doctype/vehicle_log/vehicle_log.py b/erpnext/hr/doctype/vehicle_log/vehicle_log.py index ceea4933f3..df633611be 100644 --- a/erpnext/hr/doctype/vehicle_log/vehicle_log.py +++ b/erpnext/hr/doctype/vehicle_log/vehicle_log.py @@ -18,11 +18,11 @@ class VehicleLog(Document): if (service_detail.service_item or service_detail.type or service_detail.frequency or service_detail.expense_amount): if not (service_detail.service_item and service_detail.type and service_detail.frequency and service_detail.expense_amount): frappe.throw(_("Service Item,Type,frequency and expense amount are required")) - + def on_submit(self): frappe.db.sql("update `tabVehicle` set last_odometer=%s where license_plate=%s", (self.odometer, self.license_plate)) - + @frappe.whitelist() def get_make_model(license_plate): vehicle=frappe.get_doc("Vehicle",license_plate) @@ -41,7 +41,7 @@ def make_expense_claim(docname): for serdetail in vehicle_log.service_detail: total_exp_amt = total_exp_amt + serdetail.expense_amount return total_exp_amt - + vehicle_log = frappe.get_doc("Vehicle Log", docname) exp_claim = frappe.new_doc("Expense Claim") exp_claim.employee=vehicle_log.employee @@ -52,6 +52,6 @@ def make_expense_claim(docname): exp_claim.append("expenses",{ "expense_date":vehicle_log.date, "description":_("Vehicle Expenses"), - "claim_amount":total_claim_amt + "amount":total_claim_amt }) return exp_claim.as_dict() From a1d354e14752ef1ee2faee52f9239fd7b9b3ef81 Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Wed, 12 Jun 2019 18:18:38 +0530 Subject: [PATCH 3/8] feat: add grand total field --- .../hr/doctype/expense_claim/expense_claim.js | 46 +++++++++++-------- .../doctype/expense_claim/expense_claim.json | 29 ++++++++---- .../hr/doctype/expense_claim/expense_claim.py | 32 ++++++------- 3 files changed, 64 insertions(+), 43 deletions(-) diff --git a/erpnext/hr/doctype/expense_claim/expense_claim.js b/erpnext/hr/doctype/expense_claim/expense_claim.js index 6425b95d7e..4a61963d49 100644 --- a/erpnext/hr/doctype/expense_claim/expense_claim.js +++ b/erpnext/hr/doctype/expense_claim/expense_claim.js @@ -112,9 +112,6 @@ cur_frm.cscript.calculate_total = function(doc){ doc.total_claimed_amount += d.amount; doc.total_sanctioned_amount += d.sanctioned_amount; }); - - refresh_field("total_claimed_amount"); - refresh_field('total_sanctioned_amount'); }; cur_frm.cscript.calculate_total_amount = function(doc,cdt,cdn){ @@ -157,14 +154,14 @@ frappe.ui.form.on("Expense Claim", { } }; }); - // frm.set_query("taxes", "account_head", function(doc) { - // return { - // filters: [ - // ['docstatus', '=', 1], - // ['company', '=', doc.company] - // ] - // }; - // }); + frm.set_query("account_head", "taxes", function(doc) { + return { + filters: [ + ['company', '=', doc.company], + ['account_type', 'in', ["Tax", "Chargeable", "Income Account", "Expenses Included In Valuation"]] + ] + }; + }); }, onload: function(frm) { @@ -205,6 +202,12 @@ frappe.ui.form.on("Expense Claim", { } }, + calculate_grand_total: function(frm) { + var grand_total = flt(frm.doc.total_sanctioned_amount) + flt(frm.doc.total_taxes_and_charges) - flt(frm.doc.total_advance_amount); + frm.set_value("grand_total", grand_total); + frm.refresh_fields(); + }, + make_payment_entry: function(frm) { var method = "erpnext.accounts.doctype.payment_entry.payment_entry.get_payment_entry"; if(frm.doc.__onload && frm.doc.__onload.make_payment_via_journal_entry) { @@ -319,6 +322,7 @@ frappe.ui.form.on("Expense Claim Detail", { var doc = frm.doc; cur_frm.cscript.calculate_total(doc,cdt,cdn); frm.trigger("get_taxes"); + frm.trigger("calculate_grand_total"); } }); @@ -345,6 +349,7 @@ frappe.ui.form.on("Expense Claim Advance", { child.advance_paid = r.message[0].paid_amount; child.unclaimed_amount = flt(r.message[0].paid_amount) - flt(r.message[0].claimed_amount); child.allocated_amount = flt(r.message[0].paid_amount) - flt(r.message[0].claimed_amount); + frm.trigger('calculate_grand_total'); refresh_field("advances"); } } @@ -370,27 +375,30 @@ frappe.ui.form.on("Expense Taxes and Charges", { } }, - calculate_total: function(frm, cdt, cdn) { + calculate_total_tax: function(frm, cdt, cdn) { var child = locals[cdt][cdn]; child.total = flt(frm.doc.total_sanctioned_amount) + flt(child.tax_amount); + frm.trigger("calculate_tax_amount", cdt, cdn); + }, - refresh_field("taxes"); + calculate_tax_amount: function(frm) { + frm.doc.total_taxes_and_charges = 0; + (frm.doc.taxes || []).forEach(function(d) { + frm.doc.total_taxes_and_charges += d.tax_amount; + }); + frm.trigger("calculate_grand_total") }, rate: function(frm, cdt, cdn) { var child = locals[cdt][cdn]; if(!child.amount) { child.tax_amount = flt(frm.doc.total_sanctioned_amount) * (flt(child.rate)/100); - refresh_field("taxes"); } - frm.trigger("calculate_total", cdt, cdn) + frm.trigger("calculate_total_tax", cdt, cdn) }, tax_amount: function(frm, cdt, cdn) { - var child = locals[cdt][cdn]; - child.rate = flt(child.tax_amount/frm.doc.total_sanctioned_amount) * 100; - frm.trigger("calculate_total", cdt, cdn) - refresh_field("taxes"); + frm.trigger("calculate_total_tax", cdt, cdn) } }); diff --git a/erpnext/hr/doctype/expense_claim/expense_claim.json b/erpnext/hr/doctype/expense_claim/expense_claim.json index 409bdc8615..7b0d4943fc 100644 --- a/erpnext/hr/doctype/expense_claim/expense_claim.json +++ b/erpnext/hr/doctype/expense_claim/expense_claim.json @@ -19,10 +19,13 @@ "sb1", "taxes", "transactions_section", - "total_tax_amount", - "total_amount_reimbursed", "total_sanctioned_amount", "total_claimed_amount", + "total_advance_amount", + "column_break_17", + "total_amount_reimbursed", + "total_taxes_and_charges", + "grand_total", "section_break_16", "posting_date", "vehicle_log", @@ -44,8 +47,7 @@ "status", "amended_from", "advance_payments", - "advances", - "total_advance_amount" + "advances" ], "fields": [ { @@ -122,7 +124,6 @@ { "fieldname": "total_sanctioned_amount", "fieldtype": "Currency", - "in_list_view": 1, "label": "Total Sanctioned Amount", "no_copy": 1, "oldfieldname": "total_sanctioned_amount", @@ -337,9 +338,21 @@ "label": "Transactions" }, { - "fieldname": "total_tax_amount", + "fieldname": "grand_total", "fieldtype": "Currency", - "label": "Total Tax Amount", + "in_list_view": 1, + "label": "Grand Total", + "options": "Company:company:default_currency", + "read_only": 1 + }, + { + "fieldname": "column_break_17", + "fieldtype": "Column Break" + }, + { + "fieldname": "total_taxes_and_charges", + "fieldtype": "Currency", + "label": "Total Taxes and Charges", "options": "Company:company:default_currency", "read_only": 1 } @@ -347,7 +360,7 @@ "icon": "fa fa-money", "idx": 1, "is_submittable": 1, - "modified": "2019-06-12 12:32:13.775009", + "modified": "2019-06-12 15:35:09.092603", "modified_by": "Administrator", "module": "HR", "name": "Expense Claim", diff --git a/erpnext/hr/doctype/expense_claim/expense_claim.py b/erpnext/hr/doctype/expense_claim/expense_claim.py index 07e711939c..7f660a45b5 100644 --- a/erpnext/hr/doctype/expense_claim/expense_claim.py +++ b/erpnext/hr/doctype/expense_claim/expense_claim.py @@ -104,15 +104,13 @@ class ExpenseClaim(AccountsController): gl_entry = [] self.validate_account_details() - payable_amount = flt(self.net_total) - flt(self.total_advance_amount) - # payable entry - if payable_amount: + if self.grand_total: gl_entry.append( self.get_gl_dict({ "account": self.payable_account, - "credit": payable_amount, - "credit_in_account_currency": payable_amount, + "credit": self.grand_total, + "credit_in_account_currency": self.grand_total, "against": ",".join([d.default_account for d in self.expenses]), "party_type": "Employee", "party": self.employee, @@ -146,15 +144,16 @@ class ExpenseClaim(AccountsController): "against_voucher": self.name }) ) + self.add_tax_gl_entries(gl_entry) - if self.is_paid and payable_amount: + if self.is_paid and self.grand_total: # payment entry payment_account = get_bank_cash_account(self.mode_of_payment, self.company).get("account") gl_entry.append( self.get_gl_dict({ "account": payment_account, - "credit": payable_amount, - "credit_in_account_currency": payable_amount, + "credit": self.grand_total, + "credit_in_account_currency": self.grand_total, "against": self.employee }) ) @@ -165,15 +164,13 @@ class ExpenseClaim(AccountsController): "party_type": "Employee", "party": self.employee, "against": payment_account, - "debit": payable_amount, - "debit_in_account_currency": payable_amount, + "debit": self.grand_total, + "debit_in_account_currency": self.grand_total, "against_voucher": self.name, "against_voucher_type": self.doctype, }) ) - self.add_tax_gl_entries(gl_entry) - return gl_entry def add_tax_gl_entries(self, gl_entries): @@ -184,11 +181,12 @@ class ExpenseClaim(AccountsController): self.get_gl_dict({ "account": tax.account_head, "debit": tax.tax_amount, + "debit_in_account_currency": tax.tax_amount, "against": self.employee, "cost_center": self.cost_center, "against_voucher_type": self.doctype, "against_voucher": self.name - }, account_currency) + }) ) def validate_account_details(self): @@ -213,13 +211,15 @@ class ExpenseClaim(AccountsController): self.total_sanctioned_amount += flt(d.sanctioned_amount) def calculate_taxes(self): + self.total_taxes_and_charges = 0 for tax in self.taxes: if tax.rate: tax.tax_amount = flt(self.total_sanctioned_amount) * flt(tax.rate/100) - if tax.tax_amount: - tax.rate = flt(tax.tax_amount)/flt(self.total_sanctioned_amount) * 100 + tax.total = flt(tax.tax_amount) + flt(self.total_sanctioned_amount) - self.net_total += tax.total + self.total_taxes_and_charges += flt(tax.tax_amount) + + self.grand_total = flt(self.total_sanctioned_amount) + flt(self.total_taxes_and_charges) - flt(self.total_advance_amount) def update_task(self): task = frappe.get_doc("Task", self.task) From 0a3ed374cddf38728ee5d4735d470041d707daaf Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Wed, 12 Jun 2019 20:03:22 +0530 Subject: [PATCH 4/8] test: validate tax based expense claim gl entries --- .../doctype/expense_claim/expense_claim.json | 8 ++-- .../hr/doctype/expense_claim/expense_claim.py | 1 - .../expense_claim/test_expense_claim.py | 41 +++++++++++++++---- 3 files changed, 37 insertions(+), 13 deletions(-) diff --git a/erpnext/hr/doctype/expense_claim/expense_claim.json b/erpnext/hr/doctype/expense_claim/expense_claim.json index 7b0d4943fc..db85037795 100644 --- a/erpnext/hr/doctype/expense_claim/expense_claim.json +++ b/erpnext/hr/doctype/expense_claim/expense_claim.json @@ -20,12 +20,12 @@ "taxes", "transactions_section", "total_sanctioned_amount", - "total_claimed_amount", + "total_taxes_and_charges", "total_advance_amount", "column_break_17", - "total_amount_reimbursed", - "total_taxes_and_charges", "grand_total", + "total_claimed_amount", + "total_amount_reimbursed", "section_break_16", "posting_date", "vehicle_log", @@ -360,7 +360,7 @@ "icon": "fa fa-money", "idx": 1, "is_submittable": 1, - "modified": "2019-06-12 15:35:09.092603", + "modified": "2019-06-12 20:00:25.734108", "modified_by": "Administrator", "module": "HR", "name": "Expense Claim", diff --git a/erpnext/hr/doctype/expense_claim/expense_claim.py b/erpnext/hr/doctype/expense_claim/expense_claim.py index 7f660a45b5..caeb2dd946 100644 --- a/erpnext/hr/doctype/expense_claim/expense_claim.py +++ b/erpnext/hr/doctype/expense_claim/expense_claim.py @@ -176,7 +176,6 @@ class ExpenseClaim(AccountsController): def add_tax_gl_entries(self, gl_entries): # tax table gl entries for tax in self.get("taxes"): - account_currency = get_account_currency(tax.account_head) gl_entries.append( self.get_gl_dict({ "account": tax.account_head, diff --git a/erpnext/hr/doctype/expense_claim/test_expense_claim.py b/erpnext/hr/doctype/expense_claim/test_expense_claim.py index 6fc2a83ebd..a42209f6ad 100644 --- a/erpnext/hr/doctype/expense_claim/test_expense_claim.py +++ b/erpnext/hr/doctype/expense_claim/test_expense_claim.py @@ -6,6 +6,7 @@ import frappe import unittest from frappe.utils import random_string, nowdate from erpnext.hr.doctype.expense_claim.expense_claim import make_bank_entry +from erpnext.accounts.doctype.account.test_account import create_account test_records = frappe.get_test_records('Expense Claim') test_dependencies = ['Employee'] @@ -26,7 +27,7 @@ class TestExpenseClaim(unittest.TestCase): task_name = frappe.db.get_value("Task", {"project": "_Test Project 1"}) payable_account = get_payable_account("Wind Power LLC") - make_expense_claim(payable_account, 300, 200, "Wind Power LLC","Travel Expenses - WP", "_Test Project 1", task_name) + make_expense_claim(payable_account, 300, 200, "Wind Power LLC", "Travel Expenses - WP", "_Test Project 1", task_name) self.assertEqual(frappe.db.get_value("Task", task_name, "total_expense_claim"), 200) self.assertEqual(frappe.db.get_value("Project", "_Test Project 1", "total_expense_claim"), 200) @@ -62,7 +63,8 @@ class TestExpenseClaim(unittest.TestCase): def test_expense_claim_gl_entry(self): payable_account = get_payable_account("Wind Power LLC") - expense_claim = make_expense_claim(payable_account, 300, 200, "Wind Power LLC", "Travel Expenses - WP") + taxes = generate_taxes() + expense_claim = make_expense_claim(payable_account, 300, 200, "Wind Power LLC", "Travel Expenses - WP", do_not_submit=True, taxes=taxes) expense_claim.submit() gl_entries = frappe.db.sql("""select account, debit, credit @@ -72,7 +74,8 @@ class TestExpenseClaim(unittest.TestCase): self.assertTrue(gl_entries) expected_values = dict((d[0], d) for d in [ - [payable_account, 0.0, 200.0], + ['CGST - WP',10.0, 0.0], + [payable_account, 0.0, 210.0], ["Travel Expenses - WP", 200.0, 0.0] ]) @@ -100,22 +103,44 @@ class TestExpenseClaim(unittest.TestCase): self.assertEquals(len(gl_entry), 0) def get_payable_account(company): - return frappe.get_cached_value('Company', company, 'default_payable_account') + return frappe.get_cached_value('Company', company, 'default_payable_account') -def make_expense_claim(payable_account,amount, sanctioned_amount, company, account, project=None, task_name=None): - expense_claim = frappe.get_doc({ +def generate_taxes(): + parent_account = frappe.db.get_value('Account', + {'company': "Wind Power LLC", 'is_group':1, 'account_type': 'Tax'}, + 'name') + account = create_account(company="Wind Power LLC", account_name="CGST", account_type="Tax", parent_account=parent_account) + return {'taxes':[{ + "account_head": account, + "rate": 0, + "description": "CGST", + "tax_amount": 10, + "total": 210 + }]} + +def make_expense_claim(payable_account, amount, sanctioned_amount, company, account, project=None, task_name=None, do_not_submit=False, taxes=None): + expense_claim = { "doctype": "Expense Claim", "employee": "_T-Employee-00001", "payable_account": payable_account, "approval_status": "Approved", "company": company, "expenses": - [{ "expense_type": "Travel", "default_account": account, "amount": amount, "sanctioned_amount": sanctioned_amount }] - }) + [{"expense_type": "Travel", + "default_account": account, + "amount": amount, + "sanctioned_amount": sanctioned_amount}]} + if taxes: + expense_claim.update(taxes) + + expense_claim = frappe.get_doc(expense_claim) + if project: expense_claim.project = project if task_name: expense_claim.task = task_name + if do_not_submit: + return expense_claim expense_claim.submit() return expense_claim From f8bf57c95641034f6b91c7dff56fede81acb94d0 Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Thu, 13 Jun 2019 18:09:23 +0530 Subject: [PATCH 5/8] fix: remove transaction section head --- erpnext/hr/doctype/expense_claim/expense_claim.js | 6 +++--- erpnext/hr/doctype/expense_claim/expense_claim.json | 5 ++--- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/erpnext/hr/doctype/expense_claim/expense_claim.js b/erpnext/hr/doctype/expense_claim/expense_claim.js index 4a61963d49..9e78ca12c4 100644 --- a/erpnext/hr/doctype/expense_claim/expense_claim.js +++ b/erpnext/hr/doctype/expense_claim/expense_claim.js @@ -275,7 +275,7 @@ frappe.ui.form.on("Expense Claim", { frappe.call({ method: "calculate_taxes", doc: frm.doc, - callback: (r) => { + callback: () => { refresh_field("taxes"); } }); @@ -386,7 +386,7 @@ frappe.ui.form.on("Expense Taxes and Charges", { (frm.doc.taxes || []).forEach(function(d) { frm.doc.total_taxes_and_charges += d.tax_amount; }); - frm.trigger("calculate_grand_total") + frm.trigger("calculate_grand_total"); }, rate: function(frm, cdt, cdn) { @@ -398,7 +398,7 @@ frappe.ui.form.on("Expense Taxes and Charges", { }, tax_amount: function(frm, cdt, cdn) { - frm.trigger("calculate_total_tax", cdt, cdn) + frm.trigger("calculate_total_tax", cdt, cdn); } }); diff --git a/erpnext/hr/doctype/expense_claim/expense_claim.json b/erpnext/hr/doctype/expense_claim/expense_claim.json index db85037795..f0bc268e5d 100644 --- a/erpnext/hr/doctype/expense_claim/expense_claim.json +++ b/erpnext/hr/doctype/expense_claim/expense_claim.json @@ -334,8 +334,7 @@ }, { "fieldname": "transactions_section", - "fieldtype": "Section Break", - "label": "Transactions" + "fieldtype": "Section Break" }, { "fieldname": "grand_total", @@ -360,7 +359,7 @@ "icon": "fa fa-money", "idx": 1, "is_submittable": 1, - "modified": "2019-06-12 20:00:25.734108", + "modified": "2019-06-13 18:05:52.530462", "modified_by": "Administrator", "module": "HR", "name": "Expense Claim", From 6726dea88bdd59a17825d9947ec303de0a0a34a4 Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Fri, 14 Jun 2019 11:17:17 +0530 Subject: [PATCH 6/8] style: change formatting --- erpnext/hr/doctype/expense_claim/expense_claim.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/hr/doctype/expense_claim/expense_claim.js b/erpnext/hr/doctype/expense_claim/expense_claim.js index 9e78ca12c4..deb6e1dc08 100644 --- a/erpnext/hr/doctype/expense_claim/expense_claim.js +++ b/erpnext/hr/doctype/expense_claim/expense_claim.js @@ -394,7 +394,7 @@ frappe.ui.form.on("Expense Taxes and Charges", { if(!child.amount) { child.tax_amount = flt(frm.doc.total_sanctioned_amount) * (flt(child.rate)/100); } - frm.trigger("calculate_total_tax", cdt, cdn) + frm.trigger("calculate_total_tax", cdt, cdn); }, tax_amount: function(frm, cdt, cdn) { From 0ef1df72adb463de7ca40c00612cde6ef938d446 Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Wed, 19 Jun 2019 17:50:33 +0530 Subject: [PATCH 7/8] refactor: fetch rate directly from the account head --- erpnext/hr/doctype/expense_claim/expense_claim.js | 9 +-------- .../expense_taxes_and_charges.json | 3 ++- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/erpnext/hr/doctype/expense_claim/expense_claim.js b/erpnext/hr/doctype/expense_claim/expense_claim.js index deb6e1dc08..40bec6d4d7 100644 --- a/erpnext/hr/doctype/expense_claim/expense_claim.js +++ b/erpnext/hr/doctype/expense_claim/expense_claim.js @@ -361,16 +361,9 @@ frappe.ui.form.on("Expense Claim Advance", { frappe.ui.form.on("Expense Taxes and Charges", { account_head: function(frm, cdt, cdn) { var child = locals[cdt][cdn]; - if(child.account_head && !child.description && !child.rate) { + if(child.account_head && !child.description) { // set description from account head child.description = child.account_head.split(' - ').slice(0, -1).join(' - '); - - // set the tax rate from account head - frappe.db.get_value("Account", child.account_head, "tax_rate").then((r) => { - if(r.message) { - frappe.model.set_value(cdt, cdn, 'rate', r.message.tax_rate); - } - }); refresh_field("taxes"); } }, diff --git a/erpnext/hr/doctype/expense_taxes_and_charges/expense_taxes_and_charges.json b/erpnext/hr/doctype/expense_taxes_and_charges/expense_taxes_and_charges.json index 8caf0a975a..be51c43920 100644 --- a/erpnext/hr/doctype/expense_taxes_and_charges/expense_taxes_and_charges.json +++ b/erpnext/hr/doctype/expense_taxes_and_charges/expense_taxes_and_charges.json @@ -53,6 +53,7 @@ }, { "columns": 2, + "fetch_from": "account_head.tax_rate", "fieldname": "rate", "fieldtype": "Float", "in_list_view": 1, @@ -91,7 +92,7 @@ } ], "istable": 1, - "modified": "2019-06-11 14:19:34.780611", + "modified": "2019-06-19 17:47:40.236436", "modified_by": "Administrator", "module": "HR", "name": "Expense Taxes and Charges", From 8619fc7413e2cc98d7e9b768f671e54f7fcc9625 Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Thu, 20 Jun 2019 12:02:47 +0530 Subject: [PATCH 8/8] fix: fetch rate only if field is empty --- .../expense_taxes_and_charges/expense_taxes_and_charges.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/hr/doctype/expense_taxes_and_charges/expense_taxes_and_charges.json b/erpnext/hr/doctype/expense_taxes_and_charges/expense_taxes_and_charges.json index be51c43920..9bf69daf65 100644 --- a/erpnext/hr/doctype/expense_taxes_and_charges/expense_taxes_and_charges.json +++ b/erpnext/hr/doctype/expense_taxes_and_charges/expense_taxes_and_charges.json @@ -54,6 +54,7 @@ { "columns": 2, "fetch_from": "account_head.tax_rate", + "fetch_if_empty": 1, "fieldname": "rate", "fieldtype": "Float", "in_list_view": 1, @@ -92,7 +93,7 @@ } ], "istable": 1, - "modified": "2019-06-19 17:47:40.236436", + "modified": "2019-06-20 12:01:33.919555", "modified_by": "Administrator", "module": "HR", "name": "Expense Taxes and Charges",