Skip to content

Conversation

@Navin-S-R
Copy link
Contributor

@Navin-S-R Navin-S-R commented Dec 31, 2025

Issue: When a Purchase Receipt is created by pulling items from multiple Purchase Invoices that use the same tax account, the system does not merge the tax rows. Instead, it adds separate tax rows for the same account and the tax amount gets doubled upon save.

Ref: 56269

Steps to Reproduce:

  1. Create Purchase Invoice 1 for a supplier with taxes mentioned.
  2. Create Purchase Invoice 2 for the same supplier using the same tax account.
  3. Create a Purchase Receipt and pull items from both Purchase Invoices using "Get items From"
  4. Save the Purchase Receipt.
  5. Now its noticeable that the Tax rows with the same tax account appear twice and the tax amount is doubled.

Before:

Screen.Recording.2025-12-31.at.21.35.02.mov

After:

Screen.Recording.2025-12-31.at.21.36.30.mov

Backport needed v15

@github-actions github-actions bot added the needs-tests This PR needs automated unit-tests. label Dec 31, 2025
@codecov
Copy link

codecov bot commented Dec 31, 2025

Codecov Report

❌ Patch coverage is 90.00000% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 79.11%. Comparing base (349db6c) to head (22c20af).
⚠️ Report is 57 commits behind head on develop.

Files with missing lines Patch % Lines
...ounts/doctype/purchase_invoice/purchase_invoice.py 90.00% 1 Missing ⚠️
Additional details and impacted files
@@             Coverage Diff             @@
##           develop   #51422      +/-   ##
===========================================
+ Coverage    79.05%   79.11%   +0.06%     
===========================================
  Files         1179     1179              
  Lines       121306   121361      +55     
===========================================
+ Hits         95897    96015     +118     
+ Misses       25409    25346      -63     
Files with missing lines Coverage Δ
...ounts/doctype/purchase_invoice/purchase_invoice.py 85.85% <90.00%> (-0.03%) ⬇️

... and 47 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 Navin-S-R marked this pull request as ready for review January 3, 2026 07:07
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 3, 2026

📝 Walkthrough

Walkthrough

The PR updates Purchase Invoice → Purchase Receipt mapping to add two helper calls: one removes target items with zero quantity, the other runs the target's set_missing_values, conditionally calls merge_taxes(source_parent, target_parent) when args.merge_taxes is truthy, and then runs calculate_taxes_and_totals. The "Purchase Taxes and Charges" mapping now sets reset_value and ignore based on the merge_taxes argument. The frontend public/js/utils.js mapping logic now includes "Purchase Invoice" among source doctypes that populate tax-merge data fields for the MultiSelectDialog.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Suggested labels

backport version-15-hotfix

Suggested reviewers

  • ruthra-kumar

Pre-merge checks and finishing touches

❌ 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%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: fixing the merging of taxes in purchase receipts when items are pulled from multiple purchase invoices.
Description check ✅ Passed The description is well-related to the changeset, providing issue context, reproduction steps, and visual evidence of the fix addressing the tax merging problem.
✨ Finishing touches
  • 📝 Generate docstrings

📜 Recent review details

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1e54da4 and 22c20af.

📒 Files selected for processing (1)
  • erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
🧰 Additional context used
🧠 Learnings (4)
📚 Learning: 2025-12-23T11:28:49.128Z
Learnt from: vorasmit
Repo: frappe/erpnext PR: 51099
File: erpnext/patches/v16_0/migrate_tax_withholding_data.py:904-910
Timestamp: 2025-12-23T11:28:49.128Z
Learning: In the old Tax Withholding system (before the Tax Withholding Entry refactor), only one TCS account per Sales Invoice was supported. When reviewing migration code in erpnext/patches/v16_0/migrate_tax_withholding_data.py, the query selecting TCS accounts from GL Entries doesn't need explicit aggregation because historical data will only have one TCS account per invoice.

Applied to files:

  • erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
📚 Learning: 2025-10-17T12:17:35.397Z
Learnt from: sagarvora
Repo: frappe/erpnext PR: 50155
File: erpnext/controllers/taxes_and_totals.py:722-736
Timestamp: 2025-10-17T12:17:35.397Z
Learning: In ERPNext discount validation (erpnext/controllers/taxes_and_totals.py), the apply_discount_on field is only used to determine the base amount for calculating discount_amount from additional_discount_percentage. Once the discount_amount is determined (whether from percentage or manual entry), it should always be validated against grand_total, not against the field specified in apply_discount_on, because the discount cannot exceed the total invoice amount.

Applied to files:

  • erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
