Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
226 changes: 130 additions & 96 deletions audit_management/audit_management/dashboard.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,29 +47,59 @@ def get_dashboard_stats(pending_start=0, recent_start=0, status=None, risk=None,
elif isinstance(time_filter, list): time_filter_list = time_filter

try:
# 1. 🟢 FETCH PENDING FOR ME (From Child Table - Stage User Logic)
pending_items_query = """
SELECT DISTINCT parent
FROM `tabAudit Items`
WHERE status = 'Pending'
AND (user_id = %s OR email = %s)
"""
responded_items_query = """
SELECT DISTINCT parent
FROM `tabAudit Items`
WHERE status = 'Responded'
AND (user_id = %s OR email = %s)
"""
not_responded_items_query = """
SELECT DISTINCT parent
FROM `tabAudit Items`
WHERE status = 'No Response'
AND (user_id = %s OR email = %s)
"""

pending_records = frappe.db.sql(pending_items_query, (user, user), as_dict=True)
responded_records = frappe.db.sql(responded_items_query, (user, user), as_dict=True)
not_responded_records = frappe.db.sql(not_responded_items_query, (user, user), as_dict=True)
from audit_management.audit_management.utils import get_user_allowed_divisions
allowed_divisions = get_user_allowed_divisions(user)

# 1. 🟢 BASE FILTERS (Permissions)
base_filters = {}
if is_admin: pass
elif is_manager:
if allowed_divisions: base_filters["emp_division"] = ["in", allowed_divisions]
else: base_filters["emp_division"] = "None"
elif is_member:
base_filters["owner"] = user
else:
if allowed_divisions: base_filters["emp_division"] = ["in", allowed_divisions]
else: base_filters["emp_division"] = "None"

# 2. 🟢 COUNTER FILTERS (For all top 6 cards: Permissions + Risk ONLY)
counter_filters = base_filters.copy()
if risk_list:
if "Normal" in risk_list: counter_filters["risk"] = ["in", risk_list + [None, ""]]
else: counter_filters["risk"] = ["in", risk_list]

# 3. 🟢 LIST FILTERS (For record lists and drilldown stats: Counter + Query Type)
list_filters = counter_filters.copy()
if query_type_list:
list_filters["query_type"] = ["in", query_type_list]

# Get Allowed Parents for counts (Global within segment)
allowed_parents_global = frappe.get_all("My Audits", filters=counter_filters, fields=["name"], pluck="name")
allowed_parents_global_tuple = tuple(allowed_parents_global) if allowed_parents_global else ("None",)

# 4. 🟢 STAGE USER LOGIC
# Get All User's Assigned Stages (No division restriction for assigned items)
assigned_stages = frappe.get_all("Audit Items",
filters={"user_id": user, "status": ["in", ["Pending", "No Response", "Responded"]]},
fields=["stage_name"], pluck="stage_name")
user_stages = list(set(assigned_stages)) if assigned_stages else ["BM"]
stages_tuple = tuple(user_stages) if len(user_stages) > 1 else (user_stages[0],)

# Count Queries for Stage User (Static - respect Risk but ignore Query Type)
if not risk_list and not (is_admin or is_manager or is_member):
parent_limit_sql = ""
parent_params = []
else:
parent_limit_sql = " AND parent IN %s"
parent_params = [allowed_parents_global_tuple]

pending_items_query = f"SELECT DISTINCT parent FROM `tabAudit Items` WHERE status = 'Pending' AND (user_id = %s OR email = %s) AND stage_name IN %s {parent_limit_sql}"
responded_items_query = f"SELECT DISTINCT parent FROM `tabAudit Items` WHERE status = 'Responded' AND (user_id = %s OR email = %s) AND stage_name IN %s {parent_limit_sql}"
not_responded_items_query = f"SELECT DISTINCT parent FROM `tabAudit Items` WHERE status = 'No Response' AND (user_id = %s OR email = %s) AND stage_name IN %s {parent_limit_sql}"

pending_records = frappe.db.sql(pending_items_query, [user, user, stages_tuple] + parent_params, as_dict=True)
responded_records = frappe.db.sql(responded_items_query, [user, user, stages_tuple] + parent_params, as_dict=True)
not_responded_records = frappe.db.sql(not_responded_items_query, [user, user, stages_tuple] + parent_params, as_dict=True)

pending_for_me_count = len(pending_records)
responded_by_me_count = len(responded_records)
Expand All @@ -78,37 +108,33 @@ def get_dashboard_stats(pending_start=0, recent_start=0, status=None, risk=None,
pending_for_me_list = []
has_more_pending = False

# Identify which parent records to fetch for the stage user UI list
# For list fetching, respect all filters (list_filters)
contextual_parents = frappe.get_all("My Audits", filters=list_filters, fields=["name"], pluck="name")
contextual_parents_set = set(contextual_parents) if contextual_parents else set()

selected_parents_stage_user = []
target_child_status = None

if 'Responded' in status_list:
selected_parents_stage_user = [r.parent for r in responded_records]
selected_parents_stage_user = [r.parent for r in responded_records if r.parent in contextual_parents_set]
target_child_status = 'Responded'
elif 'No Response' in status_list:
selected_parents_stage_user = [r.parent for r in not_responded_records]
selected_parents_stage_user = [r.parent for r in not_responded_records if r.parent in contextual_parents_set]
target_child_status = 'No Response'
elif 'Pending' in status_list:
selected_parents_stage_user = [r.parent for r in pending_records]
selected_parents_stage_user = [r.parent for r in pending_records if r.parent in contextual_parents_set]
target_child_status = 'Pending'
else:
selected_parents_stage_user = list(set(
[r.parent for r in pending_records] +
[r.parent for r in responded_records] +
[r.parent for r in not_responded_records]
[r.parent for r in pending_records if r.parent in contextual_parents_set] +
[r.parent for r in responded_records if r.parent in contextual_parents_set] +
[r.parent for r in not_responded_records if r.parent in contextual_parents_set]
))

if selected_parents_stage_user:
p_filters = {"name": ["in", selected_parents_stage_user]}
if status_list:
actual_statuses = [s for s in status_list if s in ['Draft', 'Pending', 'Closed']]
if actual_statuses: p_filters["status"] = ["in", actual_statuses]

if risk_list:
if "Normal" in risk_list: p_filters["risk"] = ["in", risk_list + [None, ""]]
else: p_filters["risk"] = ["in", risk_list]

if query_type_list:
p_filters["query_type"] = ["in", query_type_list]

pending_for_me_list = frappe.get_all(
"My Audits",
filters=p_filters,
filters={"name": ["in", selected_parents_stage_user]},
fields=["name", "audit_query_subject_box", "risk", "status", "emp_branch", "emp_division", "aging", "creation"],
order_by="creation desc",
limit_start=pending_start,
Expand All @@ -119,84 +145,97 @@ def get_dashboard_stats(pending_start=0, recent_start=0, status=None, risk=None,
has_more_pending = True
pending_for_me_list = pending_for_me_list[:page_length]

# Override status with child status
for idx, item in enumerate(pending_for_me_list, start=pending_start + 1):
item["sr_no"] = idx
if target_child_status:
item["status"] = target_child_status
else:
item["status"] = frappe.db.get_value("Audit Items",
{"parent": item.name, "user_id": user, "stage_name": ["in", user_stages]}, "status") or item.status

# 2. 🔵 FETCH GLOBAL/ROLE STATS (Manager/Admin/Member)
from audit_management.audit_management.utils import get_user_allowed_divisions
allowed_divisions = get_user_allowed_divisions(user)

filters = {}
if is_admin:
pass
elif is_manager:
if allowed_divisions: filters["emp_division"] = ["in", allowed_divisions]
else: filters["emp_division"] = "None"
elif is_member:
filters["owner"] = user
else:
if allowed_divisions: filters["emp_division"] = ["in", allowed_divisions]
else: filters["emp_division"] = "None"

# Apply Global query_type filter
if query_type_list:
filters["query_type"] = ["in", query_type_list]

# Calculate Counters for Manager/Member
total_pending = frappe.db.count("My Audits", {**filters, "status": "Pending"})
closed_count = frappe.db.count("My Audits", {**filters, "status": "Closed"})
draft_count = frappe.db.count("My Audits", {**filters, "status": "Draft"})
total_count = frappe.db.count("My Audits", filters)

# New: Responded and Not Responded for Managers/Members from Child Table
manager_parents_responded = frappe.get_all("My Audits", filters={**filters}, fields=["name"], as_list=True)
manager_parents_names = [p[0] for p in manager_parents_responded] if manager_parents_responded else []
# 5. 🔵 MANAGER/ADMIN/MEMBER LOGIC
# Top 4 Card Counts (Static - Counter Filters ignore Query Type)
total_pending = frappe.db.count("My Audits", {**counter_filters, "status": "Pending"})
closed_count = frappe.db.count("My Audits", {**counter_filters, "status": "Closed"})
draft_count = frappe.db.count("My Audits", {**counter_filters, "status": "Draft"})
total_count = frappe.db.count("My Audits", counter_filters)

# Responded/Not Responded Counts (Static - Counter Filters ignore Query Type)
responded_count_manager = 0
not_responded_count_manager = 0
stage_counts = {}
time_counts = {"Today": 0, "Yesterday": 0, "Last Week": 0, "All Time": 0}

if manager_parents_names:
if allowed_parents_global:
resp_sql = "SELECT COUNT(DISTINCT parent) FROM `tabAudit Items` WHERE status = 'Responded' AND parent IN %s"
nr_sql = "SELECT COUNT(DISTINCT parent) FROM `tabAudit Items` WHERE status = 'No Response' AND parent IN %s"
responded_count_manager = frappe.db.sql(resp_sql, (tuple(manager_parents_names),))[0][0] if manager_parents_names else 0
not_responded_count_manager = frappe.db.sql(nr_sql, (tuple(manager_parents_names),))[0][0] if manager_parents_names else 0
responded_count_manager = frappe.db.sql(resp_sql, (allowed_parents_global_tuple,))[0][0]
not_responded_count_manager = frappe.db.sql(nr_sql, (allowed_parents_global_tuple,))[0][0]

# Calculate Stage and Time Counts for Drilldown
# Contextual Drilldown Stats (Dynamic - respect all list_filters)
manager_parents_contextual = frappe.get_all("My Audits", filters=list_filters, fields=["name"], pluck="name")
manager_parents_contextual_tuple = tuple(manager_parents_contextual) if manager_parents_contextual else ("None",)

stage_counts = {}
query_type_counts = {}
time_counts = {"Today": 0, "Yesterday": 0, "Last Week": 0, "All Time": 0}

if manager_parents_contextual:
child_status = None
if 'Responded' in status_list: child_status = 'Responded'
elif 'No Response' in status_list: child_status = 'No Response'

if child_status:
# 1. Stage Counts
stg_sql = f"SELECT stage_name, COUNT(DISTINCT parent) as count FROM `tabAudit Items` WHERE status = %s AND parent IN %s GROUP BY stage_name"
stg_data = frappe.db.sql(stg_sql, (child_status, tuple(manager_parents_names)), as_dict=True)
stg_data = frappe.db.sql(stg_sql, (child_status, manager_parents_contextual_tuple), as_dict=True)
stage_counts = {d.stage_name: d.count for d in stg_data}

# 2. Query Type Counts (Contextual to status, stage, time)
qt_parents_filters = counter_filters.copy()
child_qt_conds = [f"status = '{child_status}'"]
qt_params = []
if item_stage_list:
child_qt_conds.append("stage_name IN %s")
qt_params.append(tuple(item_stage_list))
if time_filter_list:
t_conds = []
fld = "response_time" if child_status == 'Responded' else "pending_time"
for t in time_filter_list:
if t == "Today": t_conds.append(f"DATE({fld}) = CURDATE()")
elif t == "Yesterday": t_conds.append(f"DATE({fld}) = DATE_SUB(CURDATE(), INTERVAL 1 DAY)")
elif t == "Last Week": t_conds.append(f"DATE({fld}) >= DATE_SUB(CURDATE(), INTERVAL 7 DAY)")
if t_conds: child_qt_conds.append("(" + " OR ".join(t_conds) + ")")

qt_child_sql = f"SELECT DISTINCT parent FROM `tabAudit Items` WHERE {' AND '.join(child_qt_conds)}"
qt_child_parents_sql = frappe.db.sql(qt_child_sql, tuple(qt_params))
qt_child_parents = [r[0] for r in qt_child_parents_sql] if qt_child_parents_sql else []

if qt_child_parents:
qt_parents_filters["name"] = ["in", qt_child_parents]
qt_data = frappe.db.get_all("My Audits", filters=qt_parents_filters, fields=["query_type", "count(name) as count"], group_by="query_type")
query_type_counts = {d.query_type: d.count for d in qt_data if d.query_type}

# 3. Time Counts
field = "response_time" if child_status == 'Responded' else "pending_time"
time_counts["Today"] = frappe.db.sql(f"SELECT COUNT(DISTINCT parent) FROM `tabAudit Items` WHERE status = %s AND parent IN %s AND DATE({field}) = CURDATE()", (child_status, tuple(manager_parents_names)))[0][0] or 0
time_counts["Yesterday"] = frappe.db.sql(f"SELECT COUNT(DISTINCT parent) FROM `tabAudit Items` WHERE status = %s AND parent IN %s AND DATE({field}) = DATE_SUB(CURDATE(), INTERVAL 1 DAY)", (child_status, tuple(manager_parents_names)))[0][0] or 0
time_counts["Last Week"] = frappe.db.sql(f"SELECT COUNT(DISTINCT parent) FROM `tabAudit Items` WHERE status = %s AND parent IN %s AND DATE({field}) >= DATE_SUB(CURDATE(), INTERVAL 7 DAY)", (child_status, tuple(manager_parents_names)))[0][0] or 0
time_counts["All Time"] = responded_count_manager if child_status == 'Responded' else not_responded_count_manager
time_counts["Today"] = frappe.db.sql(f"SELECT COUNT(DISTINCT parent) FROM `tabAudit Items` WHERE status = %s AND parent IN %s AND DATE({field}) = CURDATE()", (child_status, manager_parents_contextual_tuple))[0][0] or 0
time_counts["Yesterday"] = frappe.db.sql(f"SELECT COUNT(DISTINCT parent) FROM `tabAudit Items` WHERE status = %s AND parent IN %s AND DATE({field}) = DATE_SUB(CURDATE(), INTERVAL 1 DAY)", (child_status, manager_parents_contextual_tuple))[0][0] or 0
time_counts["Last Week"] = frappe.db.sql(f"SELECT COUNT(DISTINCT parent) FROM `tabAudit Items` WHERE status = %s AND parent IN %s AND DATE({field}) >= DATE_SUB(CURDATE(), INTERVAL 7 DAY)", (child_status, manager_parents_contextual_tuple))[0][0] or 0
time_counts["All Time"] = len(frappe.db.sql(f"SELECT DISTINCT parent FROM `tabAudit Items` WHERE status = %s AND parent IN %s", (child_status, manager_parents_contextual_tuple)))

recent_list = []
has_more_recent = False
if is_admin or is_manager or is_member:
r_filters = filters.copy()

r_filters = list_filters.copy()
# (Rest of child filtering logic remains the same as it correctly applies drilldown to the list)
# If Responded or No Response is selected, we must filter parents based on child items
child_parent_ids = []
if 'Responded' in status_list or 'No Response' in status_list or item_stage_list or time_filter_list:
child_conds = []
params = []

if 'Responded' in status_list: child_conds.append("status = 'Responded'")
elif 'No Response' in status_list: child_conds.append("status = 'No Response'")

if item_stage_list:
child_conds.append("stage_name IN %s")
params.append(tuple(item_stage_list))

if time_filter_list:
time_conds = []
for t in time_filter_list:
Expand All @@ -205,26 +244,19 @@ def get_dashboard_stats(pending_start=0, recent_start=0, status=None, risk=None,
elif t == "Yesterday": time_conds.append(f"DATE({field}) = DATE_SUB(CURDATE(), INTERVAL 1 DAY)")
elif t == "Last Week": time_conds.append(f"DATE({field}) >= DATE_SUB(CURDATE(), INTERVAL 7 DAY)")
if time_conds: child_conds.append("(" + " OR ".join(time_conds) + ")")

if child_conds:
child_sql = f"SELECT DISTINCT parent FROM `tabAudit Items` WHERE {' AND '.join(child_conds)}"
child_records = frappe.db.sql(child_sql, tuple(params), as_dict=True)
child_parent_ids = [r.parent for r in child_records]

if child_parent_ids: r_filters["name"] = ["in", child_parent_ids]
else:
if 'Responded' in status_list or 'No Response' in status_list or item_stage_list or time_filter_list:
r_filters["name"] = "None"

# Standard Parent Status Filter
if status_list and not ('Responded' in status_list or 'No Response' in status_list):
actual_statuses = [s for s in status_list if s in ['Draft', 'Pending', 'Closed']]
if actual_statuses: r_filters["status"] = ["in", actual_statuses]

if risk_list:
if "Normal" in risk_list: r_filters["risk"] = ["in", risk_list + [None, ""]]
else: r_filters["risk"] = ["in", risk_list]

recent_list = frappe.get_all(
"My Audits",
filters=r_filters,
Expand Down Expand Up @@ -263,6 +295,7 @@ def get_dashboard_stats(pending_start=0, recent_start=0, status=None, risk=None,

return {
"role_type": "manager" if (is_manager or is_admin) else ("member" if is_member else "stage_user"),
"user_stages": user_stages if (is_manager or is_admin or is_member) == False else [],
"pending_for_me": pending_for_me_count,
"responded_by_me": responded_by_me_count if (is_member or is_manager or is_admin) == False else responded_count_manager,
"not_responded_count": not_responded_count_me if (is_member or is_manager or is_admin) == False else not_responded_count_manager,
Expand All @@ -271,6 +304,7 @@ def get_dashboard_stats(pending_start=0, recent_start=0, status=None, risk=None,
"closed_count": closed_count,
"draft_count": draft_count,
"stage_counts": stage_counts,
"query_type_counts": query_type_counts,
"time_counts": time_counts,
"pending_list": pending_for_me_list,
"recent_list": recent_list,
Expand Down
Loading
Loading