diff --git a/audit_management/audit_management/doctype/my_audits/my_audits.js b/audit_management/audit_management/doctype/my_audits/my_audits.js
index 093fdba..da65b02 100755
--- a/audit_management/audit_management/doctype/my_audits/my_audits.js
+++ b/audit_management/audit_management/doctype/my_audits/my_audits.js
@@ -79,8 +79,16 @@ frappe.ui.form.on("My Audits", {
date_html += item("Closed", closed_date);
}
- if (frm.doc.aging !== undefined && frm.doc.aging !== null) {
- date_html += item("Aging", `${frm.doc.aging} Days`);
+ if (frm.doc.creation) {
+ const created_date = frappe.datetime.str_to_user(frm.doc.creation.split(" ")[0]);
+
+ // Calculate dynamic aging: today - creation date
+ const creation_date_obj = frappe.datetime.str_to_obj(frm.doc.creation);
+ const today = new Date();
+ const time_diff = today - creation_date_obj;
+ const dynamic_aging = Math.floor(time_diff / (1000 * 60 * 60 * 24));
+
+ date_html += item("Aging", `${dynamic_aging} Days`);
}
date_html += ``;
@@ -483,11 +491,14 @@ frappe.ui.form.on("My Audits", {
args: { docname: frm.doc.name },
callback: function (r) {
if (r.message) {
- // Check again if it's already there to be absolutely sure
if (frm.page.wrapper.find(".custom-status-tracker").length === 0) {
frm.set_intro(
`
${r.message}
`,
);
+ // Initialize Bootstrap tooltips
+ setTimeout(() => {
+ frm.page.wrapper.find('[data-toggle="tooltip"]').tooltip();
+ }, 300);
}
} else {
frm.is_intro_set = false;
@@ -2327,6 +2338,7 @@ function render_interactive_tracker(frm, can_edit) {
.pill-pending { background-color: #fef2f2; border: 1px solid #fecaca; color: #b91c1c; }
.pill-responded { background-color: #f0fdf4; border: 1px solid #bbf7d0; color: #15803d; }
.pill-skipped { background-color: #faf5ff; border: 1px solid #e9d5ff; color: #6b21a8; }
+ .pill-no-response { background-color: #fff7ed; border: 1px solid #fdba74; color: #c2410c; }
.pill-default { background-color: #f3f4f6; border: 1px solid #e5e7eb; color: #374151; }
.pill-audit-team { background-color: #eff6ff; border: 1px solid #bfdbfe; color: #1d4ed8; }
@@ -2437,7 +2449,9 @@ function render_interactive_tracker(frm, can_edit) {
? "pill-responded"
: row.status === "Skipped"
? "pill-skipped"
- : "pill-default";
+ : row.status === "No Response"
+ ? "pill-no-response"
+ : "pill-default";
// Get the best available name for the tooltip
let emp_name =
@@ -2445,9 +2459,15 @@ function render_interactive_tracker(frm, can_edit) {
// Prepare time info (Date + Aging)
let time_info = "";
- if (row.status === "Pending" && row.pending_time) {
+ if (
+ (row.status === "Pending" || row.status === "No Response") &&
+ row.pending_time
+ ) {
let d = frappe.datetime.str_to_user(row.pending_time.split(" ")[0]);
- time_info = ` | Pending: ${d} (${fmt_age(row.pending_time)})`;
+ time_info =
+ row.status === "No Response"
+ ? ` | No Response Since: ${d} (${fmt_age(row.pending_time)})`
+ : ` | Pending: ${d} (${fmt_age(row.pending_time)})`;
} else if (row.status === "Responded" && row.response_time) {
let d = frappe.datetime.str_to_user(row.response_time.split(" ")[0]);
time_info = ` | Responded: ${d} (${fmt_age(row.response_time)} ago)`;
diff --git a/audit_management/audit_management/doctype/my_audits/my_audits.py b/audit_management/audit_management/doctype/my_audits/my_audits.py
old mode 100755
new mode 100644
index 841dd9c..bc27507
--- a/audit_management/audit_management/doctype/my_audits/my_audits.py
+++ b/audit_management/audit_management/doctype/my_audits/my_audits.py
@@ -249,11 +249,7 @@ def sync_new_to_old(self):
prefix = m["prefix"]
break
- # Debugging log
- frappe.log_error(
- title="Stage Mapping Debug",
- message=f"Stage Name: {row.stage_name} | Found Prefix: {prefix}"
- )
+
if prefix:
self.set(f"{prefix}_name", row.employee_name)
@@ -348,30 +344,48 @@ def populate_audit_stages(doc):
@frappe.whitelist()
def get_status_tracker_html(docname):
audit_doc = frappe.get_doc("My Audits", docname)
+
+ from frappe.utils import getdate, nowdate
+ creation_date = getdate(audit_doc.creation)
+ today = getdate(nowdate())
+ dynamic_aging = (today - creation_date).days
def create_status_box(text, color, title):
- return f"{text}
"
+ return f"{text}
"
html_output = create_status_box(
- "AUDIT TEAM", '#1E6EB2', 'Stage 0 : Audit Query') + " --> "
+ "AUDIT TEAM", '#1E6EB2', f'Stage 0 : Audit Query (Aging: {dynamic_aging} days)') + " --> "
for i, row in enumerate(audit_doc.audit_stages):
color = "grey"
- title = f"Stage {row.stage} : {row.stage_name} - {row.status or 'Waiting'}"
+ # Include aging in tooltip
+ title = f"Stage {row.stage} : {row.stage_name} - {row.status or 'Waiting'} (Aging: {dynamic_aging} days)"
if row.status == "Pending":
color = "red"
elif row.status == "Responded":
color = "green"
elif row.status == "No Response":
- color = "#4b0a7d"
+ color = "#4b0a7d"
elif row.status == "Skipped":
color = "#ffbe0b"
+ else:
+ color = "grey"
+
+ # Constructing Title with specific check for No Response details
+ status_text = row.status or 'Waiting'
+ title = f"Stage {row.stage} : {row.stage_name} - {status_text} (Aging: {dynamic_aging} days)"
+ if row.status == "No Response" and row.pending_time:
+ from frappe.utils import format_datetime
+ title += f" | Last Attempt: {format_datetime(row.pending_time, 'dd-MM-yyyy')}"
html_output += create_status_box(row.stage_name, color, title)
if i < len(audit_doc.audit_stages) - 1:
html_output += " --> "
+ # Log for debugging
+ frappe.log_error(title="Tracker HTML Output", message=html_output)
+
return html_output
@@ -1251,3 +1265,57 @@ def check_pending_tat():
doc.query_status = "Unresolved - Escalation Exhausted"
doc.save(ignore_permissions=True)
+import frappe
+from frappe.utils import getdate, nowdate, format_datetime
+import html
+
+@frappe.whitelist()
+def get_status_tracker_html(docname):
+ try:
+ audit_doc = frappe.get_doc("My Audits", docname)
+
+ creation_date = getdate(audit_doc.creation)
+ today = getdate(nowdate())
+ dynamic_aging = (today - creation_date).days
+
+ html_output = """
+
+
+ """
+
+ # Base Node
+ html_output += f"AUDIT TEAM"
+
+ for row in audit_doc.audit_stages:
+ status = (row.status or "").strip()
+ status_class_map = {
+ "Pending": "tracker-red",
+ "Responded": "tracker-green",
+ "No Response": "tracker-purple",
+ "Skipped": "tracker-yellow",
+ }
+ css_class = status_class_map.get(status, "tracker-grey")
+
+ # Tooltip details
+ title = f"{row.stage_name} : {status or 'Waiting'} (Aging: {dynamic_aging} days)"
+ if status == "No Response" and row.pending_time:
+ title += f" | Last Attempt: {format_datetime(row.pending_time, 'dd-MM-yyyy')}"
+
+ safe_title = html.escape(title)
+
+ html_output += f"{row.stage_name}"
+
+ html_output += "
"
+ return html_output
+ except Exception as e:
+ frappe.log_error(f"Tracker Error: {str(e)}")
+ return f"Error: {str(e)}"
diff --git a/audit_management/audit_management/report/my_audits_report/my_audits_report.js b/audit_management/audit_management/report/my_audits_report/my_audits_report.js
index 31fb32d..c484742 100755
--- a/audit_management/audit_management/report/my_audits_report/my_audits_report.js
+++ b/audit_management/audit_management/report/my_audits_report/my_audits_report.js
@@ -3,136 +3,65 @@
/* eslint-disable */
frappe.query_reports["My Audits Report"] = {
- filters: [
- {
- fieldname: "from_date",
- label: "From Date",
- fieldtype: "Date",
- },
- {
- fieldname: "to_date",
- label: "To Date",
- fieldtype: "Date",
- },
- {
- fieldname: "emp_branch",
- label: "Branch",
- fieldtype: "Link",
- options: "Audit Level",
- },
- {
- fieldname: "query_generated_by_empid",
- label: "Generated By (Emp ID)",
- fieldtype: "Link",
- options: "Employee",
- get_query: () => {
- return {
- filters: {
- status: "Active",
- department: "Audit",
- },
- };
- },
- },
- {
- fieldname: "query_generated_by_name",
- label: "Generated By (Name)",
- fieldtype: "Link",
- options: "Employee",
- get_query: () => {
- return {
- filters: {
- status: "Active",
- department: "Audit",
- },
- };
- },
- },
- {
- fieldname: "query_type",
- label: "Query Type",
- fieldtype: "Select",
- options: "\nAudit Report Compliance\nCritical Compliance",
- },
- {
- fieldname: "status",
- label: "Query Status",
- fieldtype: "Select",
- options: "\nDraft\nPending\nClosed",
- },
- {
- fieldname: "bm_user_status",
- label: "BM Response Status",
- fieldtype: "Select",
- options: "\nResponded\nNo response",
- },
- {
- fieldname: "dh_user_status",
- label: "DH Response Status",
- fieldtype: "Select",
- options: "\nResponded\nNo response",
- },
- {
- fieldname: "com_user_status",
- label: "COM Response Status",
- fieldtype: "Select",
- options: "\nResponded\nNo response",
- },
- {
- fieldname: "rm_user_status",
- label: "RM Response Status",
- fieldtype: "Select",
- options: "\nResponded\nNo response",
- },
- {
- fieldname: "rom_user_status",
- label: "ROM Response Status",
- fieldtype: "Select",
- options: "\nResponded\nNo response",
- },
- {
- fieldname: "zm_user_status",
- label: "ZM Response Status",
- fieldtype: "Select",
- options: "\nResponded\nNo response",
- },
- {
- fieldname: "zom_user_status",
- label: "ZOM Response Status",
- fieldtype: "Select",
- options: "\nResponded\nNo response",
- },
- {
- fieldname: "gm_user_status",
- label: "GM Response Status",
- fieldtype: "Select",
- options: "\nResponded\nNo response",
- },
- {
- fieldname: "hr_user_status",
- label: "HR Response Status",
- fieldtype: "Select",
- options: "\nResponded\nNo response",
- },
- {
- fieldname: "coo_user_status",
- label: "COO Response Status",
- fieldtype: "Select",
- options: "\nResponded\nNo response",
- },
- {
- fieldname: "ceo_user_status",
- label: "CEO Response Status",
- fieldtype: "Select",
- options: "\nResponded\nNo response",
- },
- ],
+ filters: [
+ {
+ fieldname: "from_date",
+ label: __("From Date"),
+ fieldtype: "Date",
+ },
+ {
+ fieldname: "to_date",
+ label: __("To Date"),
+ fieldtype: "Date",
+ },
+ {
+ fieldname: "emp_branch",
+ label: __("Branch"),
+ fieldtype: "Link",
+ options: "Audit Level",
+ },
+ {
+ fieldname: "query_generated_by_empid",
+ label: __("Generated By Employee"),
+ fieldtype: "Link",
+ options: "Employee",
+ },
+ {
+ fieldname: "query_type",
+ label: __("Query Type"),
+ fieldtype: "Link",
+ options: "Audit Query Type",
+ },
+ {
+ fieldname: "status",
+ label: __("Query Status"),
+ fieldtype: "Select",
+ options: "\nDraft\nPending\nClosed",
+ },
+ {
+ fieldname: "stage_name",
+ label: __("Audit Stage"),
+ fieldtype: "Link",
+ options: "Audit Stage",
+ },
+ {
+ fieldname: "stage_status",
+ label: __("Stage Response Status"),
+ fieldtype: "Select",
+ options: "\nPending\nResponded\nNo response\nSkipped",
+ },
+ {
+ fieldname: "stage_employee",
+ label: __("Stage Employee"),
+ fieldtype: "Link",
+ options: "Employee",
+ },
+ ],
- // clear filters button
- onload: function (report) {
- report.page.add_inner_button(__("Clear Filters"), function () {
- report.filters.forEach((f) => f.set_value(""));
- report.refresh();
- });
- },
+ onload: function (report) {
+ report.page.add_inner_button(__("Clear Filters"), function () {
+ report.filters.forEach((f) => f.set_value(""));
+ report.refresh();
+ });
+ },
};
diff --git a/audit_management/audit_management/report/my_audits_report/my_audits_report.py b/audit_management/audit_management/report/my_audits_report/my_audits_report.py
index 0673b38..ea9a799 100755
--- a/audit_management/audit_management/report/my_audits_report/my_audits_report.py
+++ b/audit_management/audit_management/report/my_audits_report/my_audits_report.py
@@ -5,15 +5,13 @@
get_user_allowed_sol_ids
)
-
def execute(filters=None):
columns = get_columns()
data = get_data(filters)
return columns, data
-
def get_columns():
- return [
+ columns = [
{"label": "ID", "fieldname": "name", "fieldtype": "Link", "options": "My Audits", "width": 120},
{"label": "Date", "fieldname": "date_formatted", "fieldtype": "Data", "width": 100},
{"label": "Branch", "fieldname": "emp_branch", "fieldtype": "Link", "options": "Audit Level", "width": 100},
@@ -23,143 +21,109 @@ def get_columns():
{"label": "Query Status", "fieldname": "status", "fieldtype": "Select", "width": 130},
{"label": "Subject", "fieldname": "audit_query_subject_box", "fieldtype": "Data", "width": 200},
{"label": "Description", "fieldname": "audit_query_box", "fieldtype": "Data", "width": 300},
- {"label": "BM Name", "fieldname": "bm_name", "fieldtype": "Data", "width": 120},
- {"label": "BM Response", "fieldname": "bm_response_box", "fieldtype": "Data", "width": 180},
- {"label": "DH Name", "fieldname": "dh_name", "fieldtype": "Data", "width": 120},
- {"label": "DH Response", "fieldname": "dh_response_box", "fieldtype": "Data", "width": 180},
- {"label": "COM Name", "fieldname": "com_name", "fieldtype": "Data", "width": 120},
- {"label": "COM Response", "fieldname": "com_response_box", "fieldtype": "Data", "width": 180},
- {"label": "RM Name", "fieldname": "rm_name", "fieldtype": "Data", "width": 120},
- {"label": "RM Response", "fieldname": "rm_response_box", "fieldtype": "Data", "width": 180},
- {"label": "ROM Name", "fieldname": "rom_name", "fieldtype": "Data", "width": 120},
- {"label": "ROM Response", "fieldname": "rom_response_box", "fieldtype": "Data", "width": 180},
- {"label": "ZM Name", "fieldname": "zm_name", "fieldtype": "Data", "width": 120},
- {"label": "ZM Response", "fieldname": "zm_response_box", "fieldtype": "Data", "width": 180},
- {"label": "ZOM Name", "fieldname": "zom_name", "fieldtype": "Data", "width": 120},
- {"label": "ZOM Response", "fieldname": "zom_response_box", "fieldtype": "Data", "width": 180},
- {"label": "GM Name", "fieldname": "gm_name", "fieldtype": "Data", "width": 120},
- {"label": "GM Response", "fieldname": "gm_response_box", "fieldtype": "Data", "width": 180},
- {"label": "HR Name", "fieldname": "hr_name", "fieldtype": "Data", "width": 120},
- {"label": "HR Response", "fieldname": "hr_response_box", "fieldtype": "Data", "width": 180},
- {"label": "COO Name", "fieldname": "coo_name", "fieldtype": "Data", "width": 120},
- {"label": "COO Response", "fieldname": "coo_response_box", "fieldtype": "Data", "width": 180},
- {"label": "CEO Name", "fieldname": "ceo_name", "fieldtype": "Data", "width": 120},
- {"label": "CEO Response", "fieldname": "ceo_response_box", "fieldtype": "Data", "width": 180},
]
+ # Fetch all audit stages to add dynamic columns
+ stages = frappe.get_all("Audit Stage", order_by="creation asc")
+ for s in stages:
+ stage_name = s.name
+ prefix = stage_name.lower().replace(" ", "_").replace("(", "").replace(")", "").replace("/", "_")
+
+ columns.extend([
+ {"label": f"{stage_name} Name", "fieldname": f"{prefix}_name", "fieldtype": "Data", "width": 120},
+ {"label": f"{stage_name} Response", "fieldname": f"{prefix}_response", "fieldtype": "Data", "width": 180},
+ {"label": f"{stage_name} Status", "fieldname": f"{prefix}_status", "fieldtype": "Data", "width": 100}
+ ])
-def get_data(filters):
- conditions = []
- user = frappe.session.user
- roles = frappe.get_roles(user)
+ return columns
- # ======================
- # ROLE-BASED PERMISSIONS
- # ======================
-
- is_audit_manager = "Audit Manager" in roles or "Administrator" in roles or "System Manager" in roles
- is_audit_member = "Audit Member" in roles
+def get_data(filters):
+ # Base query filters
+ conditions = ["1=1"]
+ params = {}
- if is_audit_manager:
- # Sees everything, no perm filter needed
- pass
- elif is_audit_member:
- # Audit Member: only own created records
- conditions.append(
- f"owner = {frappe.db.escape(user)}"
- )
- else:
- # Other users: Sol ID based access (from Report Preference)
- # OR records where they are participants
- allowed_sol_ids = get_user_allowed_sol_ids(user)
- perm_conds = [f"owner = {frappe.db.escape(user)}"]
-
- if allowed_sol_ids:
- sol_list = ", ".join([frappe.db.escape(str(s)) for s in allowed_sol_ids])
- perm_conds.append(f"""
- (
- status != 'Draft'
- AND
- emp_branch IN (
- SELECT name FROM `tabAudit Level`
- WHERE sahayog_branch IN ({sol_list})
- )
- )
- """)
-
- # Also include where they are assigned (Audit Items)
- perm_conds.append(f"""
- (
- status != 'Draft'
- AND EXISTS (
- SELECT name
- FROM `tabAudit Items`
- WHERE parent = `tabMy Audits`.name
- AND (
- user_id = {frappe.db.escape(user)}
- OR email = {frappe.db.escape(user)}
- )
- )
- )
- """)
-
- conditions.append(f"({' OR '.join(perm_conds)})")
-
- # ======================
- # UI FILTERS
- # ======================
+ # 1. Standard Filters
if filters.get("emp_branch"):
- conditions.append(f"emp_branch = {frappe.db.escape(filters.emp_branch)}")
-
+ conditions.append("ma.emp_branch = %(emp_branch)s")
+ params["emp_branch"] = filters.emp_branch
+
if filters.get("query_generated_by_empid"):
- conditions.append(f"query_generated_by_empid = {frappe.db.escape(filters.query_generated_by_empid)}")
-
+ conditions.append("ma.query_generated_by_empid = %(emp_id)s")
+ params["emp_id"] = filters.query_generated_by_empid
+
if filters.get("query_type"):
- conditions.append(f"query_type = {frappe.db.escape(filters.query_type)}")
-
+ conditions.append("ma.query_type = %(q_type)s")
+ params["q_type"] = filters.query_type
+
if filters.get("status"):
- conditions.append(f"status = {frappe.db.escape(filters.status)}")
-
- for level in ["bm", "dh", "com", "rm", "rom", "zm", "zom", "gm", "hr", "coo", "ceo"]:
- if filters.get(f"{level}_user_status"):
- status = filters.get(f"{level}_user_status")
- if status == "Responded":
- conditions.append(f"{level}_response_box IS NOT NULL AND {level}_response_box != ''")
- elif status == "No response":
- conditions.append(f"({level}_response_box IS NULL OR {level}_response_box = '')")
+ conditions.append("ma.status = %(status)s")
+ params["status"] = filters.status
+
+ # 2. Dynamic Child Table Filters (via JOIN)
+ stage_conditions = []
+ if filters.get("stage_name"):
+ stage_conditions.append("ai.stage_name = %(stage_name)s")
+ params["stage_name"] = filters.stage_name
+
+ if filters.get("stage_status"):
+ stage_conditions.append("ai.status = %(stage_status)s")
+ params["stage_status"] = filters.stage_status
+
+ if filters.get("stage_employee"):
+ stage_conditions.append("ai.employee = %(stage_employee)s")
+ params["stage_employee"] = filters.stage_employee
- if filters.get("from_date"):
- conditions.append(f"DATE(creation) >= {frappe.db.escape(filters.from_date)}")
+ if stage_conditions:
+ conditions.append(f"EXISTS (SELECT 1 FROM `tabAudit Items` ai WHERE ai.parent = ma.name AND {' AND '.join(stage_conditions)})")
- if filters.get("to_date"):
- conditions.append(f"DATE(creation) <= {frappe.db.escape(filters.to_date)}")
+ # 3. Permissions
+ user = frappe.session.user
+ if "Audit Manager" not in frappe.get_roles(user):
+ allowed_sol_ids = get_user_allowed_sol_ids(user)
+ if allowed_sol_ids:
+ sol_list = ", ".join([frappe.db.escape(str(s)) for s in allowed_sol_ids])
+ conditions.append(f"ma.emp_branch IN (SELECT name FROM `tabAudit Level` WHERE sahayog_branch IN ({sol_list}))")
+ else:
+ conditions.append("ma.owner = %(user)s")
+ params["user"] = user
- where_clause = f"WHERE {' AND '.join(conditions)}" if conditions else ""
+ where_clause = "WHERE " + " AND ".join(conditions)
query = f"""
SELECT
- name, creation, status,
- emp_branch, query_generated_by_empid, query_generated_by_name, query_type,
- audit_query_subject_box, audit_query_box,
- bm_name, bm_response_box,
- dh_name, dh_response_box,
- com_name, com_response_box,
- rm_name, rm_response_box,
- rom_name, rom_response_box,
- zm_name, zm_response_box,
- zom_name, zom_response_box,
- gm_name, gm_response_box,
- hr_name, hr_response_box,
- coo_name, coo_response_box,
- ceo_name, ceo_response_box
- FROM `tabMy Audits`
+ ma.name, ma.creation, ma.status,
+ ma.emp_branch, ma.query_generated_by_empid, ma.query_generated_by_name, ma.query_type,
+ ma.audit_query_subject_box, ma.audit_query_box
+ FROM `tabMy Audits` ma
{where_clause}
- ORDER BY creation DESC
+ ORDER BY ma.creation DESC
"""
- result = frappe.db.sql(query, as_dict=True)
+ result = frappe.db.sql(query, params, as_dict=True)
+
+ if not result:
+ return []
+
+ parent_names = [r.name for r in result]
+ items = frappe.get_all(
+ "Audit Items",
+ filters={"parent": ["in", parent_names]},
+ fields=["parent", "stage_name", "employee_name", "response", "status"]
+ )
+
+ items_map = {}
+ for it in items:
+ if it.parent not in items_map:
+ items_map[it.parent] = {}
+
+ prefix = it.stage_name.lower().replace(" ", "_").replace("(", "").replace(")", "").replace("/", "_")
+ items_map[it.parent][f"{prefix}_name"] = it.employee_name
+ items_map[it.parent][f"{prefix}_response"] = it.response
+ items_map[it.parent][f"{prefix}_status"] = it.status
for row in result:
row["date_formatted"] = formatdate(row.creation, "dd-mm-yyyy")
+ if row.name in items_map:
+ row.update(items_map[row.name])
return result