📚 Learning: 2025-11-05T16:19:19.510Z
Learnt from: alexleach
Repo: frappe/erpnext PR: 50373
File: erpnext/regional/united_kingdom/setup.py:28-31
Timestamp: 2025-11-05T16:19:19.510Z
Learning: In ERPNext's setup wizard flow, `update_regional_tax_settings` is called from `setup_taxes_and_charges` in `erpnext/setup/setup_wizard/operations/taxes_setup.py` only after `from_detailed_data()` has created all tax templates from `country_wise_tax.json`, guaranteeing that templates exist when regional tax rules are created.

Applied to files:

  • erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
📚 Learning: 2025-12-16T05:33:58.723Z
Learnt from: Abdeali099
Repo: frappe/erpnext PR: 51078
File: erpnext/accounts/doctype/financial_report_template/financial_report_engine.py:486-491
Timestamp: 2025-12-16T05:33:58.723Z
Learning: In ERPNext/Frappe codebase, query.run(as_dict=True) returns frappe._dict objects that support both dict-style access (obj["key"]) and attribute-style access (obj.key). Therefore, attribute access on query results is valid and will not raise AttributeError. When reviewing Python code, prefer attribute access (obj.key) for readability where the key is known to exist, but ensure existence checks or fallback handling if there is any doubt about missing keys.

Applied to files:

  • erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
🧬 Code graph analysis (1)
erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py (2)
erpnext/controllers/accounts_controller.py (1)
  • merge_taxes (4226-4266)
erpnext/stock/doctype/purchase_receipt/purchase_receipt.py (2)
  • set_missing_values (1365-1378)
  • set_missing_values (1527-1530)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
  • GitHub Check: Python Unit Tests (4)
  • GitHub Check: Python Unit Tests (3)
  • GitHub Check: Python Unit Tests (1)
  • GitHub Check: Python Unit Tests (2)
  • GitHub Check: Patch Test
  • GitHub Check: Summary
🔇 Additional comments (3)
erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py (3)

39-39: LGTM! Import statement is correct.

The merge_taxes import from erpnext.controllers.accounts_controller is properly added and used in the post-processing flow (line 2017) to merge tax rows from multiple Purchase Invoices.


2007-2018: Excellent implementation! All past issues have been properly addressed.

The post-processing flow is now correct and safe:

  1. Safe list filtering (line 2012): Uses list comprehension to remove zero-quantity items, eliminating the iteration-during-modification issue from the past review.

  2. Correct operation order (lines 2015-2018): The sequence is perfect:

    • set_missing_values() first
    • merge_taxes() when flag is set (consolidates tax rows from multiple sources)
    • calculate_taxes_and_totals() last (recalculates on merged taxes)
  3. Matches established pattern: This implementation follows the same pattern used in purchase_receipt.py (lines 1364-1377), ensuring consistency across the codebase.


2070-2074: Perfect! Tax mapping configuration is correct.

The reset_value and ignore parameters now implement the correct logic for tax consolidation:

  • When merge_taxes=True (pulling from multiple invoices):

    • reset_value=False: Preserves existing taxes already merged from previous invoices
    • ignore=1: Skips automatic copying by the mapper
    • Then merge_taxes() intelligently consolidates taxes by account and cost center
  • When merge_taxes=False (single invoice mapping):

    • reset_value=True: Clears target taxes before copying
    • ignore=0: Mapper automatically copies taxes from source

This implementation matches the established pattern from purchase_receipt.py and delivery_note.py, ensuring the fix works correctly without introducing duplicate tax rows.


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

📜 Review details

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 767bb53 and 8360602.

📒 Files selected for processing (1)
  • erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-12-16T05:33:58.723Z
Learnt from: Abdeali099
Repo: frappe/erpnext PR: 51078
File: erpnext/accounts/doctype/financial_report_template/financial_report_engine.py:486-491
Timestamp: 2025-12-16T05:33:58.723Z
Learning: In ERPNext/Frappe codebase, query.run(as_dict=True) returns frappe._dict objects that support both dict-style access (obj["key"]) and attribute-style access (obj.key). Therefore, attribute access on query results is valid and will not raise AttributeError. When reviewing Python code, prefer attribute access (obj.key) for readability where the key is known to exist, but ensure existence checks or fallback handling if there is any doubt about missing keys.

Applied to files:

  • erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
🧬 Code graph analysis (1)
erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py (4)
erpnext/buying/doctype/purchase_order/purchase_order.py (1)
  • set_missing_values (697-700)
erpnext/stock/doctype/purchase_receipt/purchase_receipt.py (2)
  • set_missing_values (1365-1378)
  • set_missing_values (1527-1530)
