Skip to content

fix(tds): multiple fixes for tax withholding entry#54483

Draft
ljain112 wants to merge 8 commits intofrappe:developfrom
ljain112:misc-tds-fixes
Draft

fix(tds): multiple fixes for tax withholding entry#54483
ljain112 wants to merge 8 commits intofrappe:developfrom
ljain112:misc-tds-fixes

Conversation

@ljain112
Copy link
Copy Markdown
Collaborator

@ljain112 ljain112 commented Apr 23, 2026

  • update tax withholding category on change of party
  • remove read_only property from apply_tds in sales invoice item
  • add mandatory dependency for tax withholding category based on apply_tds
  • default apply_tds to 1 for newly inserted row
  • update item level apply_tds based on doc

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 23, 2026

📝 Walkthrough

Walkthrough

Client-side invoice logic now centralizes TDS/withholding behavior in TransactionController by adding apply_tds(doc, cdt, cdn) and update_item_tax_withholding_categories(party_category). Per-doctype apply_tds() handlers were removed from Purchase and Sales invoice scripts; those scripts now call update_item_tax_withholding_categories(...) and set document apply_tds via frm.set_value. Server-side get_item_details propagates apply_tds into item detail resolution and a new whitelisted API get_item_tax_withholding_categories returns item-level withholding categories in bulk. set_missing_item_details now assigns item.apply_tds from master data. DocType JSONs make tax_withholding_category mandatory when apply_tds is true and make apply_tds editable on sales invoice items.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main objective of the changeset, which focuses on multiple fixes for tax withholding entry functionality in TDS handling.
Description check ✅ Passed The description is directly related to the changeset, listing specific improvements to tax withholding category handling, apply_tds field behavior, and mandatory dependencies.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@erpnext/public/js/controllers/transaction.js`:
- Around line 3189-3195: The withholding table is cleared with
me.frm.clear_table("tax_withholding_entries") but only the items grid is
refreshed; update the code that sets item.apply_tds (the block using var me =
this; and $.each(this.frm.doc.items || [], ...)) to also refresh the cleared
withholding table by calling me.frm.refresh_field("tax_withholding_entries")
after clearing it so stale rows are not shown.
- Around line 3200-3217: In update_item_tax_withholding_categories, prevent
stale async responses overwriting newer party fallbacks by capturing the current
party identifier or a request token before making the frappe.call and verifying
it in the callback; e.g., store a local const requestParty = party_category (or
increment a this._itemTaxRequestId and capture it) and in the callback return
early if the saved token/party doesn't match the current one on this.frm (or the
latest request id). Apply this check before assigning
item.tax_withholding_category and refreshing the items field so only the latest
response updates rows.

In `@erpnext/stock/get_item_details.py`:
- Around line 1412-1418: The call to frappe.get_list in the return statement
should explicitly set limit_page_length to len(item_codes) so all requested
items are returned; update the frappe.get_list(...) invocation (the call that
uses filters={"name": ["in", item_codes]} and fields=["name", field]) to include
limit_page_length=len(item_codes) while keeping the surrounding
frappe._dict(...) return unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

Run ID: 17a6f4f4-77df-4255-a442-01ee419e78b8

📥 Commits

Reviewing files that changed from the base of the PR and between b8c3765 and 301db41.

📒 Files selected for processing (7)
  • erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js
  • erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json
  • erpnext/accounts/doctype/sales_invoice/sales_invoice.js
  • erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json
  • erpnext/controllers/accounts_controller.py
  • erpnext/public/js/controllers/transaction.js
  • erpnext/stock/get_item_details.py

Comment thread erpnext/public/js/controllers/transaction.js
Comment thread erpnext/public/js/controllers/transaction.js
Comment thread erpnext/stock/get_item_details.py
@codecov
Copy link
Copy Markdown

codecov Bot commented Apr 23, 2026

Codecov Report

❌ Patch coverage is 50.00000% with 5 lines in your changes missing coverage. Please review.
✅ Project coverage is 79.41%. Comparing base (7f8fa7c) to head (a1b3de7).
⚠️ Report is 156 commits behind head on develop.

Files with missing lines Patch % Lines
erpnext/stock/get_item_details.py 62.50% 3 Missing ⚠️
erpnext/controllers/accounts_controller.py 0.00% 2 Missing ⚠️
Additional details and impacted files
@@             Coverage Diff             @@
##           develop   #54483      +/-   ##
===========================================
- Coverage    79.44%   79.41%   -0.03%     
===========================================
  Files         1160     1160              
  Lines       125984   126114     +130     
===========================================
+ Hits        100083   100156      +73     
- Misses       25901    25958      +57     
Files with missing lines Coverage Δ
erpnext/controllers/accounts_controller.py 84.92% <0.00%> (-0.08%) ⬇️
erpnext/stock/get_item_details.py 85.02% <62.50%> (-0.24%) ⬇️

... and 14 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@erpnext/stock/get_item_details.py`:
- Around line 1407-1419: The current code uses frappe.get_all (which bypasses
record-level permissions) and only does a generic frappe.has_permission check,
and it silently defaults to purchase category for unsupported doctype values;
change to frappe.get_list (which enforces permissions) when fetching Items in
the get_item_details flow, passing the same filters/fields/as_list options so
record permissions are applied; keep or tighten the DocType-level check but rely
on get_list for per-record enforcement; additionally validate the incoming
doctype against sales_doctypes and purchase_doctypes and raise a clear error
(e.g., frappe.throw or ValueError) instead of falling back to the purchase field
when doctype is unsupported; also ensure item_codes is parsed/validated as a
list before calling get_list.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

