mirror of
https://github.com/frappe/erpnext.git
synced 2026-08-19 01:18:43 +00:00
Convert raw `frappe.db.sql` in the Setup, Utilities, Templates and Regional areas to `frappe.qb` / the ORM so the same code runs on MariaDB and Postgres. Behaviour is preserved on MariaDB; the conversions also make these paths valid under Postgres' stricter SQL (GROUP BY, case-sensitivity, reserved words). Conversions of note (behaviour kept identical to the MariaDB original): - email_digest: ToDo ordering replicated with a CASE that mirrors MySQL `field(priority,'High','Medium','Low')` (unknown/NULL -> 0, sorts first), NULL-date-first and a `name` tie-break for a deterministic LIMIT. - company.get_all_transactions_annual_history: the cross-DocType UNION + GROUP BY is replaced by one grouped query per DocType merged with a Counter, so two different DocTypes sharing a transaction_date still collapse into one bucket. - templates/utils.send_message: contact lookup wraps both sides in LOWER() to keep MariaDB's case-insensitive email match on case-sensitive Postgres. - regional/irs_1099 & uae_vat_201: address ranking and emirate aggregation rebuilt with CASE/aggregate selects that satisfy Postgres GROUP BY, with a deterministic tie-break on the LIMIT-1 address lookups. - utilities/product.get_item_codes_by_attributes: numeric attribute values are cast with cstr() so Postgres doesn't reject `varchar = numeric`. Tests (run on both MariaDB and Postgres, --lightmode): - New: company merge test, authorization_rule duplicate-check, youtube report, templates/utils, and utilities/templates page reports (partners, rfq, material_request_info, product, utilities __init__). - Existing suites kept green: company, email_digest, transaction_deletion_record, irs_1099, uae_vat_201. Deferred (tracked separately): - setup/doctype/authorization_control.py still has raw `.format()` SELECTs; left for its own PR. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
64 lines
2.3 KiB
Python
64 lines
2.3 KiB
Python
import frappe
|
|
|
|
from erpnext.tests.utils import ERPNextTestSuite
|
|
from erpnext.utilities import update_doctypes
|
|
|
|
|
|
class TestUtilitiesInit(ERPNextTestSuite):
|
|
def test_description_child_field_query_finds_core_child_fields(self):
|
|
"""The converted query in update_doctypes() joins DocField + DocType to find
|
|
description-bearing fields on child tables (istable=1). Reproduce the exact
|
|
query and assert known core child-doctype description fields are returned."""
|
|
df = frappe.qb.DocType("DocField")
|
|
dt_table = frappe.qb.DocType("DocType")
|
|
rows = (
|
|
frappe.qb.from_(df)
|
|
.inner_join(dt_table)
|
|
.on(df.parent == dt_table.name)
|
|
.select(df.parent, df.fieldname)
|
|
.where(df.fieldname.like("%description%") & (dt_table.istable == 1))
|
|
.run(as_dict=1)
|
|
)
|
|
|
|
# Map parent -> set of matched fieldnames for concrete assertions.
|
|
matched = {}
|
|
for d in rows:
|
|
matched.setdefault(d.parent, set()).add(d.fieldname)
|
|
|
|
# Known core child tables (istable=1) carrying a "description" field.
|
|
self.assertIn("Sales Invoice Item", matched)
|
|
self.assertIn("description", matched["Sales Invoice Item"])
|
|
|
|
self.assertIn("Purchase Invoice Item", matched)
|
|
self.assertIn("description", matched["Purchase Invoice Item"])
|
|
|
|
# Every returned fieldname must satisfy the LIKE predicate, and every
|
|
# returned parent must genuinely be a child table (istable=1) -- guards
|
|
# against the join/where being dropped during the qb conversion.
|
|
for d in rows:
|
|
self.assertIn("description", d.fieldname)
|
|
parents = {d.parent for d in rows}
|
|
istable_map = dict(
|
|
frappe.get_all(
|
|
"DocType",
|
|
filters={"name": ("in", list(parents))},
|
|
fields=["name", "istable"],
|
|
as_list=1,
|
|
)
|
|
)
|
|
for parent in parents:
|
|
self.assertEqual(
|
|
istable_map.get(parent),
|
|
1,
|
|
msg=f"{parent} returned by description-child query but is not a child table",
|
|
)
|
|
|
|
def test_update_doctypes_is_importable_and_callable(self):
|
|
"""update_doctypes() is the public entry point exercising the converted
|
|
query; ensure it imports and runs without error against real schema."""
|
|
self.assertTrue(callable(update_doctypes))
|
|
# Run it: it should only ever upgrade Text/Small Text description fields to
|
|
# Text Editor; core fixtures used above are already Text Editor, so this is
|
|
# effectively a no-op but must not raise.
|
|
update_doctypes()
|