erpnext/accounts/doctype/sales_invoice/sales_invoice.py (3)
  • set_missing_values (729-762)
  • set_missing_values (2353-2356)
  • set_missing_values (2519-2521)
erpnext/stock/doctype/delivery_note/delivery_note.py (3)
  • set_missing_values (853-873)
  • set_missing_values (1025-1026)
  • set_missing_values (1194-1208)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Summary
🔇 Additional comments (2)
erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py (2)

2007-2009: LGTM: Clean preprocessing orchestration.

The function correctly orchestrates the preprocessing steps by first removing zero-quantity items and then recalculating taxes and totals.


2070-2070: No changes needed. The reset_value: True implementation is correct for this use case.

The function make_purchase_receipt(source_name, target_doc=None) is designed to handle a single source Purchase Invoice per call (note the singular source_name parameter). It is not intended to merge items from multiple Purchase Invoices in a single operation. The reset_value: True flag ensures that taxes are properly cleared and then recalculated based on the final set of items in the target Purchase Receipt. The post_parent_process function calls calculate_taxes_and_totals, which correctly recalculates all taxes from scratch based on the mapped items. This pattern is consistent across similar conversion functions in the codebase (Sales Order→Delivery Note, etc.). If a workflow requires merging items from multiple Purchase Invoices into a single Purchase Receipt, that logic would need to be implemented at a higher level, not within this single-source conversion function.

Likely an incorrect or invalid review comment.

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/purchase_invoice/purchase_invoice.py:
- Around line 2068-2071: Update the "Purchase Taxes and Charges" mapping so that
"reset_value" is set to not (args and args.get("merge_taxes")) and add an
"ignore" key with args.get("merge_taxes") if args else 0; then in
post_parent_process import and call merge_taxes(source, target_doc) when args
and args.get("merge_taxes") is truthy, placing that call before
calculate_taxes_and_totals(target_doc) so taxes are consolidated from sources
rather than being cleared and recomputed from items.
♻️ Duplicate comments (1)
erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py (1)

2014-2016: The merge_taxes call is still missing.

Per the previous review comment and the PR objective to fix duplicate tax rows when pulling from multiple Purchase Invoices, this function should call merge_taxes to consolidate tax rows by account and cost center. The current implementation only calls calculate_taxes_and_totals, which recalculates totals but doesn't merge duplicate tax entries.

Reference implementations:

  • Purchase Receipt → Purchase Invoice: calls merge_taxes(source, doc) when args.get("merge_taxes")
  • Delivery Note → Sales Invoice: calls merge_taxes(source, target) when args.get("merge_taxes")
🔎 Suggested fix

Import merge_taxes at the top of the file:

 from erpnext.controllers.buying_controller import BuyingController
