Backend services: - targo-hub: extract deepGetValue to helpers.js, DRY disconnect reasons lookup map, compact CAPABILITIES, consolidate vision.js prompts/schemas, extract dispatch scoring weights, trim section dividers across 9 files - modem-bridge: extract getSession() helper (6 occurrences), resetIdleTimer(), consolidate DM query factory, fix duplicate username fill bug, trim headers (server.js -36%, tplink-session.js -47%, docker-compose.yml -57%) Frontend: - useWifiDiagnostic: extract THRESHOLDS const, split processDiagnostic into 6 focused helpers (processOnlineStatus, processWanIPs, processRadios, processMeshNodes, processClients, checkRadioIssues) - EquipmentDetail: merge duplicate ROLE_LABELS, remove verbose comments Documentation (17 → 13 files, -1,400 lines): - New consolidated README.md (architecture, services, dependencies, auth) - Merge ECOSYSTEM-OVERVIEW into ARCHITECTURE.md - Merge MIGRATION-PLAN + ARCHITECTURE-COMPARE + FIELD-GAP + CHANGELOG → MIGRATION.md - Merge COMPETITIVE-ANALYSIS into PLATFORM-STRATEGY.md - Update ROADMAP.md with current phase status - Delete CONTEXT.md (absorbed into README) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
148 lines
4.7 KiB
Python
148 lines
4.7 KiB
Python
"""
|
|
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")
|