From ebf5a462b3563fab9a465e72310d261d832aaa9d Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Fri, 31 Jul 2026 12:05:44 +0530 Subject: [PATCH] fix: filter Accounts Receivable by invoice sales partner (backport #57628) (#57646) fix: filter Accounts Receivable by invoice sales partner (#57628) Filter Accounts Receivable and AR Summary on the Sales Invoice's own sales_partner instead of the customer's default_sales_partner, and read the Sales Partner column from the invoice. Returns are attributed to the invoice they settle, matching how the Sales Person filter works. (cherry picked from commit fd7765ac02c22133a95b60a6d9f997f5df0cf5a2) Co-authored-by: Jatin3128 <140256508+Jatin3128@users.noreply.github.com> --- .../accounts_receivable.py | 39 +++++++----- .../test_accounts_receivable.py | 59 +++++++++++++++++++ .../accounts_receivable_summary.py | 6 +- .../test_accounts_receivable_summary.py | 39 ++++++++++++ 4 files changed, 126 insertions(+), 17 deletions(-) diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py index db74275238e..5405bafab07 100644 --- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py +++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py @@ -106,6 +106,7 @@ class ReceivablePayableReport: def get_data(self): self.get_sales_invoices_or_customers_based_on_sales_person() + self.get_invoices_based_on_sales_partner() # Get invoice details like bill_no, due_date etc for all invoices self.get_invoice_details() @@ -241,6 +242,12 @@ class ReceivablePayableReport: ): return + if self.filters.get("sales_partner"): + # a return is folded onto the invoice it settles, so match that invoice's + # partner (like the sales_person filter above), not the return's own + if ple.against_voucher_no not in self.sales_partner_invoices: + return + if self.filters.get("ignore_accounts"): key = (ple.against_voucher_type, ple.against_voucher_no, ple.party) else: @@ -469,7 +476,7 @@ class ReceivablePayableReport: "company": self.filters.company, "docstatus": 1, }, - fields=["name", "due_date", "po_no"], + fields=["name", "due_date", "po_no", "sales_partner"], ) for d in si_list: self.invoice_details.setdefault(d.name, d) @@ -903,6 +910,22 @@ class ReceivablePayableReport: for d in records: self.sales_person_records.setdefault(d.parenttype, set()).add(d.parent) + def get_invoices_based_on_sales_partner(self): + if not self.filters.get("sales_partner"): + return + + self.sales_partner_invoices = set( + frappe.get_all( + "Sales Invoice", + filters={ + "sales_partner": self.filters.get("sales_partner"), + "docstatus": 1, + "company": self.filters.company, + }, + pluck="name", + ) + ) + def prepare_conditions(self): self.qb_selection_filter = [] self.or_filters = [] @@ -1005,15 +1028,6 @@ class ReceivablePayableReport: self.qb_selection_filter.append(Criterion.any([customer_ptt, sales_ptt])) - if self.filters.get("sales_partner"): - self.qb_selection_filter.append( - self.ple.party.isin( - qb.from_(self.customer) - .select(self.customer.name) - .where(self.customer.default_sales_partner == self.filters.get("sales_partner")) - ) - ) - def exclude_employee_transaction(self): self.qb_selection_filter.append(self.ple.party_type != "Employee") @@ -1113,9 +1127,6 @@ class ReceivablePayableReport: if self.account_type == "Receivable": fields = ["customer_name", "territory", "customer_group", "customer_primary_contact"] - if self.filters.get("sales_partner"): - fields.append("default_sales_partner") - self.party_details[party] = frappe.db.get_value( "Customer", party, @@ -1242,7 +1253,7 @@ class ReceivablePayableReport: self.add_column(label=_("Sales Person"), fieldname="sales_person", fieldtype="Data") if self.filters.sales_partner: - self.add_column(label=_("Sales Partner"), fieldname="default_sales_partner", fieldtype="Data") + self.add_column(label=_("Sales Partner"), fieldname="sales_partner", fieldtype="Data") if self.filters.account_type == "Payable": self.add_column( diff --git a/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py index 4a73d62ee2e..7354b48e4a2 100644 --- a/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py +++ b/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py @@ -7,6 +7,7 @@ from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_ent from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice from erpnext.accounts.report.accounts_receivable.accounts_receivable import execute from erpnext.accounts.test.accounts_mixin import AccountsTestMixin +from erpnext.controllers.sales_and_purchase_return import make_return_doc from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order @@ -1303,3 +1304,61 @@ class TestAccountsReceivable(AccountsTestMixin, FrappeTestCase): self.assertIn(original_customer, parties) self.assertNotIn(second_customer, parties) self.assertEqual(allowed_invoice.customer, original_customer) + + def test_receivable_filtered_by_sales_partner(self): + frappe.set_user("Administrator") + partner_a, partner_b = "_Test AR Sales Partner A", "_Test AR Sales Partner B" + for partner in (partner_a, partner_b): + if not frappe.db.exists("Sales Partner", partner): + frappe.get_doc( + { + "doctype": "Sales Partner", + "partner_name": partner, + "commission_rate": 0, + "territory": "All Territories", + } + ).insert() + + def _si(sales_partner): + si = self.create_sales_invoice(no_payment_schedule=True, do_not_submit=True, qty=2) + si.sales_partner = sales_partner + return si.save().submit() + + partner_a_si = _si(partner_a) + partner_b_si = _si(partner_b) + no_partner_si = _si(None) + + # a return is folded onto the invoice it settles, so it nets against that + # invoice's partner even when the return's own partner is cleared + no_partner_return = make_return_doc("Sales Invoice", partner_a_si.name) + no_partner_return.sales_partner = None + no_partner_return.items[0].qty = -1 + no_partner_return.update_outstanding_for_self = 0 + no_partner_return.save().submit() + + filters = { + "company": self.company, + "party_type": "Customer", + "report_date": today(), + "range": "30, 60, 90, 120", + } + + def rows_for(partner): + return { + r.voucher_no: r + for r in execute({**filters, "sales_partner": partner})[1] + if r.get("voucher_no") + } + + rows_a = rows_for(partner_a) + self.assertIn(partner_a_si.name, rows_a) + self.assertEqual(rows_a[partner_a_si.name].sales_partner, partner_a) + self.assertNotIn(partner_b_si.name, rows_a) + self.assertNotIn(no_partner_si.name, rows_a) + self.assertNotIn(no_partner_return.name, rows_a) + self.assertEqual(rows_a[partner_a_si.name].credit_note, 100) + self.assertEqual(rows_a[partner_a_si.name].outstanding, 100) + + rows_b = rows_for(partner_b) + self.assertIn(partner_b_si.name, rows_b) + self.assertNotIn(partner_a_si.name, rows_b) diff --git a/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.py b/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.py index 19d2faddf44..7ebbd26c69a 100644 --- a/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.py +++ b/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.py @@ -132,8 +132,8 @@ class AccountsReceivableSummary(ReceivablePayableReport): if row.sales_person: self.party_total[row.party].sales_person.append(row.get("sales_person", "")) - if self.filters.sales_partner: - self.party_total[row.party]["default_sales_partner"] = row.get("default_sales_partner", "") + if self.filters.sales_partner and row.get("sales_partner"): + self.party_total[row.party]["sales_partner"] = row.get("sales_partner") def get_columns(self): self.columns = [] @@ -191,7 +191,7 @@ class AccountsReceivableSummary(ReceivablePayableReport): self.add_column(label=_("Sales Person"), fieldname="sales_person", fieldtype="Data") if self.filters.sales_partner: - self.add_column(label=_("Sales Partner"), fieldname="default_sales_partner", fieldtype="Data") + self.add_column(label=_("Sales Partner"), fieldname="sales_partner", fieldtype="Data") else: self.add_column( diff --git a/erpnext/accounts/report/accounts_receivable_summary/test_accounts_receivable_summary.py b/erpnext/accounts/report/accounts_receivable_summary/test_accounts_receivable_summary.py index a98cc6af7a3..02dbe214ecb 100644 --- a/erpnext/accounts/report/accounts_receivable_summary/test_accounts_receivable_summary.py +++ b/erpnext/accounts/report/accounts_receivable_summary/test_accounts_receivable_summary.py @@ -193,3 +193,42 @@ class TestAccountsReceivable(AccountsTestMixin, FrappeTestCase): report = execute(filters) rpt_output = report[1] self.assertEqual(len(rpt_output), 0) + + def test_03_summary_sales_partner_column(self): + partner = "_Test AR Summary Sales Partner" + if not frappe.db.exists("Sales Partner", partner): + frappe.get_doc( + { + "doctype": "Sales Partner", + "partner_name": partner, + "commission_rate": 0, + "territory": "All Territories", + } + ).insert() + + si = create_sales_invoice( + item=self.item, + company=self.company, + customer=self.customer, + debit_to=self.debit_to, + posting_date=today(), + parent_cost_center=self.cost_center, + cost_center=self.cost_center, + rate=200, + price_list_rate=200, + do_not_submit=True, + ) + si.sales_partner = partner + si.save().submit() + + filters = { + "company": self.company, + "customer": self.customer, + "posting_date": today(), + "range": "30, 60, 90, 120", + "sales_partner": partner, + } + + rpt_output = execute(filters)[1] + self.assertEqual(len(rpt_output), 1) + self.assertEqual(rpt_output[0].get("sales_partner"), partner)