+from erpnext.controllers.accounts_controller import merge_taxes
 from erpnext.stock.doctype.purchase_receipt.purchase_receipt import (

Then update set_missing_values:

 def set_missing_values(target):
 	target.run_method("set_missing_values")
+	if args and args.get("merge_taxes"):
+		# merge_taxes expects source document; in multi-source scenarios,
+		# the mapper handles merging across calls
+		# Here we ensure taxes are consolidated by account/cost_center
+		from erpnext.controllers.accounts_controller import merge_taxes
+		# Note: This may need adjustment based on mapper's multi-source handling
 	target.run_method("calculate_taxes_and_totals")

Note: The mapper may need additional logic if multiple source Purchase Invoices are being processed. Please verify whether merge_taxes should be called with a specific source or if additional consolidation logic is needed for multi-document mapping.

📜 Review details

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8360602 and f45e7f6.

📒 Files selected for processing (2)
  • erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
  • erpnext/public/js/utils.js
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-12-23T11:28:49.128Z
Learnt from: vorasmit
Repo: frappe/erpnext PR: 51099
File: erpnext/patches/v16_0/migrate_tax_withholding_data.py:904-910
Timestamp: 2025-12-23T11:28:49.128Z
Learning: In the old Tax Withholding system (before the Tax Withholding Entry refactor), only one TCS account per Sales Invoice was supported. When reviewing migration code in erpnext/patches/v16_0/migrate_tax_withholding_data.py, the query selecting TCS accounts from GL Entries doesn't need explicit aggregation because historical data will only have one TCS account per invoice.

Applied to files:

  • erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
📚 Learning: 2025-12-16T05:33:58.723Z
Learnt from: Abdeali099
Repo: frappe/erpnext PR: 51078
File: erpnext/accounts/doctype/financial_report_template/financial_report_engine.py:486-491
Timestamp: 2025-12-16T05:33:58.723Z
Learning: In ERPNext/Frappe codebase, query.run(as_dict=True) returns frappe._dict objects that support both dict-style access (obj["key"]) and attribute-style access (obj.key). Therefore, attribute access on query results is valid and will not raise AttributeError. When reviewing Python code, prefer attribute access (obj.key) for readability where the key is known to exist, but ensure existence checks or fallback handling if there is any doubt about missing keys.

Applied to files:

  • erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
  • GitHub Check: Patch Test
  • GitHub Check: Python Unit Tests (2)
  • GitHub Check: Python Unit Tests (4)
  • GitHub Check: Python Unit Tests (1)
  • GitHub Check: Python Unit Tests (3)
  • GitHub Check: Summary
🔇 Additional comments (3)
erpnext/public/js/utils.js (1)

996-996: LGTM! Enables tax-merge UI for Purchase Invoice source.

The addition of "Purchase Invoice" to the source doctype list correctly enables the "merge_taxes" checkbox when creating a Purchase Receipt from multiple Purchase Invoices, aligning with the PR's objective to fix duplicate tax rows.

erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py (2)

2007-2009: LGTM! Clear orchestration of post-processing steps.

The post_parent_process function logically sequences the removal of zero-quantity items followed by recalculation of values.


2011-2012: LGTM! Safe filtering approach.

The list comprehension correctly filters out zero-quantity items without the iteration-during-modification issue flagged in earlier reviews.

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/purchase_invoice/purchase_invoice.py:
- Around line 2014-2019: The current set_missing_values implementation calls
target_parent.run_method("calculate_taxes_and_totals") before merging tax rows,
so totals are computed on unconsolidated taxes; change the order to call
merge_taxes(source_parent, target_parent) immediately after
target_parent.run_method("set_missing_values") (and only if args and
args.get("merge_taxes") is true), then call
target_parent.run_method("calculate_taxes_and_totals") so totals are
recalculated from the merged tax rows, matching the pattern used in
purchase_receipt.py.
📜 Review details

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f45e7f6 and 1e54da4.

📒 Files selected for processing (1)
  • erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-12-23T11:28:49.128Z
Learnt from: vorasmit
Repo: frappe/erpnext PR: 51099
File: erpnext/patches/v16_0/migrate_tax_withholding_data.py:904-910
Timestamp: 2025-12-23T11:28:49.128Z
Learning: In the old Tax Withholding system (before the Tax Withholding Entry refactor), only one TCS account per Sales Invoice was supported. When reviewing migration code in erpnext/patches/v16_0/migrate_tax_withholding_data.py, the query selecting TCS accounts from GL Entries doesn't need explicit aggregation because historical data will only have one TCS account per invoice.

Applied to files:

  • erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
📚 Learning: 2025-12-16T05:33:58.723Z
Learnt from: Abdeali099
Repo: frappe/erpnext PR: 51078
File: erpnext/accounts/doctype/financial_report_template/financial_report_engine.py:486-491
Timestamp: 2025-12-16T05:33:58.723Z
Learning: In ERPNext/Frappe codebase, query.run(as_dict=True) returns frappe._dict objects that support both dict-style access (obj["key"]) and attribute-style access (obj.key). Therefore, attribute access on query results is valid and will not raise AttributeError. When reviewing Python code, prefer attribute access (obj.key) for readability where the key is known to exist, but ensure existence checks or fallback handling if there is any doubt about missing keys.

Applied to files:

  • erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
🧬 Code graph analysis (1)
erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py (3)
erpnext/controllers/accounts_controller.py (1)
  • merge_taxes (4226-4266)
erpnext/buying/doctype/purchase_order/purchase_order.py (1)
  • set_missing_values (697-700)
erpnext/stock/doctype/purchase_receipt/purchase_receipt.py (2)
  • set_missing_values (1365-1378)
  • set_missing_values (1527-1530)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
  • GitHub Check: Python Unit Tests (3)
  • GitHub Check: Python Unit Tests (2)
  • GitHub Check: Python Unit Tests (1)
  • GitHub Check: Python Unit Tests (4)
  • GitHub Check: Patch Test
  • GitHub Check: Summary
🔇 Additional comments (2)
erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py (2)

39-39: LGTM!

The import of merge_taxes from erpnext.controllers.accounts_controller is correctly placed and necessary for the new tax consolidation functionality.


2071-2075: LGTM!

The tax mapping configuration is correct. When merge_taxes is enabled:

  • reset_value: False preserves existing tax rows
  • ignore: True prevents copying taxes from source

This pattern matches the reference implementations in purchase_receipt.py and delivery_note.py.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

needs-tests This PR needs automated unit-tests.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant