ERPNext Expenses Doctype

@bluesky @dannyfoo I think the key here is use case. The fact is different organisations might decide to use different methods to settle expenses. Some would prefer payment entry, others would prefer journal entry. In my opinion, no solution is a waste as someone somewhere would like and want to use it.

I modified the cash advance and expense claim doctypes to manage both Employee and general expenses as my use case demanded that an Employee be responsible for all expenses not paid directly to recognised suppliers/vendors using invoicing (cash and open market expenses like flight tickets, prepaid bills payment, e.t.c). So while some might argue this is not right, I have justification for using it and it helps my financial reporting so it works for me. It may not work for you but it works for me and that’s the beauty of ERPNext… you can bend it to your will if you need to.

So kudos to @bluesky for not only coming up with a solution to fit his use case, he shared it with the community so whoever shares his use case can benefit.

In our app POS Bahrain we have made a simple payment entry method wherein user can just mention the amount and ledger. No need to make purchase invoice or item. Tax also can be added so it shows up in the custom built tax report for our country. The method was copied from SAP. It was during our ERPNext installation work where KPMG was the software consultant that KPMG showed us this feature of SAP. The code is below. It’s part of an app so someone can extract the code into another app.

3 Likes

Kudos should go to @adam26d! :slight_smile:

2 Likes

Actually, this is not unnecessary. It is a must-have feature!
Not all the expenses are paid right away. However, they should be “accrued” at the time of the obligation according to all accepted accounting conventions. And recording it as a “purchase invoice” or a “journal entry” is not a good practice.

There are many weak points in simple things in the Accounting module of ERPNext that hinder its power as a fully-fledged ERP such as the incorrect format of profit and loss account, Incorrect gross profit reports etc. Which I will hope improve in future versions.

Anyways kudos to @adam26d for bringing this in!

1 Like

@bluesky oh right… tagged you wrongly. @adam26d Kudos to you!!!

I don’t think it is impractical. I think the opposite. Use of purchase invoice to record general invoices is the last thing you want in an organized environment.

1 Like

Yes. It is different. Not all the expenses in a business are employee-related. There are general expenses like rent, electricity etc. These need to be accounted for separately and most importantly accrued at the period ends.

As of now ERP NEXT allows tax to item.

Is there any chance to add tax to Indirect expenses.

Then we can create purchases for rent, office expenses, etc

And create payment againist the purchase. Then only it should reflected to tax report like GSTR

Can you detailed on this, how to do it?

  1. Create a default item in Purchase Taxes and Charges Template (e.g. “DEFAULT VAT + EWT”), charges should be 0 rate.

  2. On Expense Claim
    -Add a link to “Purchase Taxes and Charges Template” renamed to “Expense Taxes and Charges Template” where it will prefill the “Expense Taxes and Charges” table base on the template.

  3. On Expense Claim Detail, add the following columns:
    -Gross Amount
    -BIR Company (link)
    -Item Tax Template (link)

  4. Add a client script to apply calculations. Sanction amount should be less to the taxes and charges as the employee will not compute for it and so is the Taxes and Charges.

