* fix(controllers): source trend report labels from the master
item_name, customer_name, territory and supplier_name are stored on each
transaction and editable, so they are not functionally dependent on the grouped
key and historical documents can hold different values for the same item,
customer or supplier. Aggregating them with Max() is a text sort, and MariaDB
folds case while PostgreSQL orders by byte value, so the two engines can label
the same row differently.
Read each from its master instead. Those values ARE dependent on the grouped
key, so they can be grouped without splitting rows and agree on both engines by
construction rather than by an assumption about the data. Supplier needed no new
join -- the Supplier master was already joined as t3 for supplier_group.
A Quotation's party_name is a dynamic link to either a Customer or a Lead, so
neither master can be joined without dropping the other; there the values come
from correlated subqueries over both, keyed only on the grouped party_name.
Row counts and every numeric total are unchanged. What changes is that a
renamed record now shows its current name rather than whichever historical
snapshot happened to sort highest.
* test(selling): assert which label the trends report returns
The existing tests assert the customer stays one row but never which territory
or name comes back, so a divergence between engines passes unnoticed. Asserts
both equal the Customer master's values while an order stores a different
territory.
* fix(controllers): resolve a Quotation's party label through quotation_to
party_name is a dynamic link, so looking it up in Customer and Lead alone was
wrong twice over: a Quotation raised against a Prospect or a CRM Deal got a
blank label, and when a Lead shared its name with a Customer the Customer-first
lookup returned the wrong record's name and territory.
Resolve through the quotation_to discriminator instead, mirroring
Quotation.set_customer_name -- Customer, Lead (company_name falling back to
lead_name), Prospect, and CRM Deal. The CRM Deal branch is emitted only when its
table exists, since it ships with the CRM app.
quotation_to joins the GROUP BY as well: two parties of different types can
share a name, and merging them into one row was never right.
* style(controllers): name the quotation CASE branches
semgrep's string-concat-in-list flags adjacent string literals inside a list,
since that shape is usually a missing comma rather than deliberate. Bind each
branch to a name first so the concatenation is unambiguous.
based_wise_columns_query() and group_wise_column() in trends.py built
column labels as raw strings, so "Item", "Item Name", "Customer",
"Supplier", "Territory", "Currency", etc. never went through _() and
stayed in English regardless of the user's language, unlike the
period and total columns right next to them which were already
wrapped correctly.
With an explicit GROUP BY, a zero-match detail query returns no rows (the old bare aggregate returned one all-NULL row), so row1[0][0] would raise IndexError. Skip the entry instead.
Conservative cleanup of frappe.throw/msgprint messages per the message style
guide; meaning, severity, and .format() arguments are unchanged:
- index bare {} placeholders as {0}/{1}/... so translators can reorder
- move f-strings / .format() / concatenation out of _() (they break gettext
extraction and never translate)
- wrap translatable dynamic values (DocType/Select labels) in _()
- fix grammar and colloquialisms
- drop no-op _() wrapping runtime-built strings
Part of #53976.
The earlier parity fix aggregated the non-key descriptive columns for the Item
and Customer based-on paths but left Supplier grouping by all three selected
columns (supplier, supplier_name, supplier_group). supplier_name is a stored
per-transaction field, so historical purchase docs holding a divergent value for
the same supplier would split one supplier into multiple rows — diverging from
the original MariaDB output, which grouped by t1.supplier only.
Aggregate supplier_name with Max() and keep only supplier + the FD master column
supplier_group in GROUP BY, restoring one row per supplier on both engines.
Add regression tests for the Supplier (purchase) and Customer (sales) paths that
assert a single row per key even when stored descriptive fields diverge; both
fail on the pre-fix multi-column GROUP BY and pass after the fix, on MariaDB and
Postgres.
#56192 made the trends queries Postgres-strict-GROUP-BY-valid by widening based_on_group_by
to include the selected descriptive columns. For Item it added t2.item_name, for Customer
t1.territory (and customer_name) — but item_name is an editable per-line field and territory an
editable per-document field, not functionally dependent on the item_code/customer key. On MariaDB
(ONLY_FULL_GROUP_BY off) this SPLITS the single row per key into one row per distinct
(key, item_name)/(key, territory), so a customer transacting across two territories (or an item
with an edited item_name) now shows duplicate rows with fractured per-period subtotals.
Group by the KEY only and aggregate the non-key descriptive columns with Max(): one row per
based-on key (identical to the pre-#56192 MariaDB output) and still Postgres-valid. Supplier
columns are master-joined / fetch-locked (functionally dependent) so they stay unchanged.
- Asset Movement deletion: raw implicit-join select -> frappe.get_all on
Asset Movement Item (pluck="parent").
- validate_item_type: raw `name in (...)` select -> frappe.get_all with an
`in` filter (pluck="item_code").
Both are engine-portable, MariaDB-identical. Surgical re-apply: develop's
actual-tax distribution rewrite (distribute_actual_tax_amount / get_tax_details)
is preserved (the staging branch predated it).
validate_item_type runs on every Purchase Receipt validation (covered by
test_asset.test_purchase_asset on both engines); the Asset Movement deletion
is covered by the asset cancellation flow.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(dashboard-trends): set default fiscal year and company before validating filters Ensure and are populated with default values
* fix(dashboard-trends): ensure fiscal_year and company are properly set before validation to avoid empty filter issues
* Update erpnext/controllers/trends.py
---------
Co-authored-by: Mihir Kandoi <kandoimihir@gmail.com>
1. Sales Person wise transaction report
2. Sales Person wise target variance report
3. Sales order trend report generation for selection in group by feild