mirror of
https://github.com/frappe/erpnext.git
synced 2026-08-17 00:18:39 +00:00
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 fd7765ac02)
Co-authored-by: Jatin3128 <140256508+Jatin3128@users.noreply.github.com>
This commit is contained in:
@@ -108,6 +108,7 @@ class ReceivablePayableReport:
|
|||||||
|
|
||||||
def get_data(self):
|
def get_data(self):
|
||||||
self.get_sales_invoices_or_customers_based_on_sales_person()
|
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
|
# Get invoice details like bill_no, due_date etc for all invoices
|
||||||
self.get_invoice_details()
|
self.get_invoice_details()
|
||||||
@@ -243,6 +244,12 @@ class ReceivablePayableReport:
|
|||||||
):
|
):
|
||||||
return
|
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"):
|
if self.filters.get("ignore_accounts"):
|
||||||
key = (ple.against_voucher_type, ple.against_voucher_no, ple.party)
|
key = (ple.against_voucher_type, ple.against_voucher_no, ple.party)
|
||||||
else:
|
else:
|
||||||
@@ -471,7 +478,7 @@ class ReceivablePayableReport:
|
|||||||
"company": self.filters.company,
|
"company": self.filters.company,
|
||||||
"docstatus": 1,
|
"docstatus": 1,
|
||||||
},
|
},
|
||||||
fields=["name", "due_date", "po_no"],
|
fields=["name", "due_date", "po_no", "sales_partner"],
|
||||||
)
|
)
|
||||||
for d in si_list:
|
for d in si_list:
|
||||||
self.invoice_details.setdefault(d.name, d)
|
self.invoice_details.setdefault(d.name, d)
|
||||||
@@ -909,6 +916,22 @@ class ReceivablePayableReport:
|
|||||||
for d in records:
|
for d in records:
|
||||||
self.sales_person_records.setdefault(d.parenttype, set()).add(d.parent)
|
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):
|
def prepare_conditions(self):
|
||||||
self.qb_selection_filter = []
|
self.qb_selection_filter = []
|
||||||
self.or_filters = []
|
self.or_filters = []
|
||||||
@@ -1011,15 +1034,6 @@ class ReceivablePayableReport:
|
|||||||
|
|
||||||
self.qb_selection_filter.append(Criterion.any([customer_ptt, sales_ptt]))
|
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):
|
def exclude_employee_transaction(self):
|
||||||
self.qb_selection_filter.append(self.ple.party_type != "Employee")
|
self.qb_selection_filter.append(self.ple.party_type != "Employee")
|
||||||
|
|
||||||
@@ -1119,9 +1133,6 @@ class ReceivablePayableReport:
|
|||||||
if self.account_type == "Receivable":
|
if self.account_type == "Receivable":
|
||||||
fields = ["customer_name", "territory", "customer_group", "customer_primary_contact"]
|
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(
|
self.party_details[party] = frappe.db.get_value(
|
||||||
"Customer",
|
"Customer",
|
||||||
party,
|
party,
|
||||||
@@ -1251,7 +1262,7 @@ class ReceivablePayableReport:
|
|||||||
self.add_column(label=_("Sales Person"), fieldname="sales_person", fieldtype="Data")
|
self.add_column(label=_("Sales Person"), fieldname="sales_person", fieldtype="Data")
|
||||||
|
|
||||||
if self.filters.sales_partner:
|
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":
|
if self.filters.account_type == "Payable":
|
||||||
self.add_column(
|
self.add_column(
|
||||||
|
|||||||
@@ -6,6 +6,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.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
|
||||||
from erpnext.accounts.report.accounts_receivable.accounts_receivable import execute
|
from erpnext.accounts.report.accounts_receivable.accounts_receivable import execute
|
||||||
from erpnext.accounts.test.accounts_mixin import AccountsTestMixin
|
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
|
from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order
|
||||||
from erpnext.tests.utils import ERPNextTestSuite
|
from erpnext.tests.utils import ERPNextTestSuite
|
||||||
|
|
||||||
@@ -1292,3 +1293,61 @@ class TestAccountsReceivable(ERPNextTestSuite, AccountsTestMixin):
|
|||||||
self.assertIn(original_customer, parties)
|
self.assertIn(original_customer, parties)
|
||||||
self.assertNotIn(second_customer, parties)
|
self.assertNotIn(second_customer, parties)
|
||||||
self.assertEqual(allowed_invoice.customer, original_customer)
|
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)
|
||||||
|
|||||||
@@ -132,8 +132,8 @@ class AccountsReceivableSummary(ReceivablePayableReport):
|
|||||||
if row.sales_person:
|
if row.sales_person:
|
||||||
self.party_total[row.party].sales_person.append(row.get("sales_person", ""))
|
self.party_total[row.party].sales_person.append(row.get("sales_person", ""))
|
||||||
|
|
||||||
if self.filters.sales_partner:
|
if self.filters.sales_partner and row.get("sales_partner"):
|
||||||
self.party_total[row.party]["default_sales_partner"] = row.get("default_sales_partner", "")
|
self.party_total[row.party]["sales_partner"] = row.get("sales_partner")
|
||||||
|
|
||||||
def get_columns(self):
|
def get_columns(self):
|
||||||
self.columns = []
|
self.columns = []
|
||||||
@@ -191,7 +191,7 @@ class AccountsReceivableSummary(ReceivablePayableReport):
|
|||||||
self.add_column(label=_("Sales Person"), fieldname="sales_person", fieldtype="Data")
|
self.add_column(label=_("Sales Person"), fieldname="sales_person", fieldtype="Data")
|
||||||
|
|
||||||
if self.filters.sales_partner:
|
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:
|
else:
|
||||||
self.add_column(
|
self.add_column(
|
||||||
|
|||||||
@@ -191,3 +191,42 @@ class TestAccountsReceivable(ERPNextTestSuite, AccountsTestMixin):
|
|||||||
report = execute(filters)
|
report = execute(filters)
|
||||||
rpt_output = report[1]
|
rpt_output = report[1]
|
||||||
self.assertEqual(len(rpt_output), 0)
|
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)
|
||||||
|
|||||||
Reference in New Issue
Block a user