From 303b7795abc2262d56f2f73784e4bb9faf978d9c Mon Sep 17 00:00:00 2001 From: Sendipad Date: Sun, 22 Feb 2026 02:38:02 +0300 Subject: [PATCH] feat: enhance transaction health tracking and fix dashboard stats card --- uph/__init__.py | 2 +- uph/hooks.py | 4 +- uph/party/controllers/transaction_health.py | 65 ++-- .../party_master_settings_doctype.json | 311 ++++++++++-------- .../data_quality_dashboard.js | 7 +- 5 files changed, 209 insertions(+), 180 deletions(-) diff --git a/uph/__init__.py b/uph/__init__.py index 6ae87a47..5118352a 100644 --- a/uph/__init__.py +++ b/uph/__init__.py @@ -5,7 +5,7 @@ ) from uph.party.controllers.cache_utils import SmartCache -__version__ = "3.1.0" +__version__ = "3.1.5" # ---------------------------------------------------------------------------- diff --git a/uph/hooks.py b/uph/hooks.py index dde0ef58..8c9ada80 100644 --- a/uph/hooks.py +++ b/uph/hooks.py @@ -109,10 +109,10 @@ scheduler_events = { "hourly": [ "uph.tasks.refresh_dashboard_stats", - "uph.party.controllers.unlinked_resolver.enqueue_unlinked_issue_scan", - "uph.party.controllers.transaction_health.enqueue_transaction_policy_scan", ], "daily": [ + "uph.party.controllers.unlinked_resolver.enqueue_unlinked_issue_scan", + "uph.party.controllers.transaction_health.enqueue_transaction_policy_scan", "uph.party.controllers.duplicate_scanner.enqueue_duplicate_scan", ], } diff --git a/uph/party/controllers/transaction_health.py b/uph/party/controllers/transaction_health.py index 5606465e..fbf599b3 100644 --- a/uph/party/controllers/transaction_health.py +++ b/uph/party/controllers/transaction_health.py @@ -322,6 +322,11 @@ def run_transaction_policy_scan(): for dt_info in tx_doctypes: dt = dt_info.get("document_type") parent_dt = dt_info.get("parent_doctype") or dt + track = dt_info.get("track_transaction_health", "Include") + configured_severity = dt_info.get("transaction_health_severity", "High") + + if track == "Ignore": + continue if not dt or not frappe.db.exists("DocType", dt): continue @@ -356,11 +361,10 @@ def run_transaction_policy_scan(): break for d in drafts: age_days = max(1, (now_datetime() - d.creation).days) - severity = "Medium" if age_days <= draft_days * 2 else "High" create_party_issue_if_missing( party=d.party_master, issue_type="Transaction Policy", - severity=severity, + severity=configured_severity, status="Open", source_engine="transaction_health", reference_doctype=parent_dt, @@ -413,7 +417,7 @@ def run_transaction_policy_scan(): create_party_issue_if_missing( party=r.party_master, issue_type="Transaction Policy", - severity="High", + severity=configured_severity, status="Open", source_engine="transaction_health", reference_doctype=parent_dt, @@ -446,7 +450,7 @@ def run_transaction_policy_scan(): create_party_issue_if_missing( party=row.party_master, issue_type="Transaction Policy", - severity="High", + severity=configured_severity, status="Open", source_engine="transaction_health", reference_doctype=parent_dt, @@ -484,7 +488,7 @@ def run_transaction_policy_scan(): create_party_issue_if_missing( party=row.party_master, issue_type="Transaction Policy", - severity="High", + severity=configured_severity, status="Open", source_engine="transaction_health", reference_doctype=parent_dt, @@ -590,7 +594,8 @@ def rebuild_health_cache(): def _get_transaction_doctypes(): """ Get the list of transaction DocTypes configured in Party Master Settings. - Returns list of dicts with 'document_type' and 'parent_doctype' keys. + Returns list of dicts with 'document_type', 'parent_doctype', 'track_transaction_health', + and 'transaction_health_severity' keys. """ try: settings = frappe.get_cached_doc("Party Master Settings") @@ -598,23 +603,22 @@ def _get_transaction_doctypes(): for d in settings.document_types or []: dt = d.get("document_type") parent_dt = d.get("parent_doctype") or dt + track = d.get("track_transaction_health", "Include") + severity = d.get("transaction_health_severity", "High") + if dt and not frappe.get_meta(dt).issingle: - tx_doctypes.append({"document_type": dt, "parent_doctype": parent_dt}) + tx_doctypes.append( + { + "document_type": dt, + "parent_doctype": parent_dt, + "track_transaction_health": track, + "transaction_health_severity": severity, + } + ) # Fallback to sensible defaults when nothing is configured if not tx_doctypes: - return [ - {"document_type": "Sales Invoice", "parent_doctype": "Sales Invoice"}, - { - "document_type": "Purchase Invoice", - "parent_doctype": "Purchase Invoice", - }, - {"document_type": "Payment Entry", "parent_doctype": "Payment Entry"}, - { - "document_type": "Journal Entry Account", - "parent_doctype": "Journal Entry", - }, - ] + return _get_fallback_transaction_doctypes() # Deduplicate by document_type seen = set() @@ -626,12 +630,17 @@ def _get_transaction_doctypes(): return unique except Exception: # Fallback - return [ - {"document_type": "Sales Invoice", "parent_doctype": "Sales Invoice"}, - {"document_type": "Purchase Invoice", "parent_doctype": "Purchase Invoice"}, - {"document_type": "Payment Entry", "parent_doctype": "Payment Entry"}, - { - "document_type": "Journal Entry Account", - "parent_doctype": "Journal Entry", - }, - ] + return _get_fallback_transaction_doctypes() + + +def _get_fallback_transaction_doctypes(): + defaults = [ + {"document_type": "Sales Invoice", "parent_doctype": "Sales Invoice"}, + {"document_type": "Purchase Invoice", "parent_doctype": "Purchase Invoice"}, + {"document_type": "Payment Entry", "parent_doctype": "Payment Entry"}, + {"document_type": "Journal Entry Account", "parent_doctype": "Journal Entry"}, + ] + for d in defaults: + d["track_transaction_health"] = "Include" + d["transaction_health_severity"] = "High" + return defaults diff --git a/uph/party/doctype/party_master_settings_doctype/party_master_settings_doctype.json b/uph/party/doctype/party_master_settings_doctype/party_master_settings_doctype.json index c14d8e61..fdbb455c 100644 --- a/uph/party/doctype/party_master_settings_doctype/party_master_settings_doctype.json +++ b/uph/party/doctype/party_master_settings_doctype/party_master_settings_doctype.json @@ -1,148 +1,167 @@ { - "actions": [], - "allow_rename": 1, - "creation": "2025-03-26 23:19:50.940952", - "doctype": "DocType", - "editable_grid": 1, - "engine": "InnoDB", - "field_order": [ - "document_type", - "parent_doctype", - "is_system_generated", - "document_categories", - "enabled", - "client_script", - "column_break_brmi", - "reqd", - "is_dynamic_party_type", - "party_fieldname", - "party_type", - "party_type_fieldname", - "party_master_custom_field", - "warn_not_submitted_document" - ], - "fields": [ - { - "fieldname": "document_type", - "fieldtype": "Link", - "in_list_view": 1, - "label": "Document Type", - "options": "DocType", - "read_only_depends_on": "eval:doc.is_system_generated===1", - "reqd": 1, - "search_index": 1 - }, - { - "fieldname": "parent_doctype", - "fieldtype": "Link", - "label": "Parent DocType", - "link_filters": "[[\"DocType\",\"istable\",\"=\",0]]", - "options": "DocType", - "read_only_depends_on": "eval:doc.is_system_generated===1", - "search_index": 1 - }, - { - "default": "0", - "fieldname": "is_dynamic_party_type", - "fieldtype": "Check", - "in_list_view": 1, - "label": "Is Dynamic Party Type", - "read_only_depends_on": "doc.is_system_generated" - }, - { - "fieldname": "party_fieldname", - "fieldtype": "Select", - "label": "Party FieldName", - "read_only_depends_on": "eval:doc.is_system_generated===1", - "reqd": 1 - }, - { - "depends_on": "eval:doc.is_dynamic_party_type===0", - "description": "If Party Type is constant Type for this Document Type", - "fieldname": "party_type", - "fieldtype": "Link", - "in_list_view": 1, - "label": "Party Type", - "mandatory_depends_on": "eval:doc.is_dynamic_party_type===0", - "options": "Party Type" - }, - { - "depends_on": "eval:doc.is_dynamic_party_type===1", - "fieldname": "party_type_fieldname", - "fieldtype": "Select", - "label": "Party Type Fieldname", - "mandatory_depends_on": "eval:doc.is_dynamic_party_type===1" - }, - { - "default": "1", - "fieldname": "reqd", - "fieldtype": "Check", - "in_list_view": 1, - "label": "Mandatory" - }, - { - "depends_on": "eval:!doc.enabled", - "description": "Copy Script above code to client script on your custom Doctype and Enabled it", - "fieldname": "client_script", - "fieldtype": "Link", - "label": "Client Script", - "options": "Client Script" - }, - { - "default": "0", - "fieldname": "is_system_generated", - "fieldtype": "Check", - "hidden": 1, - "label": "Is System generated", - "read_only": 1 - }, - { - "fieldname": "document_categories", - "fieldtype": "Select", - "label": "Document Categories", - "options": "\nSelling DocType\nPurchasing DocType\nDynamic type As Parent\nDynamic Type As Child\nEmployee DocType", - "read_only_depends_on": "doc.is_system_generated" - }, - { - "fieldname": "party_master_custom_field", - "fieldtype": "Link", - "label": "Party Master Custom Field", - "options": "Custom Field", - "search_index": 1 - }, - { - "default": "1", - "description": "If unchecked there will be no default script applied to this Doctype or the custom client script", - "fieldname": "enabled", - "fieldtype": "Check", - "label": "Enabled", - "search_index": 1 - }, - { - "fieldname": "column_break_brmi", - "fieldtype": "Column Break" - }, - { - "default": "0", - "description": "Checked will show warn message if Draft or Cancelled(Not Edited) Document exists", - "fieldname": "warn_not_submitted_document", - "fieldtype": "Check", - "label": "Warn If Un-Submitted Documents" - } - ], - "grid_page_length": 50, - "index_web_pages_for_search": 1, - "istable": 1, - "links": [], - "modified": "2025-11-15 19:33:31.396245", - "modified_by": "Administrator", - "module": "Party", - "name": "Party Master Settings DocType", - "owner": "Administrator", - "permissions": [], - "row_format": "Dynamic", - "rows_threshold_for_grid_search": 20, - "sort_field": "modified", - "sort_order": "DESC", - "states": [] + "actions": [], + "allow_rename": 1, + "creation": "2025-03-26 23:19:50.940952", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "document_type", + "document_type", + "parent_doctype", + "track_transaction_health", + "transaction_health_severity", + "is_system_generated", + "document_categories", + "enabled", + "client_script", + "column_break_brmi", + "reqd", + "is_dynamic_party_type", + "party_fieldname", + "party_type", + "party_type_fieldname", + "party_master_custom_field", + "warn_not_submitted_document" + ], + "fields": [ + { + "fieldname": "document_type", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Document Type", + "options": "DocType", + "read_only_depends_on": "eval:doc.is_system_generated===1", + "reqd": 1, + "search_index": 1 + }, + { + "fieldname": "parent_doctype", + "fieldtype": "Link", + "label": "Parent DocType", + "link_filters": "[[\"DocType\",\"istable\",\"=\",0]]", + "options": "DocType", + "read_only_depends_on": "eval:doc.is_system_generated===1", + "search_index": 1 + }, + { + "default": "Include", + "fieldname": "track_transaction_health", + "fieldtype": "Select", + "in_list_view": 1, + "label": "Track Transaction Health", + "options": "Include\nIgnore" + }, + { + "default": "High", + "depends_on": "eval:doc.track_transaction_health=='Include'", + "fieldname": "transaction_health_severity", + "fieldtype": "Select", + "label": "Severity", + "options": "Low\nMedium\nHigh\nCritical" + }, + { + "default": "0", + "fieldname": "is_dynamic_party_type", + "fieldtype": "Check", + "in_list_view": 1, + "label": "Is Dynamic Party Type", + "read_only_depends_on": "doc.is_system_generated" + }, + { + "fieldname": "party_fieldname", + "fieldtype": "Select", + "label": "Party FieldName", + "read_only_depends_on": "eval:doc.is_system_generated===1", + "reqd": 1 + }, + { + "depends_on": "eval:doc.is_dynamic_party_type===0", + "description": "If Party Type is constant Type for this Document Type", + "fieldname": "party_type", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Party Type", + "mandatory_depends_on": "eval:doc.is_dynamic_party_type===0", + "options": "Party Type" + }, + { + "depends_on": "eval:doc.is_dynamic_party_type===1", + "fieldname": "party_type_fieldname", + "fieldtype": "Select", + "label": "Party Type Fieldname", + "mandatory_depends_on": "eval:doc.is_dynamic_party_type===1" + }, + { + "default": "1", + "fieldname": "reqd", + "fieldtype": "Check", + "in_list_view": 1, + "label": "Mandatory" + }, + { + "depends_on": "eval:!doc.enabled", + "description": "Copy Script above code to client script on your custom Doctype and Enabled it", + "fieldname": "client_script", + "fieldtype": "Link", + "label": "Client Script", + "options": "Client Script" + }, + { + "default": "0", + "fieldname": "is_system_generated", + "fieldtype": "Check", + "hidden": 1, + "label": "Is System generated", + "read_only": 1 + }, + { + "fieldname": "document_categories", + "fieldtype": "Select", + "label": "Document Categories", + "options": "\nSelling DocType\nPurchasing DocType\nDynamic type As Parent\nDynamic Type As Child\nEmployee DocType", + "read_only_depends_on": "doc.is_system_generated" + }, + { + "fieldname": "party_master_custom_field", + "fieldtype": "Link", + "label": "Party Master Custom Field", + "options": "Custom Field", + "search_index": 1 + }, + { + "default": "1", + "description": "If unchecked there will be no default script applied to this Doctype or the custom client script", + "fieldname": "enabled", + "fieldtype": "Check", + "label": "Enabled", + "search_index": 1 + }, + { + "fieldname": "column_break_brmi", + "fieldtype": "Column Break" + }, + { + "default": "0", + "description": "Checked will show warn message if Draft or Cancelled(Not Edited) Document exists", + "fieldname": "warn_not_submitted_document", + "fieldtype": "Check", + "label": "Warn If Un-Submitted Documents" + } + ], + "grid_page_length": 50, + "index_web_pages_for_search": 1, + "istable": 1, + "links": [], + "modified": "2025-11-15 19:33:31.396245", + "modified_by": "Administrator", + "module": "Party", + "name": "Party Master Settings DocType", + "owner": "Administrator", + "permissions": [], + "row_format": "Dynamic", + "rows_threshold_for_grid_search": 20, + "sort_field": "modified", + "sort_order": "DESC", + "states": [] } \ No newline at end of file diff --git a/uph/party/page/data_quality_dashboard/data_quality_dashboard.js b/uph/party/page/data_quality_dashboard/data_quality_dashboard.js index 31a0a080..2d0552d4 100644 --- a/uph/party/page/data_quality_dashboard/data_quality_dashboard.js +++ b/uph/party/page/data_quality_dashboard/data_quality_dashboard.js @@ -265,8 +265,10 @@ class DataQualityDashboard { $('#stat-total-parties .stat-value').text(stats.total_parties || 0); $('#stat-duplicate-issues .stat-value').text(stats.duplicate_issues || 0); $('#stat-unlinked .stat-value').text(stats.unlinked_count || 0); - $('#stat-drafts .stat-value').text(stats.draft_voucher_count || 0); - $('#stat-dismissed .stat-value').text(stats.total_dismissed || 0); + + // Transaction Health stat card update (using ID from render_layout) + const health_total = (stats.draft_voucher_count || 0) + (stats.cancelled_unamended_count || 0); + $('#stat-policy-issues .stat-value').text(health_total); // Update tab badges if (stats.duplicate_issues) { @@ -280,7 +282,6 @@ class DataQualityDashboard { $('#tab-badge-unlinked').hide(); } - const health_total = (stats.draft_voucher_count || 0) + (stats.cancelled_unamended_count || 0); if (health_total) { $('#tab-badge-health').text(health_total).show(); } else {