frm.add_custom_button('Calculate Expense Taxes And Charges', () => {
	        if (frm.doc.expense_taxes_and_charges_template) {
                var taxes = frm.doc.taxes;
                var newTaxes = [];
                frm.doc.expenses.forEach((expense) => {
                    var expense_rate = 0;
                    var tax_template = frm.doc.expense_taxes_and_charges_template;
                    if (expense.item_tax_template) {
                        tax_template = expense.item_tax_template;
                    }
                    frappe.call({
                        method: 'frappe.client.get_list',
                        async: false,
                        args: {
                            'doctype': 'Purchase Taxes and Charges',
                            'parent': 'Purchase Taxes and Charges Template',
                            'filters': {
                                'parent': tax_template,
                                'parenttype': 'Purchase Taxes and Charges Template'
                            },
                            'fields': [
                                'add_deduct_tax',
                                'included_in_print_rate',
                                'rate',
                                'account_head',
                                'description'
                            ]
                        },
                        callback: function(r) {
                            if (!r.exc) {
                                r.message.forEach((obj) => {
                                    if (obj.included_in_print_rate === 1) {
                                        if (obj.rate!==0) {
                                            if (obj.add_deduct_tax==="Add") {
                                                expense_rate = expense_rate+obj.rate;
                                            } else {
                                                expense_rate = expense_rate-obj.rate;
                                            }
                                        }
                                    } else {
                                        var tax_amount = 0;
                                        if (obj.rate!==0) {
                                            if (obj.add_deduct_tax==="Add") {
                                                tax_amount=(expense.gross_amount*obj.rate)/100;
                                            } else {
                                                tax_amount=-(expense.gross_amount*obj.rate)/100;
                                            }
                                        }
                                        newTaxes.push({
                                            account_head: obj.account_head,
                                            add_deduct_tax: obj.add_deduct_tax,
                                            san_amount: expense.gross_amount,
                                            tax_amount: tax_amount
                                        });
                                    }
                                });
                                expense.amount = expense.gross_amount;
                                expense.sanctioned_amount = expense.gross_amount;
                                if (expense_rate!==0) {
                                    var san_amount = expense.gross_amount/(1+(expense_rate/100));
                                    expense.amount = san_amount;
                                    expense.sanctioned_amount = san_amount;
                                    frm.refresh_fields("expenses");
                                    r.message.forEach((obj) => {
                                        var tax_amount = 0;
                                        if (obj.rate!==0) {
                                            if (obj.add_deduct_tax==="Add") {
                                                tax_amount=(san_amount*obj.rate)/100;
                                            } else {
                                                tax_amount=-(san_amount*obj.rate)/100;
                                            }
                                        }
                                        newTaxes.push({
                                            account_head: obj.account_head,
                                            add_deduct_tax: obj.add_deduct_tax,
                                            san_amount: san_amount,
                                            tax_amount: tax_amount
                                        });
                                    });
                                }
                                var total_sanctioned_amount = 0;
                                frm.doc.expenses.forEach((obj)=> {
                                    total_sanctioned_amount = total_sanctioned_amount*1 + obj.sanctioned_amount*1;
                                });
                                if (total_sanctioned_amount!==0) {
                                    frm.doc.total_sanctioned_amount = total_sanctioned_amount*1;
                                }
                                taxes = taxes.map((obj)=> {
                                    obj.tax_amount=0;
                                    return obj;
                                });
                                newTaxes.forEach((obj)=>{
                                    if (taxes && taxes.length) {
                                        taxes.forEach((tax) => {
                                            if (tax.account_head===obj.account_head) {
                                                tax.tax_amount=tax.tax_amount+obj.tax_amount;
                                                tax.total=frm.doc.total_sanctioned_amount*1+tax.tax_amount*1;
                                            }
                                        });
                                    } 
                                });
                                var total_taxes_and_charges = 0;
                                taxes.forEach((tax) => { 
                                    total_taxes_and_charges = total_taxes_and_charges+tax.tax_amount;
                                });
                                frm.doc.total_taxes_and_charges = total_taxes_and_charges;
                                frm.doc.total_claimed_amount = frm.doc.total_sanctioned_amount + total_taxes_and_charges;
                                if (frm.doc.total_advance_amount) {
                                    frm.doc.grand_total = frm.doc.total_sanctioned_amount*1 + total_taxes_and_charges*1;
                                } else {
                                    frm.doc.grand_total = frm.doc.total_sanctioned_amount*1 + total_taxes_and_charges*1 - frm.doc.total_advance_amount*1;
                                }
                                frm.refresh_fields("taxes");
                                frappe.msgprint("Calculate Expense Taxes And Charges: Done!");
                            }
                        }
                    });
                });
	        } else {
	            frm.doc.expenses.forEach((expense) => {
    	            expense.amount = expense.gross_amount;
                    expense.sanctioned_amount = expense.gross_amount;
    	        });
    	        frm.refresh_fields("expenses");
    	        frappe.msgprint("Calculate Expense Taxes And Charges: Done!");
	        }
        });
  1. Create a report showing the new doctype per line item. “Expense VAT and EWT”
1 Like

Thank you so much for reserving an hour out of your busy schedule to detailed and customize the doctype. you’re so generous!

1 Like

Hi @adam26d, could you add the app to Frappe Cloud Market Place? This way users on Frappe Cloud can also benefit from the app.

(The instructions how to do this, can be found here:
Publishing an App to Marketplace)

1 Like

how to update the app
what’s the commanded

The easiest way is to go into the app directory and do a git pull

cd <bench>/apps/expense_request
git pull

Will be doing this in december and refining the app. Thanks for the suggestion

1 Like

i get error in approval

i have 2 company setup

Failed Transactions

EXP-2022-00003

Journal Entry ACC-JV-2022-00006: Cost Center Main - P does not belong to Company xc Pvt Ltd

can not approve
i did pull
still show Expense Request: v0.0.1 (main)

I see, I think it’s pulling from the default company… I can do some private support and add a company field so that we can solve this for you… for a small price of two coffee’s :slight_smile:

For now try using Session Defaults to change your company like so:

@adam26d :star_struck:
Its working smoothly even on v14 but how to add TAX in there ?

Thanks for the feedback. Tax is coming soon, for now, perhaps you could add it manually as an account

2 Likes

Hi @adam26d

i have checked your expense entry and these are my observations.

  1. The expense entry is not posting into the General ledger because you specified it should only post to GL only and when the doc.status === 'Approved" in your code. The issue with this is that if any organization like mine has multiple approval level. for example imagine a scenerio of 3 approvals with different approval level A, B and C

A can approve when if the total amount is <= 200,000.00
B can approve if the amount >200,000 but less than 1,000,000.000001
C can approve from 1,000,0000 and above.

If the organization wants to capture the approval above and also because you have to differentiate between the approvals. lets say you have;
for A, doc status will read “Approved by A”
for B, doc status will read “Approved by B”
and for C, the doc status will read “Approved by C”

Neither of these 3 approvals even though they are set to 1 in the doc status, will never be posted to the General ledger which was the problem i faced.

It would rather be best if you change it to submit or define it as on_submit.

Let me know what you feel about the feedback.

3 Likes