Skip to content

feat!: Introduce tax withholding entry (backport #51099)#51297

Closed
mergify[bot] wants to merge 1 commit intoversion-16-betafrom
mergify/bp/version-16-beta/pr-51099
Closed

feat!: Introduce tax withholding entry (backport #51099)#51297
mergify[bot] wants to merge 1 commit intoversion-16-betafrom
mergify/bp/version-16-beta/pr-51099

Conversation

@mergify
Copy link
Copy Markdown
Contributor

@mergify mergify Bot commented Dec 23, 2025

This PR introduces a comprehensive refactor of the Tax Withholding (TDS/TCS) system in ERPNext. The key change is the introduction of a new child table Tax Withholding Entry that tracks every tax withholding transaction with full auditability, replacing the previous approach of calculating TDS/TCS on-the-fly.

Breaking Changes

  1. Removed Tax Withheld Vouchers child table - Replaced by Tax Withholding Entry
  2. Removed TDS fields from Purchase Order and Purchase Receipt - TDS is now only applicable on invoices and payment entries
  3. Removed allocated_amount field from Advance Taxes and charges table - Advance TDS allocation is now tracked through Tax Withholding Entries
  4. Removed consider_party_ledger_amount setting - Simplified threshold calculation logic (User will have select the checkbox for document to be considered).
  5. Removed Consideration of unallocated payment entry in threshhold - Now if check box is enabled in Payment Entry, then TDS will always be deducted without the threshold check.

New Architecture

Tax Withholding Entry (Child Table)

A new child doctype that tracks every tax withholding transaction with the following key fields:

Field Description
tax_withholding_category The TDS/TCS category applied
taxable_amount Base amount on which tax is calculated
withholding_amount Actual tax withheld
tax_rate Rate at which tax is calculated
taxable_doctype/name Document that is subject to tax (Invoice)
withholding_doctype/name Document from which tax is deducted (Invoice/Payment)
status Settled, Under Withheld, Over Withheld, Duplicate, Cancelled
under_withheld_reason Threshold Exemption or Lower Deduction Certificate
lower_deduction_certificate Link to LDC if applicable

Entry Status Flow

┌─────────────────┐     ┌─────────────────┐     ┌─────────────────┐
│  Under Withheld │────▶│    Settled      │◀────│  Over Withheld  │
│  (No TDS yet)   │     │  (TDS matched)  │     │ (Advance TDS)   │
└─────────────────┘     └─────────────────┘     └─────────────────┘
  • Under Withheld: Tax is due but not yet deducted (e.g., below threshold)
  • Over Withheld: Tax deducted in advance without a taxable document (e.g., advance payments)
  • Settled: Taxable amount and withholding amount are properly matched

Controller Architecture

TaxWithholdingController (Base)
├── PurchaseTaxWithholding  # For Purchase Invoice
├── SalesTaxWithholding     # For Sales Invoice  
├── PaymentTaxWithholding   # For Payment Entry
└── JournalTaxWithholding   # For Journal Entry

Key Features

1. Automatic Entry Matching

When a document is submitted, the system automatically matches:

  • Under withheld entries (pending TDS) with over withheld entries (advance TDS)
  • Entries are matched based on party, category, and fiscal year
  • Partial matching is supported with proportional allocation
  • First preference is given to allocated advance entry.

2. Cumulative Threshold Tracking

Invoice 1: ₹10,000 → Under threshold → Status: Under Withheld
Invoice 2: ₹10,000 → Under threshold → Status: Under Withheld  
Invoice 3: ₹15,000 → Crosses ₹30,000 threshold → All 3 settled with TDS

3. Tax on Excess Amount

When tax_on_excess_amount is enabled:

  • Only the amount exceeding threshold is taxed
  • Threshold exemption entries are created with under_withheld_reason = "Threshold Exemption"

4. Lower Deduction Certificate (LDC) Support

  • LDC utilization is tracked per entry
  • Supports partial LDC application
  • Automatic rate adjustment based on LDC

5. Advance Payment TDS/TCS

Scenario: Payment Entry with TDS → Sales Invoice allocation

Payment Entry: ₹30,000 (TCS: ₹3,000) → Status: Over Withheld
Sales Invoice: ₹50,000 with ₹30,000 advance allocated
  → Entry 1: ₹30,000 taxable, ₹0 TCS (Threshold Exemption)
  → Entry 2: ₹20,000 taxable, ₹2,000 TCS (Settled from PE)

6. Item-Level Tax Withholding

  • Items can have their own tax_withholding_category
  • Supports multiple TDS categories in a single invoice
  • Item-wise tax breakup in tax rows
  • TDS on Gross Amount and Net Amount.

7. Return Invoice Handling

  • Return invoices create negative taxable entries
  • Automatic adjustment of original invoice's TDS entries
  • Proper GL entry reversal

8. Tax Withholding Group

Introduces Tax Withholding Group to support different TDS/TCS rates within the same category based on entity type.
Use Case
In India, Section 194C has different rates:

  • Individual/HUF: 1%
  • Company/Firm: 2%
  • Without PAN: 20%

Migration

A patch update_tax_withholding_field_in_payment_entry handles:

  • Renaming apply_tax_withholding_amount to apply_tds in Payment Entry

Future Enhancements

  1. Manual Adjustment
  2. Update After Submit
  3. Bulk reconciliation tool for under/over withheld entries
  4. TDS in Journal Entry for multiple parties.(Manual Entry)
  5. Handle Amendments
  6. Don't use Advance taxes and Charges table for TDS in Payment Entry.
  7. Configurable setting for TDS: Item level vs Party Level. Can be used to toggle additional settings like Tax withholding Group.
  8. Generalize LDC (say TaxID instead of PAN)
  9. Rounding off in auto calculation when the rate/taxable amount is changed.

Checklist

  • All existing tests pass
  • New test cases added for all scenarios
  • Documentation updated
  • Migration patch included

Features

  • Cumulative threshold TDS/TCS
  • Single threshold TDS
  • Tax on excess amount
  • Lower Deduction Certificate
  • Tax Withholding Group multiple rate at Item Level (e.g. Rate for company and Individual can be set in single template)
  • TDS on Advance Payment (always manually deducted)
  • Ignore Threshold checkbox in Invoice.
  • Item-level TDS
  • Return invoice handling
  • Invoice cancellation and adjustment
  • Payment entry cancellation
  • Cross fiscal year scenarios
  • Manual tax adjustments
  • Lower Deduction certificate
  • Regional Override (PAN)
  • Update Reports

no-docs


This is an automatic backport of pull request #51099 done by Mergify.

(cherry picked from commit c66f78c)

# Conflicts:
#	erpnext/accounts/doctype/journal_entry/journal_entry.py
#	erpnext/accounts/doctype/payment_entry/payment_entry.json
#	erpnext/accounts/doctype/sales_invoice/sales_invoice.py
#	erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py
#	erpnext/accounts/doctype/tax_withholding_category/test_tax_withholding_category.py
#	erpnext/patches.txt
@mergify
Copy link
Copy Markdown
Contributor Author

mergify Bot commented Dec 23, 2025

Cherry-pick of c66f78c has failed:

On branch mergify/bp/version-16-beta/pr-51099
Your branch is up to date with 'origin/version-16-beta'.

You are currently cherry-picking commit c66f78c784.
  (fix conflicts and run "git cherry-pick --continue")
  (use "git cherry-pick --skip" to skip this patch)
  (use "git cherry-pick --abort" to cancel the cherry-pick operation)

Changes to be committed:
	deleted:    erpnext/accounts/doctype/advance_tax/advance_tax.json
	deleted:    erpnext/accounts/doctype/advance_tax/advance_tax.py
	modified:   erpnext/accounts/doctype/advance_taxes_and_charges/advance_taxes_and_charges.json
	modified:   erpnext/accounts/doctype/advance_taxes_and_charges/advance_taxes_and_charges.py
	modified:   erpnext/accounts/doctype/journal_entry/journal_entry.js
	modified:   erpnext/accounts/doctype/journal_entry/journal_entry.json
	modified:   erpnext/accounts/doctype/payment_entry/payment_entry.js
	modified:   erpnext/accounts/doctype/payment_entry/payment_entry.py
	modified:   erpnext/accounts/doctype/payment_entry_deduction/payment_entry_deduction.json
	modified:   erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js
	modified:   erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json
	modified:   erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
	modified:   erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py
	modified:   erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json
	modified:   erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.py
	modified:   erpnext/accounts/doctype/purchase_taxes_and_charges/purchase_taxes_and_charges.json
	modified:   erpnext/accounts/doctype/purchase_taxes_and_charges/purchase_taxes_and_charges.py
	modified:   erpnext/accounts/doctype/sales_invoice/sales_invoice.js
	modified:   erpnext/accounts/doctype/sales_invoice/sales_invoice.json
	modified:   erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json
	modified:   erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.py
	modified:   erpnext/accounts/doctype/sales_taxes_and_charges/sales_taxes_and_charges.json
	modified:   erpnext/accounts/doctype/sales_taxes_and_charges/sales_taxes_and_charges.py
	modified:   erpnext/accounts/doctype/subscription/subscription.py
	deleted:    erpnext/accounts/doctype/tax_withheld_vouchers/tax_withheld_vouchers.json
	modified:   erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.js
	modified:   erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.json
	renamed:    erpnext/accounts/doctype/advance_tax/__init__.py -> erpnext/accounts/doctype/tax_withholding_entry/__init__.py
	new file:   erpnext/accounts/doctype/tax_withholding_entry/tax_withholding_entry.json
	new file:   erpnext/accounts/doctype/tax_withholding_entry/tax_withholding_entry.py
	new file:   erpnext/accounts/doctype/tax_withholding_entry/test_tax_withholding_entry.py
	renamed:    erpnext/accounts/doctype/tax_withheld_vouchers/__init__.py -> erpnext/accounts/doctype/tax_withholding_group/__init__.py
	new file:   erpnext/accounts/doctype/tax_withholding_group/tax_withholding_group.js
	new file:   erpnext/accounts/doctype/tax_withholding_group/tax_withholding_group.json
	renamed:    erpnext/accounts/doctype/tax_withheld_vouchers/tax_withheld_vouchers.py -> erpnext/accounts/doctype/tax_withholding_group/tax_withholding_group.py
	new file:   erpnext/accounts/doctype/tax_withholding_group/test_tax_withholding_group.py
	modified:   erpnext/accounts/doctype/tax_withholding_rate/tax_withholding_rate.json
	modified:   erpnext/accounts/doctype/tax_withholding_rate/tax_withholding_rate.py
	modified:   erpnext/accounts/party.py
	modified:   erpnext/accounts/report/tax_withholding_details/tax_withholding_details.js
	modified:   erpnext/accounts/report/tax_withholding_details/tax_withholding_details.py
	modified:   erpnext/accounts/report/tds_computation_summary/tds_computation_summary.py
	modified:   erpnext/buying/doctype/purchase_order/purchase_order.js
	modified:   erpnext/buying/doctype/purchase_order/purchase_order.json
	modified:   erpnext/buying/doctype/purchase_order/purchase_order.py
	modified:   erpnext/buying/doctype/purchase_order_item/purchase_order_item.json
	modified:   erpnext/buying/doctype/purchase_order_item/purchase_order_item.py
	modified:   erpnext/buying/doctype/supplier/supplier.json
	modified:   erpnext/buying/doctype/supplier/supplier.py
	modified:   erpnext/controllers/accounts_controller.py
	modified:   erpnext/controllers/sales_and_purchase_return.py
	modified:   erpnext/controllers/taxes_and_totals.py
	modified:   erpnext/hooks.py
	deleted:    erpnext/patches/v14_0/update_partial_tds_fields.py
	new file:   erpnext/patches/v16_0/migrate_tax_withholding_data.py
	new file:   erpnext/patches/v16_0/update_tax_withholding_field_in_payment_entry.py
	modified:   erpnext/public/js/controllers/transaction.js
	modified:   erpnext/public/js/utils/party.js
	modified:   erpnext/selling/doctype/customer/customer.json
	modified:   erpnext/selling/doctype/customer/customer.py
	modified:   erpnext/stock/doctype/item/item.json
	modified:   erpnext/stock/doctype/item/item.py
	modified:   erpnext/stock/doctype/purchase_receipt/purchase_receipt.json
	modified:   erpnext/stock/doctype/purchase_receipt/purchase_receipt.py
	modified:   erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json
	modified:   erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.py
	modified:   erpnext/stock/get_item_details.py
	modified:   erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py

Unmerged paths:
  (use "git add <file>..." to mark resolution)
	both modified:   erpnext/accounts/doctype/journal_entry/journal_entry.py
	both modified:   erpnext/accounts/doctype/payment_entry/payment_entry.json
	both modified:   erpnext/accounts/doctype/sales_invoice/sales_invoice.py
	both modified:   erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py
	both modified:   erpnext/accounts/doctype/tax_withholding_category/test_tax_withholding_category.py
	both modified:   erpnext/patches.txt

To fix up this pull request, you can check it out locally. See documentation: https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/reviewing-changes-in-pull-requests/checking-out-pull-requests-locally

@github-actions github-actions Bot locked as resolved and limited conversation to collaborators Jan 7, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants