Try ERPNext Try Frappe Cloud Buy Support Partners Foundation

TDS Enhancements

I propose the following changes to the Tax Deducted at Source (TDS) Feature - this is an India Government requirement for purchase of services.

  1. Tax withholding Category: I propose we move it from the Supplier Master to the Purchase Invoice Item level. Why? It’s possible that the Services Supplier could provide a suite of services to the organization that are under different Tax Withholding Categories

  2. TDS on Purchase Receipt: While some might argue that TDS being a service, the underlying item needs to be set up as a Non-Stock item and when setup as a non-stock item makes it conflicting to do a Purchase Receipt, there are two very strong reasosn why you would still want to enable that:

2.1: Unless you do a Stock Transaction (Purchase Receipt or Purchase Invoice with Update Stock Checked), the PO doesn’t disappear from the Pending PO list
2.2: In most organizations the person responsible for the delivery of services and the person responsible for making the purchase invoice are two different people. So the person certifying that the services have been delivered to the organization can do a Purchase Receipt and the accounts person can do the Purchase Invoice

So we just replicate the TDS features on the Purchase Invoice to the Purchase Receipt

  1. TDS on Purchase Order: The TDS rule says that TDS needs to be deducted and remitted to the government the earlier of: 3.1 Invoice, 3.2: Payment

Therefore the TDS process collapses when advance payments need to be made. So, here’s the proposal for #3:

We extend the TDS feature to the Purchase Order. However when a Payment Entry is made for say X amount connected against this PO, TDS is deducted from X and the supplier is given X*(1-R) where R is the TDS deduction rate and R*X is an entry made against the account used in the Tax withholding Category

And as the Purchase Receipt/Invoice is made against this PO, the account balances move to the downstream documents

  1. Tax Withholding Category: There are inconsistencies in the way ERPnext is calculating withholding based on the data entered in Tax Withholding Category:

4.1: If the values in the Single Transaction Threshold and Cumulative Transaction Threshold are left blank (or 0), the formula computes and deducts TDS for any amount.
4.2 Formula for deduction: The Formula for deduction is higher of: 4.2.1: SUM(A)>Cumulative Transaction Threshold, TDS for Transaction = SUM(A)R-Sum(TDS) .OR. AR

Where A is the Net Total of the particular Purchase Invoice/Receipt, SUM(A) is the cumulative Invoicing that has happened by the Supplier for the particular Tax Withholding Category and SUM(TDS) is the sum of TDS deducted for the particularsupplier for the Tax Withholding Category, R is the Tax Withhoding Rate for the particular Tax Withholding Category

Is everybody okay about it?

Is there somebody already working on this? If yes, how can we help her/his efforts?

If no, shall I get my developer to do a pull request to fix this?

Thanks

Jay

2 Likes

Okay, I’m moving this to GitHub.

Thanks

Jay

If you can get a customer to fund it, go for it. All I can say is that build it as a contribution from Day 1. Pay the 30% contribution tax and your customer will be happier in the long run.

Sure chief! And let’s try to keep the contribution tax at 30%, not thrice or four times that. :slight_smile:

Thanks

Jay

This is Ok.

Also, could this feature be extended to payment entry of type Pay too.

In Kenya, WH amount is withheld at the point of paying the vendor. (This could be a different scenario altogether)

When i go to submitt purchase invoice then system shows me error : Supplier is required for TDS payable. Which is stands in Taxes and charges.

Hi

Ever got this implemented?

If not needs a revival. Wonder how much this would cost, to anyone who who wish to implement it?

Eventually used a custom script.
Notes:

  • Doesn’t factor different taxes in an invoice, just gets total taxes in a purchase invoice, order
  • Doesn’t factor partial payments, for now.
frappe.ui.form.on('Payment Entry', {
    refresh: function (frm) {
        get_supplier_details(frm);
    },
    party: function (frm) {
        get_supplier_details(frm);
    },
    validate: function (frm) {
        if (frm.doc.payment_type == 'Pay' && frm.doc.docstatus == 0) {
            update_references_total_charges_deducted(frm);
        }
    },
    before_save: function (frm) {
        if (frm.doc.payment_type == 'Pay' && frm.doc.docstatus == 0) {
            update_total_charges_deducted(frm);
            create_deductions_and_losses(frm);
        }
    }
});

//Begin of Functions
//Get supplier details
var get_supplier_details = function (frm) {
    if ((frm.doc.party_type == 'Supplier') && (frm.doc.party)) {
        frappe.model.with_doc(frm.doc.party_type, frm.doc.party, function () {
            let party = frappe.model.get_doc(frm.doc.party_type, frm.doc.party);
            let twcfieldname = 'tax_withholding_category';

            if (!frm.doc.tax_withholding_category) {
                frm.set_value('tax_withholding_category', party[twcfieldname]);
            }
 
            refresh_field('tax_withholding_category');
        });
    }
};

