diff --git a/erpnext/accounts/report/share_balance/share_balance.py b/erpnext/accounts/report/share_balance/share_balance.py index db475a86265..b58c439a14c 100644 --- a/erpnext/accounts/report/share_balance/share_balance.py +++ b/erpnext/accounts/report/share_balance/share_balance.py @@ -22,7 +22,7 @@ def execute(filters=None): else: share_type, no_of_shares, rate, amount = 1, 2, 3, 4 - all_shares = get_all_shares(filters.get("shareholder"), filters.get("date")) + all_shares = get_all_shares(filters.get("shareholder"), filters.get("date"), filters.get("company")) for share_entry in all_shares: row = False for datum in data: @@ -61,16 +61,35 @@ def get_columns(filters): return columns -def get_all_shares(shareholder, date): +def get_all_shares(shareholder, date, company=None): """Share movements for the shareholder up to (and including) `date`, signed by direction: - shares received are positive, shares transferred/sold out are negative.""" - transfers = frappe.get_all( - "Share Transfer", - filters={"docstatus": 1, "date": ("<=", date)}, - fields=["share_type", "no_of_shares", "rate", "amount", "from_shareholder", "to_shareholder"], - order_by="date", + shares received are positive, shares transferred/sold out are negative. + + The shareholder and company predicates are pushed into the query so only the + relevant transfers are fetched instead of scanning the whole table.""" + share_transfer = frappe.qb.DocType("Share Transfer") + query = ( + frappe.qb.from_(share_transfer) + .select( + share_transfer.share_type, + share_transfer.no_of_shares, + share_transfer.rate, + share_transfer.amount, + share_transfer.from_shareholder, + share_transfer.to_shareholder, + ) + .where((share_transfer.docstatus == 1) & (share_transfer.date <= date)) + .where( + (share_transfer.to_shareholder == shareholder) | (share_transfer.from_shareholder == shareholder) + ) + .orderby(share_transfer.date) ) + if company: + query = query.where(share_transfer.company == company) + + transfers = query.run(as_dict=True) + shares = [] for transfer in transfers: if transfer.to_shareholder == shareholder: diff --git a/erpnext/accounts/report/share_balance/test_share_balance.py b/erpnext/accounts/report/share_balance/test_share_balance.py index bb6da5a133d..0b91d1525f3 100644 --- a/erpnext/accounts/report/share_balance/test_share_balance.py +++ b/erpnext/accounts/report/share_balance/test_share_balance.py @@ -42,6 +42,30 @@ class TestShareBalanceReport(ERPNextTestSuite): self.assertEqual(row[3], 10) # average rate self.assertEqual(row[4], 1000) # amount = 100 * 10 + def test_company_filter_scopes_transfers(self): + # the transfer is booked under `_Test Company` + create_share_transfer( + transfer_type="Issue", + to_shareholder=self.shareholder, + share_type=self.share_type, + from_no=1, + to_no=100, + no_of_shares=100, + rate=10, + date="2026-06-01", + ) + + # matching company: the holding shows up + self.assertEqual(self.get_row(date="2026-06-05")[2], 100) + + # a different company must not surface this shareholder's transfer + other_company_data = execute( + frappe._dict( + {"date": "2026-06-05", "company": "_Test Company 1", "shareholder": self.shareholder} + ) + )[1] + self.assertEqual(other_company_data, []) + def test_balance_increases_on_second_issue(self): create_share_transfer( transfer_type="Issue",