On ERPNext v15.46.0, opening the Balance Sheet, Profit & Loss Statement or Cash Flow report throws AttributeError: 'str' object has no attribute 'get_sql' instead of returning rows. The failing call is query.walk() in get_accounting_entries in erpnext/accounts/report/financial_statements.py. The bug shipped in v15.46.0 and v14.78.3 on 18 December 2024 and was fixed hours later in v15.46.1 and v14.78.4. This post shows the traceback, tells you whether your bench is affected, gives the patch routes, and ends with a nightly guardrail so the next regression is caught the day after it lands, not on the 30th at 22:00.
Financial-statement regressions are the worst kind of ERP bug to find during audit fieldwork. I lead ERPNext and Frappe implementations at MithTech, a team of Frappe specialists in Bengaluru. This playbook came out of triaging exactly that situation on a client bench.
Are you affected?
Check the exact tag first:
That returns the release the app is on, not just the branch name.
| ERPNext version | What to do | |
|---|---|---|
| v14.78.2 or earlier, v15.45.x or earlier | No: the change that caused it is not there | Nothing for this bug |
| v14.78.3 | Yes | Move to v14.78.4 or later (v14 itself reached end of life on 31 January 2026, so plan v15/v16) |
| v15.46.0 | Yes | Move to v15.46.1 or any later v15 release |
| v15.46.1 and later, v14.78.4 and later | No: fixed | Nothing for this bug |
| Any v16 release | No | Nothing for this bug |
If your tag is not one of the two affected releases and the reports still fail, the fault is elsewhere: check permissions, the fiscal year setup or the account tree first.
What actually broke?
The three reports share one helper, get_accounting_entries in erpnext/accounts/report/financial_statements.py. On 17 December 2024, PR #44695 made the financial statements respect User Permissions by adding the user's match conditions to the GL Entry query. In v15.46.0 the end of that helper read:
# erpnext/accounts/report/financial_statements.py (v15.46.0)
from frappe.desk.reportview import build_match_conditions
match_conditions = build_match_conditions(doctype)
if match_conditions:
query += "and" + match_conditions # query is still a pypika object here
query, params = query.walk()
return frappe.db.sql(query, params, as_dict=True)
query was still a query-builder object when the string was added to it. pypika reads + on a query as a set operation, so the string became the "other query" in a union, and query.walk() failed when it asked that string for its SQL:
File "apps/erpnext/erpnext/accounts/report/financial_statements.py", line 537, in get_accounting_entries
query, params = query.walk()
...
AttributeError: 'str' object has no attribute 'get_sql'
That is the traceback in issue #44766, "After Update to v15.46.0 (version-15) - All Financial Report Can't Open". The branch only runs when build_match_conditions returns something, so it hits users whose permissions add conditions, which on most finance teams means the people who actually run the reports.
The fix, PR #44765, moves one line: turn the query into SQL first, then append the match conditions to the string.
query, params = query.walk()
match_conditions = build_match_conditions(doctype)
if match_conditions:
query += "and" + match_conditions
return frappe.db.sql(query, params, as_dict=True)
It merged on 18 December 2024 and shipped in v15.46.1 and v14.78.4 the same day.
How do you patch it, in order of safety?
The recipes below run from least to most invasive. Prefer rolling the app forward to a fixed release; cherry-pick only when a customisation pins you to v15.46.0. The Docker tab is the same idea for a production bench inside frappe_docker.
Three copy-paste recipes for landing the fix on your bench: cherry-pick, bench update, or Docker rebuild. All include a smoke-test step so you verify the reports actually run before anyone closes month-end.
For the cherry-pick route on v15, use the commit from PR #44770, the version-15-hotfix backport of PR #44765 (v14: PR #44769); copy the SHA from the PR page.
Check the Company defaults before you smoke-test
Financial statements read GL Entries and the account tree. On a fresh install, check the Company master has default_currency set (it drives the presentation-currency default) and, if you post in foreign currency, that default_receivable_account and default_payable_account are filled. Blank values give mysteriously empty rows rather than an error, which people confuse with this regression after they patch and still see an empty Balance Sheet.
How do you verify the fix?
Confirm the diagnosis
bench version --format json | grep erpnext gives the exact release. If it reads v15.46.0 (or v14.78.3) and the three reports raise AttributeError: 'str' object has no attribute 'get_sql', this is the bug. Any other release or error: look elsewhere.
Back up before you touch anything
bench --site your-site backup --with-files. bench update --patch skips the automatic backup, so take it yourself and check the SQL dump and files archive are on disk.
Roll forward or cherry-pick
Prefer bench switch-to-branch version-15 erpnext --upgrade followed by bench update, which puts you on the latest v15 release, or move to version-16. Cherry-pick the PR #44765 commit inside apps/erpnext only if you cannot move the whole app. Docker: bump the image tag in your compose or values override, docker compose pull, then migrate.
Migrate and rebuild
bench --site your-site migrate, bench build --app erpnext, then bench restart so workers pick up the new code.
Smoke-test the three reports
Repeat for profit_and_loss_statement.profit_and_loss_statement.execute and cash_flow.cash_flow.execute. Then open each report from the desk as a user with User Permissions: bench execute runs as Administrator, and the bug lived on the permissions path.
Install the nightly guardrail
Deploy the server script below. It runs the three reports every night, emails finance and ops on failure, and writes a heartbeat on green.
Who owns what on a bench with a real ops team?
The steps above assume one person owns bench maintenance, the month-end close and the patch decision. That works for a 20-user SME. Beyond that the responsibilities split, and the split matters:
Ops owns the patch
Version choice, backup, bench update or cherry-pick, image rebuild, migration. Ops treats a broken financial statement as a top-priority incident and does not defer it to the next maintenance window, because there isn't one before month-end.
Finance owns the sign-off
Ops does not close the ticket until finance opens Balance Sheet, P&L and Cash Flow from the desk, against the current fiscal year, and confirms the numbers match expectations. A green console run proves the crash is gone; it does not prove the numbers are right.
Both own the guardrail's inbox
Send the alert to a shared list, not one person's inbox. A regression caught at 03:30 on a Saturday should reach whoever is on call in ops and whoever is closing the month. We route it to a chat channel via a webhook, with email as the fallback.
Write this split down even for a five-person team: "who runs the patch" and "who decides it landed correctly" are different roles in every audit framework.
How do you harden against the next one?
v15.46 will not be the last change to break a statement report. ERPNext is under active development, and now and then a fix ships before every caller is tested; this one was caught and fixed within hours, but only because users reported it. The sensible posture is not "audit every commit" but "run each statement report once a night and shout if one raises". Forty lines of server script, no external dependency.
Server script (Scheduler Event, Daily) that runs Balance Sheet, P&L and Cash Flow against the current fiscal year and emails finance + ops on any exception.
The script runs from Frappe's own scheduler, so it stays inside the bench and works even if outbound network access is firewalled. Run it as a user with typical finance permissions if you can; this bug only showed on the permissions path.
When should you hold a close?
If the guardrail sends the red email at 03:30 on the 29th and you close on the 30th, what is the right response? This draft is a starting point for your CFO and auditor to agree, not a rule:
Statement-Regression Response Policy (draft for auditor / CFO review): If Balance Sheet, Profit & Loss Statement or Cash Flow raises an exception on the nightly smoke test, put the current month-end on hold until Ops confirms a fix has landed and Finance has re-verified all three reports from the desk. The hold applies whether or not the failing report is on this close's checklist: a broken shared helper means every number derived from it (management dashboards, board pack, GST reconciliation drafts) must be re-verified. Escalation contact: on-call ERPNext ops lead. Target time to fix: < 24 hours for a regression with a known upstream patch; < 72 hours otherwise.
Two notes on writing it down rather than leaving it to judgement on the day:
- Auditors prefer a policy to improvisation. A written response, even a conservative one, is easier to defend at year-end than "we noticed and fixed it before close".
- The 24 and 72 hour targets are placeholders. A business closing on a 24-hour cycle needs tighter numbers; one with a five-day close window can loosen them. Anchor them to your existing incident-response targets.
Should you fix it now or backlog it?
| Fix immediately | Backlog and fix the process |
|---|---|
| You are on v15.46.0 or v14.78.3 and month-end is within 7 days | You are on another release and have no upgrade planned this quarter |
| Audit fieldwork is within 30 days | You are already on v16 and triaging an unrelated issue |
| You do not yet have the nightly guardrail installed | The nightly guardrail has been green for 30+ days |
| Ops does not know which release the bench is on | Ops keeps a version log and change record |
Either way, the process fix, the nightly guardrail, is a single deploy and silent on green days. Install it.
What related issues might you hit?
- ERPNext bank reconciliation: the interactive fix guide: another accounting workflow with silent-drift failure modes.
- ERPNext Landed Cost & FX Gone Wrong: stock-valuation drift from stale exchange rates on the GRN.
- ERPNext BOM cost on Item master out of sync: how the BOM-to-Item cost link drifts on manufacturing benches.
- Tuning MariaDB for a real ERPNext workload: a report that crashes can hide a report that also times out.
- Real ERPNext bugs and their real fixes: triage patterns for other regressions from GitHub and the forum.
At a glance
| What to know | |
|---|---|
| Symptom | Balance Sheet, P&L and Cash Flow fail with AttributeError: 'str' object has no attribute 'get_sql' |
| Affected | ERPNext v15.46.0 and v14.78.3 (18 December 2024); v16 never affected |
| Cause | Match conditions added to a pypika query object as a string before query.walk() (PR #44695) |
| Fix | v15.46.1 / v14.78.4 or later (PR #44765), released the same day |
| Prevent | Nightly server script that runs the three reports and alerts on failure |
FAQ: what do people ask most?
+What ERPNext versions are affected by this financial statements crash?
ERPNext v15.46.0 and v14.78.3, both tagged on 18 December 2024. v15.46.1 and v14.78.4, released the same day, fix it, and no v16 release has it. Run bench version --format json | grep erpnext to confirm your exact release.
+How do I fix the Balance Sheet AttributeError in ERPNext v15.46 without upgrading to v16?
Move to any later v15 release: bench switch-to-branch version-15 erpnext --upgrade, then bench update. If a customisation pins you to v15.46.0, cherry-pick the PR #44765 commit into apps/erpnext. Docker deployments should bump the image tag rather than patch a running container.
+Why does this bug only affect Balance Sheet, P&L and Cash Flow, and not the General Ledger report?
Because those three share one helper, get_accounting_entries in erpnext/accounts/report/financial_statements.py, and the regression sat inside it. The General Ledger report builds its own query, so it kept running.
+Why could Administrator open the reports when other users could not?
The broken line only runs when build_match_conditions returns conditions for the user. A user with no permission restrictions gets none, so the query never takes the broken path; a finance user with User Permissions does.
+What happens if we skip the patch and just avoid opening the reports?
The month-end close cannot be signed off without the balance sheet and P&L, audit fieldwork stalls at the first request, and board packs go out incomplete. The answer is not "don't open the reports"; it is "patch or upgrade so they run".
+Can I edit financial_statements.py directly on the running bench and skip git?
Technically yes; in practice no. Files under apps/erpnext are tracked by git, and the next bench update overwrites a hand-edit without warning. Cherry-pick or roll forward; both survive the update.
+Will the nightly guardrail work on a Docker deployment?
Yes. It is a Frappe Server Script run by the built-in scheduler, which lives in the scheduler container. It has no external dependencies, so it behaves the same on a bench install and a Docker install.
+How do I know the fix landed correctly?
Run all three reports with the smoke-test command in step 5, then have a finance user with User Permissions open each one from the desk and check the totals against what they expect. The console run proves the crash is gone; the human check proves the numbers are right.
What should you do next?
Statement-breaking regressions happen: the code moves fast, and now and then a fix ships before every path is tested. The lesson from v15.46 is not that ERPNext is unstable; it was fixed within hours. It is that a self-hosted ERP with month-end obligations needs a nightly smoke test the way a production API needs a health check. Install the guardrail once, and the next regression becomes an ops-day problem instead of an audit-day one.
Comments & ratings
No comments yet. Start a new discussion.
Manoj · Published on 24 July 2026
Worked with us? Leave a Google review