Skip to content

fix(sales invoice): toggle Get Items From button based on is_return and POS view#52594

Open
Navin-S-R wants to merge 4 commits intofrappe:developfrom
aerele:fix-sales-invoice-get-items
Open

fix(sales invoice): toggle Get Items From button based on is_return and POS view#52594
Navin-S-R wants to merge 4 commits intofrappe:developfrom
aerele:fix-sales-invoice-get-items

Conversation

@Navin-S-R
Copy link
Member

@Navin-S-R Navin-S-R commented Feb 10, 2026

Issue: Return Delivery Notes are not listed on "Get Items From" (Delivery Note) when creating a Sales Invoice with is_return enabled

Ref: 59697

Steps to Reproduce

  1. Create a Sales Invoice for a customer with update stock unchecked.
  2. Create a Delivery Note against the previously created Sales Invoice.
  3. Create a Sales Return against the previously created Delivery Note.
  4. Create a new Sales Invoice with Is Return checked.
  5. Click Get Items From → Delivery Note.
  6. Observe that the Return Delivery Note does not appear in the list

Before:

Screen.Recording.2026-02-09.at.18.29.33.mov

After:

Screen.Recording.2026-02-09.at.18.33.32.mov

Backport Needed v15

@github-actions github-actions bot added the needs-tests This PR needs automated unit-tests. label Feb 10, 2026
@codecov
Copy link

codecov bot commented Feb 10, 2026

Codecov Report

❌ Patch coverage is 0% with 8 lines in your changes missing coverage. Please review.
✅ Project coverage is 79.38%. Comparing base (7b3c107) to head (9bd89f7).
⚠️ Report is 35 commits behind head on develop.

Files with missing lines Patch % Lines
erpnext/controllers/queries.py 0.00% 8 Missing ⚠️
Additional details and impacted files
@@             Coverage Diff             @@
##           develop   #52594      +/-   ##
===========================================
- Coverage    79.39%   79.38%   -0.01%     
===========================================
  Files         1169     1169              
  Lines       123580   123601      +21     
===========================================
+ Hits         98115    98123       +8     
- Misses       25465    25478      +13     
Files with missing lines Coverage Δ
erpnext/controllers/queries.py 51.93% <0.00%> (-0.77%) ⬇️

... and 12 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.

@Navin-S-R
Copy link
Member Author

Navin-S-R commented Feb 10, 2026

@CodeRabbit review this

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 10, 2026

@Navin-S-R, I'll review the changes in this pull request.

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 10, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds toggle_get_items() and is_return() to SalesInvoiceController (erpnext/accounts/doctype/sales_invoice/sales_invoice.js) to centralize visibility logic for "Get Items From" buttons and replaces the previous inline toggling; updates refresh() to call the new method; Delivery Note mapping now requires a Customer and propagates is_return into mapping filters. In Python (erpnext/controllers/queries.py) get_delivery_notes_to_be_billed() was rewritten from raw SQL to the ORM query builder (frappe.qb) with type hints, a new original_dn subquery for return handling, dynamic filter application, ordering/pagination, and returns results via query.run(as_dict=as_dict).

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main changes: toggling Get Items From button visibility based on is_return state and POS view, which aligns with the core fix in sales_invoice.js.
Description check ✅ Passed The description is directly related to the changeset, clearly documenting the issue with Return Delivery Notes not appearing in the Get Items From picker and providing reproduction steps and before/after evidence.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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
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

🤖 Fix all issues with AI agents
In `@erpnext/accounts/doctype/sales_invoice/sales_invoice.js`:
- Around line 257-272: The toggle_get_items() logic removes the "Delivery Note"
picker on reload when this.frm.doc.is_return is true; to fix, change the else
branch so that when this.frm.doc.is_return is truthy you still invoke
this.frm.cscript.delivery_note_btn() (while still removing Sales
Order/Quotation/Timesheet), otherwise remove all "Get Items From" buttons;
update toggle_get_items() to call delivery_note_btn() in the else-path for
return invoices so the DN picker is present on load.
🧹 Nitpick comments (2)
erpnext/controllers/queries.py (2)

13-13: Coalesce is imported but unused.

The new query builder code in get_delivery_notes_to_be_billed doesn't use Coalesce. Remove it to keep imports clean.

Proposed fix
-from frappe.query_builder.functions import Coalesce, Concat, Locate, Sum
+from frappe.query_builder.functions import Concat, Locate, Sum

399-401: Minor: docstatus filter is applied twice — once in the base query (line 385) and again via dynamic filters.

Not a bug, but worth noting. The base query already filters docstatus == 1, and the JS also passes docstatus: 1 in filters. Consider excluding already-handled keys from the dynamic loop to avoid redundancy, or leave as-is since it's harmless.

