""" ERPNext v16 PostgreSQL GROUP BY fixes. PostgreSQL requires all non-aggregated SELECT columns in GROUP BY. MySQL is lenient, ERPNext was built for MySQL. Run: docker cp fix_pg_groupby.py erpnext-backend-1:/tmp/ docker exec erpnext-backend-1 bench --site erp.gigafibre.ca execute "exec(open('/tmp/fix_pg_groupby.py').read())" """ import os BENCH = "/home/frappe/frappe-bench/apps/erpnext/erpnext" patches = [] # ─── 1. controllers/trends.py — based_on_group_by for Customer, Supplier, Item ─── patches.append(( f"{BENCH}/controllers/trends.py", [ # Customer (non-Quotation): group by needs customer_name, territory ( 'based_on_details["based_on_group_by"] = "t1.party_name" if trans == "Quotation" else "t1.customer"', 'based_on_details["based_on_group_by"] = "t1.party_name, t1.customer_name, t1.territory" if trans == "Quotation" else "t1.customer, t1.customer_name, t1.territory"', ), # Supplier: group by needs supplier_name, supplier_group ( 'based_on_details["based_on_group_by"] = "t1.supplier"', 'based_on_details["based_on_group_by"] = "t1.supplier, t1.supplier_name, t3.supplier_group"', ), # Item: group by needs item_name ( 'based_on_details["based_on_group_by"] = "t2.item_code"', 'based_on_details["based_on_group_by"] = "t2.item_code, t2.item_name"', ), ] )) # ─── 2. trial_balance.py — account_currency missing from groupby ─── patches.append(( f"{BENCH}/accounts/report/trial_balance/trial_balance.py", [ ( ".groupby(closing_balance.account)", ".groupby(closing_balance.account, closing_balance.account_currency)", ), ] )) # ─── 3. process_period_closing_voucher.py — account_currency ─── patches.append(( f"{BENCH}/accounts/doctype/process_period_closing_voucher/process_period_closing_voucher.py", [ # Add account_currency to groupby after account ( "query = query.groupby(gle.account)", "query = query.groupby(gle.account, gle.account_currency)", ), ] )) # ─── 4. exchange_rate_revaluation.py — account_currency ─── patches.append(( f"{BENCH}/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.py", [ ( '.groupby(gle.account, NullIf(gle.party_type, ""), NullIf(gle.party, ""))', '.groupby(gle.account, NullIf(gle.party_type, ""), NullIf(gle.party, ""), gle.account_currency)', ), ] )) # ─── 5. pos_closing_entry.py — account missing ─── patches.append(( f"{BENCH}/accounts/doctype/pos_closing_entry/pos_closing_entry.py", [ ( ".groupby(SalesInvoicePayment.mode_of_payment)", ".groupby(SalesInvoicePayment.mode_of_payment, SalesInvoicePayment.account)", ), ] )) # ─── 6. voucher_wise_balance.py — voucher_type missing ─── patches.append(( f"{BENCH}/accounts/report/voucher_wise_balance/voucher_wise_balance.py", [ ( ".groupby(gle.voucher_no)", ".groupby(gle.voucher_no, gle.voucher_type)", ), ] )) # ─── 7. total_stock_summary.py — item.description missing ─── patches.append(( f"{BENCH}/stock/report/total_stock_summary/total_stock_summary.py", [ ( ".groupby(item.item_code)", ".groupby(item.item_code, item.description)", ), ] )) # ─── 8. stock_entry.py — job_card_scrap_item missing cols ─── patches.append(( f"{BENCH}/stock/doctype/stock_entry/stock_entry.py", [ ( ".groupby(job_card_scrap_item.item_code)", ".groupby(job_card_scrap_item.item_code, job_card_scrap_item.item_name, job_card_scrap_item.description, job_card_scrap_item.stock_uom)", ), ] )) # ─── Apply patches ─── applied = 0 skipped = 0 errors = 0 for filepath, replacements in patches: if not os.path.exists(filepath): print(f"SKIP (not found): {filepath}") skipped += 1 continue with open(filepath, "r") as f: content = f.read() modified = False for old, new in replacements: if old in content: content = content.replace(old, new, 1) modified = True print(f" PATCHED: {old[:60]}...") elif new in content: print(f" ALREADY: {new[:60]}...") else: print(f" NOT FOUND in {filepath}: {old[:60]}...") errors += 1 if modified: with open(filepath, "w") as f: f.write(content) applied += 1 print(f"OK: {filepath}") print(f"\nDone: {applied} files patched, {skipped} skipped, {errors} not found")