//Get and update taxes_and_charges_deducted on references table
var update_references_total_charges_deducted = function (frm) {
    if (frm.doc.tax_withholding_category) {
        frappe.model.with_doc("Tax Withholding Category", frm.doc.tax_withholding_category, function () {
            let taxwithholdingcategory = frappe.model.get_doc("Tax Withholding Category", frm.doc.tax_withholding_category);
            $.each(taxwithholdingcategory.accounts, function (index, accountsrow) {
                if (frm.doc.company == accountsrow.company) {
                    let total_taxes_deducted = 0;
                    $.each(frm.doc.references, function (i, row) {
                        if (row.allocated_amount > 0) {
                            frappe.model.with_doc(row.reference_doctype, row.reference_name, function () {
                                let purchase_doc = frappe.model.get_doc(row.reference_doctype, row.reference_name);
                                if (purchase_doc) {
                                    let var_taxes_and_charges_added = purchase_doc['taxes_and_charges_added'];
                                    let var_taxes_and_charges_deducted = flt((accountsrow.tax_withholding_rate / accountsrow.vat_rate) * var_taxes_and_charges_added);
                                    row.taxes_and_charges_added = var_taxes_and_charges_added;
                                    row.taxes_and_charges_deducted = var_taxes_and_charges_deducted;
                                    frm.refresh_field("references");
                                }
                            });
                        }
                    });
                }
            });
        });
    }
};

//Update paid amount
var update_total_charges_deducted = function (frm) {
    let total_taxes_deducted = 0;
    $.each(frm.doc.references, function (i, row) {
        if (row.taxes_and_charges_deducted > 0) {
            total_taxes_deducted += flt(row.taxes_and_charges_deducted);
        }
    });
    //Update paid amount
    let new_paid_amount = 0;
    frm.doc.original_paid_amount = frm.doc.received_amount;
    frm.doc.total_taxes_and_charges_deducted = total_taxes_deducted;
    new_paid_amount = frm.doc.received_amount - frm.doc.total_taxes_and_charges_deducted;
    frm.doc.paid_amount = new_paid_amount;
    refresh_field('original_paid_amount');
    refresh_field('total_taxes_and_charges_deducted');
    refresh_field('paid_amount');
};

//Create deductions and losses
var create_deductions_and_losses = function (frm) {
    if (frm.doc.tax_withholding_category) {
        frappe.model.with_doc("Tax Withholding Category", frm.doc.tax_withholding_category, function () {
            let taxwithholdingcategory = frappe.model.get_doc("Tax Withholding Category", frm.doc.tax_withholding_category);
            $.each(taxwithholdingcategory.accounts, function (index, accountsrow) {
                if (frm.doc.company == accountsrow.company) {
                    frm.clear_table('deductions');

                    let wht_amount = 0;
                    if (frm.doc.total_taxes_and_charges_deducted > 0) {
                        wht_amount = (frm.doc.total_taxes_and_charges_deducted * -1);
                    } else {
                        wht_amount = frm.doc.total_taxes_and_charges_deducted;
                    }

                    let row = frm.add_child('deductions', {
                        account: accountsrow.account,
                        cost_center: accountsrow.cost_center,
                        amount: wht_amount
                    });

                    frm.refresh_field('deductions');

                }
            });
        });
    }
};

Interesting. Our country also has TDS we have not been able to use the standard TDS logic ERPnext provides so would like to build on this. Could you explain a little in words what the code is supposed to do as:

  • it seems to require some custom fields in doctypes
  • it seems to recognize the tax when purchase invoice is submitted where as here is when paid

We have client script at payment entry to show total tax withheld in current calendar year as a percent of payments in same calendar year. If this helps advise and can post.

The setup happens in the Tax Withholding Category Doctype under Accounting, this is what will be attached to each Supplier for which money needs to be withheld during payment. In our case, Kenya, it’s 2% of the tax amount of the purchase invoice/order. Note: set Single Transaction Threshold as zero to avoid calculations of TDS during purchase invoice creation.

Added the below fields in Tax Withholding Account child doctype of Tax Withholding Category

Under Payment Entry Reference child doctype of Payment Entry, added the below custom fields.

  • Taxes and Charges Added: is filled with total taxes for the document.
  • Taxes and Charges Deducted: will hold tax amount to be withheld for each purchase invoice order being paid in the payment entry.

Added the below custom fields in Payment Entry


  • Tax Withholding Category: Filled with Tax Withholding category from supplier selected. Has a Depends On and Mandatory Depends On: eval:(in_list(["Pay"], doc.payment_type) && doc.party && doc.party_type == 'Supplier')
  • Original Paid Amount: Holds original paid amount keyed in by the user. Has a Depends On: **eval:(in_list(["Pay"], doc.payment_type) && doc.party && doc.paid_amount && doc.received_amount)**
  • Total Taxes and Charges Deducted: Holds total of all Taxes and Charges Deducted in the Payment Entry Reference child doctype. It’s the figure withheld during payment. Has a Depends On: **eval:(in_list(["Pay"], doc.payment_type) && doc.party && doc.paid_amount && doc.received_amount)**

Our case is applicable during payment, during purchase invoice it didn’t work for our case.

On validate, when a Payment Entry is saved. Go through all purchase invoices/orders added in the payment entry, and get their total taxes, and calculate amount to be withheld for each. Done by function: update_references_total_charges_deducted

Before Save of Payment Entry. Sum all taxes to be withheld and store in total_taxes_and_charges_deducted custom field on payment Entry. At the same time update paid_amount less total_taxes_and_charges_deducted Done by function: update_total_charges_deducted

Before Save of Payment Entry. Create Payment Deductions or Loss, this is filled with total_taxes_and_charges_deducted and details of the Tax Withholding Category for the supplier, i.e. WHT Account, Cost Center. Done by function: create_deductions_and_losses

The order of calling the functions on Validate, then Before Save is important.

This is excellent. Thanks for sharing the script here. I will try and reuse the script for India.

Thanks

Jay

1 Like

Will be enhancing to accommodate partial payments.