Skip to main content

ERPNext how-to & fixes

The ERPNext v15.46 Balance Sheet / P&L / Cash Flow Crash — and How to Patch Your Instance

ERPNext v15.46.0 and v14.78.3 crashed Balance Sheet, P&L and Cash Flow with AttributeError: 'str' object has no attribute 'get_sql'. Fixed the same day.

Updated 12 Oct 20269 min readBy ManojFirst published 24 Jul 2026

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 versionWhat to do
v14.78.2 or earlier, v15.45.x or earlierNo: the change that caused it is not thereNothing for this bug
v14.78.3YesMove to v14.78.4 or later (v14 itself reached end of life on 31 January 2026, so plan v15/v16)
v15.46.0YesMove to v15.46.1 or any later v15 release
v15.46.1 and later, v14.78.4 and laterNo: fixedNothing for this bug
Any v16 releaseNoNothing 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:

Python
# 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.

Python
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.

Code recipePatch Command Recipe

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.

Patching upstream is fragile. The safer play is to leap-frog to a tag that already carries the fix. Use these recipes when you are pinned to v15.46.0 by a customisation and cannot yet upgrade — not as a routine maintenance pattern.

The 'clean' path: roll the whole bench forward to a tag that includes the fix. Safer than cherry-pick because it does not leave your fork half-merged, but you inherit every other change in the release.

bench.sh
# Roll the erpnext app forward to a tag that includes the fix
cd ~/frappe-bench

# 1. Verify what tag your site is on
bench version --format json | grep erpnext

# 2. Bump the branch/tag in apps.txt if you're pinned; otherwise:
bench switch-to-branch version-15 erpnext --upgrade   # or version-16 for the v16 line

# 3. Full update — pulls, migrates, builds, restarts
bench update --patch    # skips backup step; run 'bench backup' first, always

# --- Recommended pre-flight, do not skip ---
bench --site <your-site> backup --with-files
# --- After update, verify the reports actually run ---
bench --site <your-site> console <<'PY'
from erpnext.accounts.report.balance_sheet.balance_sheet import execute
execute({"company": "<your-company>",
         "from_date": "2026-04-01", "to_date": "2026-06-30",
         "periodicity": "Monthly", "accumulated_values": 1,
         "presentation_currency": "INR"})
PY

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?

1

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.

2

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.

3

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.

4

Migrate and rebuild

bench --site your-site migrate, bench build --app erpnext, then bench restart so workers pick up the new code.

5

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.

6

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:

1

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.

2

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.

3

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.

Code recipeNightly Regression Guardrail

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.

Runs at 03:15 IST

Well before anyone opens the desk. Finance sees a red email at breakfast, not a broken report at 22:00 on the 30th.

Zero clicks per day

The scheduler owns it. No maintenance window; no dependency on a human remembering to smoke-test after every bench update.

Heartbeat on green

Writes a timestamp when all three pass so you can prove the check ran on audit day. Optional but free.

fs_nightly_smoke_test.py
# Server Script → Type: Scheduler Event → Frequency: Daily (03:15 IST is fine)
# Runs each of the three financial statements against the current fiscal year and
# emails finance + ops if any of them raises. A future regression is then caught
# a day after it hits your bench, not on the 30th at 22:00.

import frappe
from frappe.utils import getdate, get_first_day, get_last_day
from erpnext.accounts.report.balance_sheet.balance_sheet import execute as bs_exec
from erpnext.accounts.report.profit_and_loss_statement.profit_and_loss_statement import execute as pl_exec
from erpnext.accounts.report.cash_flow.cash_flow import execute as cf_exec

COMPANY = frappe.db.get_single_value("Global Defaults", "default_company")
today = getdate()
frm = get_first_day(today).isoformat()
to  = get_last_day(today).isoformat()
filters = {
    "company": COMPANY,
    "from_date": frm,
    "to_date": to,
    "periodicity": "Monthly",
    "accumulated_values": 1,
    "presentation_currency": frappe.db.get_value("Company", COMPANY, "default_currency"),
}

failures = []
for label, fn in [("Balance Sheet", bs_exec),
                  ("Profit & Loss", pl_exec),
                  ("Cash Flow",     cf_exec)]:
    try:
        fn(filters.copy())
    except Exception as e:
        failures.append((label, type(e).__name__, str(e)[:400]))

if failures:
    rows = "".join(
        f"<tr><td>{r[0]}</td><td>{r[1]}</td><td><pre>{frappe.utils.escape_html(r[2])}</pre></td></tr>"
        for r in failures
    )
    frappe.sendmail(
        recipients=["finance@yourco.in", "ops-lead@yourco.in"],
        subject=f"[ERPNext] {len(failures)} financial statement(s) crashed on nightly check",
        message=(
            "<p>Nightly smoke-test of Balance Sheet / P&L / Cash Flow reports failed. "
            "Do not close month-end until this clears.</p>"
            f"<table border=1 cellpadding=6><tr><th>Report</th><th>Exception</th>"
            f"<th>Message</th></tr>{rows}</table>"
        ),
    )
else:
    # Optional: write a heartbeat so you can prove the check ran on audit day
    frappe.db.set_single_value("System Settings", "reports_last_verified_at", frappe.utils.now())

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 immediatelyBacklog and fix the process
You are on v15.46.0 or v14.78.3 and month-end is within 7 daysYou are on another release and have no upgrade planned this quarter
Audit fieldwork is within 30 daysYou are already on v16 and triaging an unrelated issue
You do not yet have the nightly guardrail installedThe nightly guardrail has been green for 30+ days
Ops does not know which release the bench is onOps 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.

At a glance

What to know
SymptomBalance Sheet, P&L and Cash Flow fail with AttributeError: 'str' object has no attribute 'get_sql'
AffectedERPNext v15.46.0 and v14.78.3 (18 December 2024); v16 never affected
CauseMatch conditions added to a pypika query object as a string before query.walk() (PR #44695)
Fixv15.46.1 / v14.78.4 or later (PR #44765), released the same day
PreventNightly 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.

Your next step

Statement reports broken with month-end days away?

Next step · ERPNext how-to & fixes

Now see what an audit finds in yours

When the same problems keep coming back, tick what you see and get an audit of the ERPNext you have, then fixes in order of what costs you most.

Comments & ratings

No comments yet. Start a new discussion.

0

Manoj · Published on 24 July 2026

Worked with us? Leave a Google review

Questions about this? Talk to the team.

Book a 30-minute Google Meet, WhatsApp us, or send a message. The engineer who works on this replies.

Send us a message
Your details go to our team's CRM and are used only to reply.