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
139 changes: 80 additions & 59 deletions audit_management/audit_management/doctype/my_audits/my_audits.js
Original file line number Diff line number Diff line change
Expand Up @@ -1113,14 +1113,19 @@ frappe.ui.form.on("My Audits", {
if (frm.doc.status === "Closed") return;

// 1. DRAFT STATE: Only Audit Team can see "Raise Request" Action
if (frm.doc.status === "Draft" && is_audit_team) {
if ((frm.doc.status === "Draft" || frm.doc.status === "") && is_audit_team) {
frm
.add_custom_button(
__("Raise Request"),
function () {
let stages = audit_table
.map((r) => r.stagename || r.stage_name)
.filter(Boolean);
.map((r) => {
return {
name: r.stage_name,
label: `${r.stage_name} (${r.employee_name || 'Unassigned'})`
};
})
.filter(r => r.name);

if (stages.length === 0) {
frappe.msgprint(
Expand All @@ -1129,67 +1134,83 @@ frappe.ui.form.on("My Audits", {
return;
}

// Add blank first option + Send to All
let options = ["", "Send to All", ...stages];
let html_content = `
<div style="padding: 10px;">
<div style="border-bottom: 1px solid #d1d8dd; padding-bottom: 10px; margin-bottom: 10px;">
<label class="checkbox-inline" style="font-weight: bold; cursor: pointer; display: flex; align-items: center;">
<input type="checkbox" id="select-all-stages" style="margin-right: 10px; width: 18px; height: 18px;">
<span style="font-size: 14px;">${__("Send to All Stages")}</span>
</label>
</div>
<div id="stage-checkboxes-list" style="max-height: 300px; overflow-y: auto;">
${stages.map(s => `
<div style="margin-bottom: 8px;">
<label class="checkbox-inline" style="cursor: pointer; display: flex; align-items: center;">
<input type="checkbox" class="stage-checkbox" value="${s.name}" style="margin-right: 10px; width: 16px; height: 16px;">
<span style="font-size: 13px;">${s.label}</span>
</label>
</div>
`).join('')}
</div>
</div>
`;

let d = new frappe.ui.Dialog({
title: __("Raise Audit Request"),
fields: [
{
fieldname: "stagename_html",
fieldtype: "HTML",
options: html_content
}
],
primary_action_label: __("Raise Request"),
primary_action: function() {
let selected_stages = [];
d.$wrapper.find('.stage-checkbox:checked').each(function() {
selected_stages.push($(this).val());
});

if (selected_stages.length === 0) {
frappe.msgprint(__("Please select at least one stage."));
return;
}

frappe.prompt(
[
{
label: "Select Target Stage",
fieldname: "stagename",
fieldtype: "Select",
options: options.join("\n"),
default: options[0],
reqd: 1,
description: "Select a stage or 'Send to All' to trigger all stages.",
},
],
function (values) {
if (!values.stagename) {
frappe.msgprint("Please select a valid option.");
return;
}

if (values.stagename === "Send to All") {
frappe.confirm(__("Are you sure you want to send this query to all stages?"), () => {
frappe.call({
method: "audit_management.audit_management.doctype.my_audits.my_audits.send_to_all_stages",
args: { docname: frm.doc.name },
freeze: true,
freeze_message: "Sending to all stages...",
callback: function (r) {
if (r.message) {
frappe.show_alert({ message: r.message, indicator: "green" });
frm.reload_doc();
method: "audit_management.audit_management.doctype.my_audits.my_audits.raise_multi_request",
args: {
docname: frm.doc.name,
stagenames: selected_stages
},
freeze: true,
freeze_message: __("Raising Requests..."),
callback: function(r) {
if (!r.exc) {
frappe.show_alert({
message: __("Requests Raised Successfully"),
indicator: "green"
});
frm.reload_doc();
d.hide();
}
}
}
});
});
} else {
frappe.call({
method:
"audit_management.audit_management.doctype.my_audits.my_audits.raise_request",
args: {
docname: frm.doc.name,
stagename: values.stagename,
},
freeze: true,
freeze_message: "Raising Request...",
callback: function (r) {
if (!r.exc) {
frappe.show_alert({
message: __("Request Raised Successfully"),
indicator: "green",
});
frm.reload_doc();
}
},
});
}
},
__("Raise Audit Request"),
__("Raise Request"),
);
});

d.show();

// Select All logic
d.$wrapper.find('#select-all-stages').on('change', function() {
let checked = $(this).prop('checked');
d.$wrapper.find('.stage-checkbox').prop('checked', checked);
});

// Individual checkbox logic to uncheck "Select All" if one is unchecked
d.$wrapper.find('.stage-checkbox').on('change', function() {
let all_checked = d.$wrapper.find('.stage-checkbox:checked').length === stages.length;
d.$wrapper.find('#select-all-stages').prop('checked', all_checked);
});
},
)
.css({ "background-color": "#007bff", color: "white" });
Expand Down
65 changes: 63 additions & 2 deletions audit_management/audit_management/doctype/my_audits/my_audits.py
Original file line number Diff line number Diff line change
Expand Up @@ -758,11 +758,17 @@ def has_permission(doc, ptype, user=None):

roles = frappe.get_roles(user)
is_audit_manager = "Audit Manager" in roles or "Administrator" in roles or "System Manager" in roles
is_audit_team = is_audit_manager or "Audit Member" in roles


# 1. Audit Manager / Admin can ALWAYS see and edit
if is_audit_manager:
return True

# Handle DocType level permission check (when doc is a string)
if isinstance(doc, str):
# Allow report and read access at doctype level for Audit Members and Employees
if ptype in ["read", "report"]:
return "Audit Member" in roles or "Employee" in roles or bool(get_user_allowed_sol_ids(user))
return True

# 3. Draft Isolation: Only Creator/Manager
if getattr(doc, "status", None) == "Draft":
Expand Down Expand Up @@ -975,6 +981,61 @@ def get_full_name(user_id):



@frappe.whitelist()
def raise_multi_request(docname, stagenames):
"""Transition from Draft to Pending and assign to multiple selected stages."""
if isinstance(stagenames, str):
import json
stagenames = json.loads(stagenames)

doc = frappe.get_doc("My Audits", docname)

if doc.status != "Draft":
frappe.throw("Only Draft requests can be raised.")

if not doc.get("audit_stages"):
frappe.throw(
"Please add stages in the operational tracking section first.")

selected_rows = []
found_stagenames = []

for row in doc.get("audit_stages"):
if row.stage_name in stagenames:
row.status = "Pending"
row.pending_time = frappe.utils.now()
selected_rows.append(row)
found_stagenames.append(row.stage_name)

# Give access to the assigned member
if row.user_id:
frappe.share.add(doc.doctype, doc.name, row.user_id,
read=1, write=1, share=1, notify=0)
else:
# We clear others if they were previously pending?
# Actually, if it was Draft, they should be empty anyway.
# But just in case, we keep the original logic of clearing others if not in selection.
if not row.status or row.status == "Pending":
row.status = ""

if not selected_rows:
frappe.throw("No valid stages selected from the selection.")

doc.status = "Pending"
if len(stagenames) == len(doc.audit_stages):
doc.query_status = "Pending From All Stages"
else:
doc.query_status = f"Pending From {', '.join(found_stagenames)}"

doc.save(ignore_permissions=True)

# Trigger custom notifications for each selected stage
for row in selected_rows:
send_stage_notification(doc, row, action="assign")

return "Requests Raised Successfully!"


@frappe.whitelist()
def raise_request(docname, stagename):
"""Transition from Draft to Pending and assign to the selected starting stage."""
Expand Down
Loading