@ravibharathi656 ravibharathi656 marked this pull request as ready for review February 10, 2026 12:48
@Navin-S-R Navin-S-R force-pushed the fix-sales-invoice-get-items branch from 462aeb8 to 9cc5d14 Compare February 23, 2026 11:27
Copy link
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: 4

🧹 Nitpick comments (1)
erpnext/accounts/doctype/sales_invoice/sales_invoice.js (1)

258-258: cint() applied to the comparison result rather than the operand — non-standard usage.

cint(this.frm.doc.docstatus == 0) wraps the boolean outcome, not the value. It works (cint(true) === 1) but diverges from the conventional pattern everywhere else in the codebase.

♻️ Idiomatic alternative
-if (cint(this.frm.doc.docstatus == 0) && this.frm.page.current_view_name !== "pos") {
+if (this.frm.doc.docstatus === 0 && this.frm.page.current_view_name !== "pos") {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@erpnext/accounts/doctype/sales_invoice/sales_invoice.js` at line 258, The
condition incorrectly applies cint to the boolean result
(cint(this.frm.doc.docstatus == 0)); change it to apply cint to the operand
instead (e.g., cint(this.frm.doc.docstatus) == 0) or drop cint and compare the
numeric/docstatus value directly so the expression follows the project's
idiomatic pattern in sales_invoice.js; update the condition used in the relevant
handler where this.frm.doc.docstatus is checked and ensure the logical AND with
this.frm.page.current_view_name !== "pos" is preserved.
🤖 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/accounts/doctype/sales_invoice/sales_invoice.js`:
- Around line 257-273: toggle_get_items is causing duplicate "Delivery Note"
buttons and leaving DN behind when skipping the add branch; to fix, at the start
of toggle_get_items() call this.frm.clear_custom_buttons() (or the form API to
clear the "Get Items From" group) so existing buttons are removed before adding
new ones, and in the else branch also remove the "Delivery Note" button by
calling this.frm.remove_custom_button("Delivery Note", "Get Items From"); update
the caller path (is_return -> toggle_get_items) only if it relies on external
clearing, but primarily ensure toggle_get_items itself clears buttons before
calling this.frm.cscript.delivery_note_btn().

In `@erpnext/controllers/queries.py`:
- Line 13: Remove the unused import Coalesce from the import line that brings in
Coalesce, Concat, Locate, Sum; locate the import statement referencing Coalesce
(used with Concat/Locate/Sum) and delete only Coalesce so the module imports
only Concat, Locate, Sum, ensuring no other references to Coalesce remain (e.g.,
in get_delivery_notes_to_be_billed) before committing.
- Around line 373-379: The subquery original_dn incorrectly filters out
originals with per_returned == 100, causing return DNs that reference
fully-returned originals to be excluded; update the where clause in the
original_dn QueryBuilder (the DeliveryNote subquery) to only check that the
original is submitted and not a return by removing the per_returned != 100
condition so it uses (DeliveryNote.docstatus == 1) & (DeliveryNote.is_return ==
0) only, leaving the main return_against.isin(original_dn) logic unchanged.
- Line 404: The return currently uses query.run(as_dict=True) which is
inconsistent with other whitelisted search functions (employee_query,
lead_query, get_project_name, get_account_list, get_filtered_child_rows) that
return tuples; change this call to return tuple form (use
query.run(as_dict=False) or query.run(as_list=True)) so the function matches the
file-wide convention of returning tuples instead of dicts/ frappe._dict,
updating any callers if needed to expect tuple semantics; locate the statement
by the return line containing query.run(as_dict=True) in
erpnext/controllers/queries.py and replace it accordingly.

---

Nitpick comments:
In `@erpnext/accounts/doctype/sales_invoice/sales_invoice.js`:
- Line 258: The condition incorrectly applies cint to the boolean result
(cint(this.frm.doc.docstatus == 0)); change it to apply cint to the operand
instead (e.g., cint(this.frm.doc.docstatus) == 0) or drop cint and compare the
numeric/docstatus value directly so the expression follows the project's
idiomatic pattern in sales_invoice.js; update the condition used in the relevant
handler where this.frm.doc.docstatus is checked and ensure the logical AND with
this.frm.page.current_view_name !== "pos" is preserved.

ℹ️ Review info

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 462aeb8 and 9cc5d14.

📒 Files selected for processing (2)
  • erpnext/accounts/doctype/sales_invoice/sales_invoice.js
  • erpnext/controllers/queries.py

@Navin-S-R Navin-S-R force-pushed the fix-sales-invoice-get-items branch from 9cc5d14 to 1bf1bc5 Compare February 23, 2026 11:45
Copy link
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 (3)
erpnext/controllers/queries.py (3)

418-418: query.run(as_dict=True) is inconsistent with every other search function in this file.

All other whitelisted search functions return tuples; get_filtered_child_rows (line 998, immediately below) explicitly uses query.run(as_dict=False). Callers of this function (and the Frappe link-field picker framework) expect the standard tuple format.

♻️ Proposed fix
-	return query.run(as_dict=True)
+	return query.run(as_dict=False)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@erpnext/controllers/queries.py` at line 418, The function currently returns
query.run(as_dict=True) which is inconsistent with other whitelisted search
helpers and breaks callers that expect tuple rows; change the call in this
function to query.run(as_dict=False) so it returns tuples (matching
get_filtered_child_rows and the Frappe link-field picker framework) and update
any inline comment if present to reflect the tuple return shape.

387-393: per_returned != 100 in original_dn breaks the fix for full-return scenarios.

When an original DN has been fully returned (per_returned = 100), it is excluded from original_dn. The return DN that references it then fails the return_against.isin(original_dn) check and is never surfaced — exactly the scenario this PR intends to fix. The subquery only needs to verify a valid, non-return, submitted original exists; its per_returned state is irrelevant.

🐛 Proposed fix
 original_dn = (
     frappe.qb.from_(DeliveryNote)
     .select(DeliveryNote.name)
     .where(
-        (DeliveryNote.docstatus == 1) & (DeliveryNote.is_return == 0) & (DeliveryNote.per_returned != 100)
+        (DeliveryNote.docstatus == 1) & (DeliveryNote.is_return == 0)
     )
 )
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@erpnext/controllers/queries.py` around lines 387 - 393, The subquery
original_dn (built from DeliveryNote) should not filter out records with
DeliveryNote.per_returned == 100 because fully-returned originals must still be
considered when checking return DN linkage; remove the per_returned != 100
predicate from the where clause in the original_dn query so original_dn only
checks (DeliveryNote.docstatus == 1) & (DeliveryNote.is_return == 0), ensuring
the return_against.isin(original_dn) test can match returns that reference
fully-returned originals.

13-13: Coalesce is imported but never used.

No call to Coalesce exists anywhere in the updated get_delivery_notes_to_be_billed or elsewhere in the file.

🗑️ Proposed fix
-from frappe.query_builder.functions import Coalesce, Concat, Locate, Sum
+from frappe.query_builder.functions import Concat, Locate, Sum
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@erpnext/controllers/queries.py` at line 13, Remove the unused Coalesce import
from the import statement in erpnext/controllers/queries.py: update the from
frappe.query_builder.functions import ... line to drop Coalesce (it is not
referenced in get_delivery_notes_to_be_billed or elsewhere), leaving only the
actually used symbols such as Concat, Locate, Sum to avoid dead imports.
🧹 Nitpick comments (1)
erpnext/controllers/queries.py (1)

380-382: filters type annotation is missing | None and a default value.

Every other comparable search function in the file (employee_query, lead_query, item_query, bom, get_project_name) declares filters: dict | None = None. The function body already handles None with if filters and isinstance(filters, dict):, so the signature should reflect that.

♻️ Proposed fix
 def get_delivery_notes_to_be_billed(
-	doctype: str, txt: str, searchfield: str, start: int, page_len: int, filters: dict
+	doctype: str, txt: str, searchfield: str, start: int, page_len: int, filters: dict | None = None
 ) -> list:
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@erpnext/controllers/queries.py` around lines 380 - 382, Update the function
signature of get_delivery_notes_to_be_billed to accept an optional filters
argument by changing the parameter from filters: dict to filters: dict | None =
None (matching other queries like employee_query/lead_query/item_query), since
the body already checks for None with if filters and isinstance(filters, dict):;
no other logic changes required.
🤖 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/controllers/queries.py`:
- Line 418: The function currently returns query.run(as_dict=True) which is
inconsistent with other whitelisted search helpers and breaks callers that
expect tuple rows; change the call in this function to query.run(as_dict=False)
so it returns tuples (matching get_filtered_child_rows and the Frappe link-field
picker framework) and update any inline comment if present to reflect the tuple
return shape.
- Around line 387-393: The subquery original_dn (built from DeliveryNote) should
not filter out records with DeliveryNote.per_returned == 100 because
fully-returned originals must still be considered when checking return DN
linkage; remove the per_returned != 100 predicate from the where clause in the
original_dn query so original_dn only checks (DeliveryNote.docstatus == 1) &
(DeliveryNote.is_return == 0), ensuring the return_against.isin(original_dn)
test can match returns that reference fully-returned originals.
- Line 13: Remove the unused Coalesce import from the import statement in
erpnext/controllers/queries.py: update the from frappe.query_builder.functions
import ... line to drop Coalesce (it is not referenced in
get_delivery_notes_to_be_billed or elsewhere), leaving only the actually used
symbols such as Concat, Locate, Sum to avoid dead imports.

---

Nitpick comments:
In `@erpnext/controllers/queries.py`:
- Around line 380-382: Update the function signature of
get_delivery_notes_to_be_billed to accept an optional filters argument by
changing the parameter from filters: dict to filters: dict | None = None
(matching other queries like employee_query/lead_query/item_query), since the
body already checks for None with if filters and isinstance(filters, dict):; no
other logic changes required.

ℹ️ Review info

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9cc5d14 and 1bf1bc5.

📒 Files selected for processing (2)
  • erpnext/accounts/doctype/sales_invoice/sales_invoice.js
  • erpnext/controllers/queries.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • erpnext/accounts/doctype/sales_invoice/sales_invoice.js

@Navin-S-R Navin-S-R force-pushed the fix-sales-invoice-get-items branch from 1bf1bc5 to b9a54ca Compare February 23, 2026 13:30
@Navin-S-R Navin-S-R force-pushed the fix-sales-invoice-get-items branch from b9a54ca to 0697b24 Compare February 23, 2026 14:02
Copy link
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: 2

♻️ Duplicate comments (1)
erpnext/accounts/doctype/sales_invoice/sales_invoice.js (1)

257-273: Duplicate "Delivery Note" button when is_return is toggled at runtime.

delivery_note_btn() on line 267 adds a DN button without removing the previous one. Each call to toggle_get_items() (from both refresh() and is_return()) adds another DN button on top of existing ones.

🐛 Proposed fix — remove before re-adding
 		}
+		this.frm.remove_custom_button("Delivery Note", "Get Items From");
 		this.frm.cscript.delivery_note_btn();
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@erpnext/accounts/doctype/sales_invoice/sales_invoice.js` around lines 257 -
273, The toggle_get_items logic causes duplicate "Delivery Note" buttons because
delivery_note_btn() adds a button unconditionally on each call; update
toggle_get_items (or delivery_note_btn) to remove any existing "Delivery Note"
button before re-adding by calling this.frm.remove_custom_button("Delivery
Note", "Get Items From") prior to invoking this.frm.cscript.delivery_note_btn(),
ensuring the button is removed in both the is_return==false path and the else
path so toggling is_return or refresh doesn't create duplicates; reference
functions: toggle_get_items, delivery_note_btn, remove_custom_button, is_return.
🤖 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/accounts/doctype/sales_invoice/sales_invoice.js`:
- Around line 257-266: toggle_get_items currently removes the "Timesheet" custom
button when is_return is true but only re-adds Sales Order and Quotation when
is_return becomes false, so restore the Timesheet button in the
!this.frm.doc.is_return branch by calling the same helper that adds it (e.g.
this.frm.cscript.timesheet_btn()) or, if a dedicated helper doesn't exist,
invoke the routine that registers all three "Get Items From" buttons (ensure you
reference toggle_get_items, this.frm.cscript.sales_order_btn,
this.frm.cscript.quotation_btn and the remove_custom_button call) so the
Timesheet button is re-added immediately (alternatively trigger a short form
refresh via this.frm.refresh() if adding a button helper is not available).

In `@erpnext/controllers/queries.py`:
- Around line 380-382: The function get_delivery_notes_to_be_billed currently
declares as_dict without a default, causing a TypeError when called via
`@frappe.validate_and_sanitize_search_inputs` (which supplies only the six
standard args); update the function signature
get_delivery_notes_to_be_billed(..., as_dict: bool = False) to give as_dict a
default (False) so calls from the Delivery Note picker succeed, and verify any
internal uses of as_dict still behave correctly with the False default.

---

Duplicate comments:
In `@erpnext/accounts/doctype/sales_invoice/sales_invoice.js`:
- Around line 257-273: The toggle_get_items logic causes duplicate "Delivery
Note" buttons because delivery_note_btn() adds a button unconditionally on each
call; update toggle_get_items (or delivery_note_btn) to remove any existing
"Delivery Note" button before re-adding by calling
this.frm.remove_custom_button("Delivery Note", "Get Items From") prior to
invoking this.frm.cscript.delivery_note_btn(), ensuring the button is removed in
both the is_return==false path and the else path so toggling is_return or
refresh doesn't create duplicates; reference functions: toggle_get_items,
delivery_note_btn, remove_custom_button, is_return.

ℹ️ Review info

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1bf1bc5 and 0697b24.

📒 Files selected for processing (2)
  • erpnext/accounts/doctype/sales_invoice/sales_invoice.js
  • erpnext/controllers/queries.py

@diptanilsaha diptanilsaha self-assigned this Feb 23, 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.

2 participants