From 7b91566435e9acd150c70def486d4b7bda558f9f Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 7 Apr 2026 17:10:42 +0000 Subject: [PATCH] refactor: financial report template enhancements (backport #52687) (#54113) Co-authored-by: Abdeali Chharchhodawala <99460106+Abdeali099@users.noreply.github.com> --- .../account_category/account_category.json | 9 +++-- .../financial_report_template.js | 21 ++++++------ .../financial_report_template.json | 6 ++-- .../financial_report_template.py | 13 +++++++ .../financial_report_validation.py | 34 ++++++++++++------- .../test_financial_report_engine.py | 1 + 6 files changed, 56 insertions(+), 28 deletions(-) diff --git a/erpnext/accounts/doctype/account_category/account_category.json b/erpnext/accounts/doctype/account_category/account_category.json index d69d37bd78b..cc8f4103f21 100644 --- a/erpnext/accounts/doctype/account_category/account_category.json +++ b/erpnext/accounts/doctype/account_category/account_category.json @@ -26,8 +26,13 @@ ], "grid_page_length": 50, "index_web_pages_for_search": 1, - "links": [], - "modified": "2025-10-15 03:19:47.171349", + "links": [ + { + "link_doctype": "Account", + "link_fieldname": "account_category" + } + ], + "modified": "2026-02-23 01:19:49.589393", "modified_by": "Administrator", "module": "Accounts", "name": "Account Category", diff --git a/erpnext/accounts/doctype/financial_report_template/financial_report_template.js b/erpnext/accounts/doctype/financial_report_template/financial_report_template.js index 739956631fd..304da47577b 100644 --- a/erpnext/accounts/doctype/financial_report_template/financial_report_template.js +++ b/erpnext/accounts/doctype/financial_report_template/financial_report_template.js @@ -3,6 +3,8 @@ frappe.ui.form.on("Financial Report Template", { refresh(frm) { + if (frm.is_new() || frm.doc.rows.length === 0) return; + // add custom button to view missed accounts frm.add_custom_button(__("View Account Coverage"), function () { let selected_rows = frm.get_field("rows").grid.get_selected_children(); @@ -20,7 +22,7 @@ frappe.ui.form.on("Financial Report Template", { }); }, - validate(frm) { + after_save(frm) { if (!frm.doc.rows || frm.doc.rows.length === 0) { frappe.msgprint(__("At least one row is required for a financial report template")); } @@ -34,14 +36,6 @@ frappe.ui.form.on("Financial Report Row", { update_formula_label(frm, row.data_source); update_formula_description(frm, row.data_source); - if (row.data_source !== "Account Data") { - frappe.model.set_value(cdt, cdn, "balance_type", ""); - } - - if (["Blank Line", "Column Break", "Section Break"].includes(row.data_source)) { - frappe.model.set_value(cdt, cdn, "calculation_formula", ""); - } - set_up_filters_editor(frm, cdt, cdn); }, @@ -322,6 +316,8 @@ function update_formula_description(frm, data_source) { const list_style = `style="margin-bottom: var(--margin-sm); color: var(--text-muted); font-size: 0.9em;"`; const note_style = `style="margin-bottom: 0; color: var(--text-muted); font-size: 0.9em;"`; const tip_style = `style="margin-bottom: 0; color: var(--text-color); font-size: 0.85em;"`; + const code_style = `style="background: var(--bg-light-gray); padding: var(--padding-xs); border-radius: var(--border-radius); font-size: 0.85em; width: max-content; margin-bottom: var(--margin-sm);"`; + const pre_style = `style="margin: 0; border-radius: var(--border-radius)"`; let description_html = ""; @@ -382,8 +378,13 @@ function update_formula_description(frm, data_source) {
  • my_app.financial_reports.get_kpi_data
  • +
    Method Signature:
    +
    +
    def get_custom_data(filters, periods, row): 
      # filters: dict — report filters (company, period, etc.)
      # periods: list[dict] — period definitions
      # row: dict — the current report row

      return [1000.0, 1200.0, 1150.0] # one value per period
    +
    +
    Return Format:
    -

    Numbers for each period: [1000.0, 1200.0, 1150.0]

    +

    A list of numbers, one for each period: [1000.0, 1200.0, 1150.0]

    `; } else if (data_source === "Blank Line") { description_html = ` diff --git a/erpnext/accounts/doctype/financial_report_template/financial_report_template.json b/erpnext/accounts/doctype/financial_report_template/financial_report_template.json index 7383306f332..5bfd56810db 100644 --- a/erpnext/accounts/doctype/financial_report_template/financial_report_template.json +++ b/erpnext/accounts/doctype/financial_report_template/financial_report_template.json @@ -1,6 +1,5 @@ { "actions": [], - "allow_rename": 1, "autoname": "field:template_name", "creation": "2025-08-02 04:44:15.184541", "doctype": "DocType", @@ -31,7 +30,8 @@ "in_list_view": 1, "in_standard_filter": 1, "label": "Report Type", - "options": "\nProfit and Loss Statement\nBalance Sheet\nCash Flow\nCustom Financial Statement" + "options": "\nProfit and Loss Statement\nBalance Sheet\nCash Flow\nCustom Financial Statement", + "reqd": 1 }, { "depends_on": "eval:frappe.boot.developer_mode", @@ -66,7 +66,7 @@ "grid_page_length": 50, "index_web_pages_for_search": 1, "links": [], - "modified": "2025-11-14 00:11:03.508139", + "modified": "2026-02-23 01:04:05.797161", "modified_by": "Administrator", "module": "Accounts", "name": "Financial Report Template", diff --git a/erpnext/accounts/doctype/financial_report_template/financial_report_template.py b/erpnext/accounts/doctype/financial_report_template/financial_report_template.py index 69ee7e4f7dd..f30ca7b1249 100644 --- a/erpnext/accounts/doctype/financial_report_template/financial_report_template.py +++ b/erpnext/accounts/doctype/financial_report_template/financial_report_template.py @@ -32,6 +32,19 @@ class FinancialReportTemplate(Document): template_name: DF.Data # end: auto-generated types + def before_validate(self): + self.clear_hidden_fields() + + def clear_hidden_fields(self): + style_data_sources = {"Blank Line", "Column Break", "Section Break"} + + for row in self.rows: + if row.data_source != "Account Data": + row.balance_type = None + + if row.data_source in style_data_sources: + row.calculation_formula = None + def validate(self): validator = TemplateValidator(self) result = validator.validate() diff --git a/erpnext/accounts/doctype/financial_report_template/financial_report_validation.py b/erpnext/accounts/doctype/financial_report_template/financial_report_validation.py index 306fb562585..170225fa74d 100644 --- a/erpnext/accounts/doctype/financial_report_template/financial_report_validation.py +++ b/erpnext/accounts/doctype/financial_report_template/financial_report_validation.py @@ -70,8 +70,8 @@ class ValidationResult: self.warnings.append(issue) def notify_user(self) -> None: - warnings = "

    ".join(str(w) for w in self.warnings) - errors = "

    ".join(str(e) for e in self.issues) + warnings = "

    ".join(str(w) for w in self.warnings if w) + errors = "

    ".join(str(e) for e in self.issues if e) if warnings: frappe.msgprint(warnings, title=_("Warnings"), indicator="orange") @@ -99,9 +99,8 @@ class TemplateValidator: result.merge(validator.validate(self.template)) # Run row-level validations - account_fields = {field.fieldname for field in frappe.get_meta("Account").fields} for row in self.template.rows: - result.merge(self.formula_validator.validate(row, account_fields)) + result.merge(self.formula_validator.validate(row)) return result @@ -383,7 +382,8 @@ class AccountFilterValidator(Validator): """Validates account filter expressions used in Account Data rows""" def __init__(self, account_fields: set | None = None): - self.account_fields = account_fields or set(frappe.get_meta("Account")._valid_columns) + self.account_meta = frappe.get_meta("Account") + self.account_fields = account_fields or set(self.account_meta._valid_columns) def validate(self, row) -> ValidationResult: result = ValidationResult() @@ -403,7 +403,11 @@ class AccountFilterValidator(Validator): try: filter_config = json.loads(row.calculation_formula) - error = self._validate_filter_structure(filter_config, self.account_fields) + error = self._validate_filter_structure( + filter_config, + self.account_fields, + row.advanced_filtering, + ) if error: result.add_error( @@ -425,7 +429,12 @@ class AccountFilterValidator(Validator): return result - def _validate_filter_structure(self, filter_config, account_fields: set) -> str | None: + def _validate_filter_structure( + self, + filter_config, + account_fields: set, + advanced_filtering: bool = False, + ) -> str | None: # simple condition: [field, operator, value] if isinstance(filter_config, list): if len(filter_config) != 3: @@ -436,8 +445,10 @@ class AccountFilterValidator(Validator): if not isinstance(field, str) or not isinstance(operator, str): return "Field and operator must be strings" + display = (field if advanced_filtering else self.account_meta.get_label(field)) or field + if field not in account_fields: - return f"Field '{field}' is not a valid account field" + return f"Field '{display}' is not a valid Account field" if operator.casefold() not in OPERATOR_MAP: return f"Invalid operator '{operator}'" @@ -460,7 +471,7 @@ class AccountFilterValidator(Validator): # recursive for condition in conditions: - error = self._validate_filter_structure(condition, account_fields) + error = self._validate_filter_structure(condition, account_fields, advanced_filtering) if error: return error else: @@ -476,7 +487,7 @@ class FormulaValidator(Validator): self.calculation_validator = CalculationFormulaValidator(reference_codes) self.account_filter_validator = AccountFilterValidator() - def validate(self, row, account_fields: set) -> ValidationResult: + def validate(self, row) -> ValidationResult: result = ValidationResult() if not row.calculation_formula: @@ -486,9 +497,6 @@ class FormulaValidator(Validator): return self.calculation_validator.validate(row) elif row.data_source == "Account Data": - # Update account fields if provided - if account_fields: - self.account_filter_validator.account_fields = account_fields return self.account_filter_validator.validate(row) elif row.data_source == "Custom API": diff --git a/erpnext/accounts/doctype/financial_report_template/test_financial_report_engine.py b/erpnext/accounts/doctype/financial_report_template/test_financial_report_engine.py index a23c6bb6883..ef6f2785184 100644 --- a/erpnext/accounts/doctype/financial_report_template/test_financial_report_engine.py +++ b/erpnext/accounts/doctype/financial_report_template/test_financial_report_engine.py @@ -1295,6 +1295,7 @@ class TestFilterExpressionParser(FinancialReportTemplateTestCase): self.data_source = "Account Data" self.idx = 1 self.reverse_sign = 0 + self.advanced_filtering = True return MockReportRow(formula, reference_code)