Run ID: 6b842d62-b1af-4beb-b50d-bccf205365b4

📥 Commits

Reviewing files that changed from the base of the PR and between 301db41 and e35a79e.

📒 Files selected for processing (2)
  • erpnext/public/js/controllers/transaction.js
  • erpnext/stock/get_item_details.py

Comment thread erpnext/stock/get_item_details.py
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (1)
erpnext/stock/get_item_details.py (1)

1402-1419: ⚠️ Potential issue | 🟡 Minor

Set limit_page_length explicitly and validate doctype input.

Two concerns on this new whitelisted endpoint:

  1. frappe.get_list applies a default page length of 20 in many contexts; invoices with more than 20 items will silently get partial results and those rows will fall back to party_category on the client. Pass limit_page_length=len(item_codes) (or 0 for no limit) to make intent explicit.
  2. Any doctype value outside sales_doctypes silently falls back to the purchase field. Since this is a @frappe.whitelist() endpoint reachable by any logged-in user, validate doctype against the allowed list and throw otherwise.

Also worth parsing/guarding item_codes for the empty/string case before the query.

Proposed fix
 `@frappe.whitelist`()
 def get_item_tax_withholding_categories(item_codes: list | str, doctype: str) -> dict:
 	"""
 	Return a mapping of {item_code: tax_withholding_category} from the Item master.
 	"""
 	item_codes = frappe.parse_json(item_codes)
+	if isinstance(item_codes, str):
+		item_codes = [item_codes]
+	if not item_codes:
+		return {}
+
+	if doctype not in [*sales_doctypes, *purchase_doctypes]:
+		frappe.throw(_("Invalid doctype for tax withholding category lookup"))
+
 	field = (
 		"sales_tax_withholding_category" if doctype in sales_doctypes else "purchase_tax_withholding_category"
 	)
 
 	return frappe._dict(
 		frappe.get_list(
 			"Item",
 			filters={"name": ["in", item_codes]},
 			fields=["name", field],
 			as_list=1,
+			limit_page_length=len(item_codes),
 		)
 	)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@erpnext/stock/get_item_details.py` around lines 1402 - 1419, In
get_item_tax_withholding_categories, ensure you validate the doctype against the
allowed sales_doctypes list and raise an error if it is not a recognized sales
or purchase document; also guard/parse item_codes to return an empty dict
quickly when item_codes is empty or invalid. When calling frappe.get_list, pass
an explicit limit_page_length (use len(item_codes) or 0 for unlimited) and keep
the existing field selection logic that picks "sales_tax_withholding_category"
or "purchase_tax_withholding_category"; reference the function name
get_item_tax_withholding_categories, the local variable field, the
sales_doctypes symbol, and the frappe.get_list call so you update those spots
only.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@erpnext/stock/get_item_details.py`:
- Around line 1402-1419: In get_item_tax_withholding_categories, ensure you
validate the doctype against the allowed sales_doctypes list and raise an error
if it is not a recognized sales or purchase document; also guard/parse
item_codes to return an empty dict quickly when item_codes is empty or invalid.
When calling frappe.get_list, pass an explicit limit_page_length (use
len(item_codes) or 0 for unlimited) and keep the existing field selection logic
that picks "sales_tax_withholding_category" or
"purchase_tax_withholding_category"; reference the function name
get_item_tax_withholding_categories, the local variable field, the
sales_doctypes symbol, and the frappe.get_list call so you update those spots
only.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

Run ID: 17afe543-3602-4b6c-9c9f-c3b5eaf3fdbc

📥 Commits

Reviewing files that changed from the base of the PR and between e35a79e and a1b3de7.

📒 Files selected for processing (1)
  • erpnext/stock/get_item_details.py

@ljain112 ljain112 marked this pull request as draft April 24, 2026 12:49
@stale
Copy link
Copy Markdown

stale Bot commented May 10, 2026

This pull request has been automatically marked as inactive because it has not had recent activity. It will be closed within 3 days if no further activity occurs, but it only takes a comment to keep a contribution alive :) Also, even if it is closed, you can always reopen the PR when you're ready. Thank you for contributing.

@stale stale Bot added the inactive label May 10, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant