PG patches reproductibles (idempotent): ORDER BY NULL (product.py) + CurDate (item.py heatmap) + CURRENT_DATE() filet (frappe_pg)

Volatils dans le conteneur → à ré-appliquer après rebuild. Period Closing Voucher GROUP BY = TODO (#53).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
louispaulb 2026-06-04 06:16:09 -04:00
parent 4760ae5e73
commit 536bd2dfa8

View File

@ -0,0 +1,85 @@
"""
Patchs ERPNext PostgreSQL (MySQLismes) idempotent.
======================================================
ERPNext v16 contient des requêtes SQL propres à MySQL qui cassent sur PostgreSQL.
Le shim `frappe_pg` en traduit certaines, mais (a) il s'applique paresseusement
(hook on_session_creation absent en auth par token) et (b) il a des trous.
On corrige donc À LA SOURCE, de façon idempotente.
Ces patchs modifient le code des apps DANS le conteneur VOLATILES (perdus au
rebuild d'image / recreate). À ré-exécuter après chaque rebuild, idéalement dans
CHAQUE conteneur applicatif (backend, queue-*, scheduler), puis redémarrer.
TODO persistance: cuire dans l'image (overlay) ou via un entrypoint.
Exécution (par conteneur applicatif) :
docker cp apply_pg_patches.py erpnext-backend-1:/tmp/
docker exec erpnext-backend-1 sh -lc \
"cd /home/frappe/frappe-bench && env/bin/python /tmp/apply_pg_patches.py"
docker restart erpnext-backend-1 # recharger les workers gunicorn
Patchs inclus :
1. utilities/product.py `ORDER BY NULL` retiré (validation de variante)
2. stock/.../item.py get_timeline_data : CurDate() add_to_date (heatmap fiche Item)
3. frappe_pg query_transformers CURRENT_DATE()/CURDATE() CURRENT_DATE (filet général)
À FAIRE (non encore résolu) :
- Period Closing Voucher : requête GROUP BY/ORDER BY creation invalide en PG
(GroupingError) qui bloque le submit des transactions de stock/compta. À localiser.
"""
import re
BENCH = "/home/frappe/frappe-bench/apps"
def patch_file(path, transform, marker):
try:
s = open(path).read()
except FileNotFoundError:
print("ABSENT", path)
return
if marker(s):
print("déjà patché :", path.split("/apps/")[-1])
return
ns = transform(s)
if ns == s:
print("ANCRE INTROUVABLE :", path.split("/apps/")[-1])
return
open(path, "w").write(ns)
print("PATCHED :", path.split("/apps/")[-1])
# 1) product.py — ORDER BY NULL (MySQL) invalide en PG
patch_file(
f"{BENCH}/erpnext/erpnext/utilities/product.py",
lambda s: re.sub(r"ORDER BY\s+NULL", "", s),
lambda s: not re.search(r"ORDER BY\s+NULL", s),
)
# 2) item.py — get_timeline_data : CurDate() - Interval → date calculée en Python
patch_file(
f"{BENCH}/erpnext/erpnext/stock/doctype/item/item.py",
lambda s: s.replace(
".where(sle.posting_date > CurDate() - Interval(years=1))",
".where(sle.posting_date > frappe.utils.add_to_date(frappe.utils.nowdate(), years=-1))",
1,
),
lambda s: "add_to_date(frappe.utils.nowdate(), years=-1)" in s,
)
# 3) frappe_pg — filet général CURRENT_DATE()/CURDATE() → CURRENT_DATE
QT = f"{BENCH}/frappe_pg/frappe_pg/postgres/query_transformers.py"
MARK = "# PG-FIX CURRENT_DATE()"
ADD = (
" " + MARK + "\n"
" import re as _re\n"
" query = _re.sub(r'\\bCURRENT_DATE\\s*\\(\\s*\\)', 'CURRENT_DATE', query, flags=_re.IGNORECASE)\n"
" query = _re.sub(r'\\bCURDATE\\s*\\(\\s*\\)', 'CURRENT_DATE', query, flags=_re.IGNORECASE)\n"
)
patch_file(
QT,
lambda s: s.replace(" query = convert_date_format(query)\n",
" query = convert_date_format(query)\n" + ADD, 1),
lambda s: MARK in s,
)
print("DONE — redémarrer le conteneur pour recharger les workers.")