From 3749aec22806d3bfa66a2d84cb799d44a0d6fe4f Mon Sep 17 00:00:00 2001 From: Avishna Murali Date: Thu, 4 Dec 2025 15:12:44 +0530 Subject: [PATCH] feat: bulk task removel --- .../task_assigning_tool.js | 583 ++++++++++-------- .../task_assigning_tool.json | 32 +- .../task_assigning_tool.py | 492 +++++++++------ 3 files changed, 660 insertions(+), 447 deletions(-) diff --git a/one_compliance/one_compliance/doctype/task_assigning_tool/task_assigning_tool.js b/one_compliance/one_compliance/doctype/task_assigning_tool/task_assigning_tool.js index adbf4ca1..319a60e8 100644 --- a/one_compliance/one_compliance/doctype/task_assigning_tool/task_assigning_tool.js +++ b/one_compliance/one_compliance/doctype/task_assigning_tool/task_assigning_tool.js @@ -2,285 +2,346 @@ // For license information, please see license.txt frappe.ui.form.on('Task Assigning Tool', { - refresh: function(frm) { - frm.disable_save(); - frm.toggle_display('add_to_subcategories', false); - }, - setup: function(frm) { - set_filters(frm); - }, - assign_from: function(frm) { - if (frm.doc.assign_from) { - prepare_task_reassign(frm); - } - else { - clear_values(frm); - } - }, - assign: function(frm) { - // Get the selected 'assign_from' and 'assign_to' values - var assignFrom = frm.doc.assign_from; - var assignTo = frm.doc.assign_to; - - if (!assignFrom || !assignTo) { - frappe.msgprint('Please select both "Assign From" and "Assign To" users.'); - return; - } - - // Get the selected tasks from the 'task_reassigns' table - var selectedRows = frm.get_selected().task_reassigns || []; - - if (selectedRows.length === 0) { - frappe.msgprint('Please select tasks to reassign.'); - return; - } - - var selectedTaskIds = selectedRows.map(function(rowName) { - var row = frm.doc.task_reassigns.find(r => r.name === rowName); - return row && row.task_id; - }).filter(Boolean); - - frappe.call({ - method: 'one_compliance.one_compliance.doctype.task_assigning_tool.task_assigning_tool.reassign_tasks', - args: { - assign_from: assignFrom, - assign_to: assignTo, - selected_tasks_json: JSON.stringify(selectedTaskIds) - }, - freeze: true, - freeze_message: 'Reassigning tasks...', - callback: function(r) { - if (r.message === 'Tasks reassigned successfully') { - frappe.msgprint('Tasks reassigned successfully.'); - // Refresh the form to update the 'task_reassigns' table - frm.reload_doc(); - } else { - frappe.msgprint('Error: Unable to reassign tasks.'); - } - } - }); - }, - - compliance_categories: function(frm) { - // Get the selected Compliance Category - var selectedCategory = frm.doc.compliance_categories; - - if (selectedCategory) { - // Make an AJAX call to the server to fetch executives - frappe.call({ - method: 'one_compliance.one_compliance.doctype.task_assigning_tool.task_assigning_tool.get_compliance_executives', - args: { compliance_category: selectedCategory }, - callback: function(response) { - if (response.message) { - // Clear the existing "Compliance Executives" table - frm.clear_table('compliance_executives'); - - // Iterate through the response and add records to the table - response.message.forEach(function(executive) { - var row = frappe.model.add_child(frm.doc, 'Compliance Executives', 'compliance_executives'); - row.employee = executive.employee; - row.designation = executive.designation; - row.employee_name = executive.employee_name; - // Set other child table fields as needed - }); - - frm.refresh_field('compliance_executives'); - } - var selectedEmployee = frm.doc.employee; - - // Fetch the name of the employee using user_id - fetchEmployeeName(selectedEmployee, function(employeeName) { - - // Check if the selected employee is in Compliance Executives and show the button - var isEmployeeInExecutives = response.message.some(function(executive) { - return executive.employee === employeeName; - }); - - frm.toggle_display('add_to_subcategories', isEmployeeInExecutives); - frm.toggle_display('add_employee', !isEmployeeInExecutives); - }); - } - }); - } - }, - add_employee: function(frm) { - var selectedEmployee = frm.doc.employee; - var selectedCategory = frm.doc.compliance_categories; - - if (selectedEmployee && selectedCategory) { - // Make a server call to add the employee to the Compliance Category - frappe.call({ - method: 'one_compliance.one_compliance.doctype.task_assigning_tool.task_assigning_tool.add_employee_to_compliance_executive', - args: { - employee: selectedEmployee, - compliance_category: selectedCategory - }, - callback: function(response) { - if (response.message) { - frappe.msgprint('Employee added to Compliance Category.'); - frm.events.compliance_categories(frm); - - } else { - frappe.msgprint('Error adding employee.'); - } - } - }); - } else { - frappe.msgprint('Please select an employee and a Compliance Category.'); - } - }, - add_to_subcategories: function(frm) { - get_available_subcategories(frm); - } + assignment_type: function(frm) { + if (frm.doc.assignment_type === "Remove") { + frm.toggle_display('employee', true); + frm.toggle_display('assign_from', false); + frm.toggle_display('assign_to', false); + frm.toggle_display('assign', false); + } else if (frm.doc.assignment_type === "Transfer") { + frm.toggle_display('employee', false); + frm.toggle_display('assign_from', true); + frm.toggle_display('assign_to', true); + frm.toggle_display('assign', true); + } + clear_values(frm); + }, + + employee: function(frm) { + if (frm.doc.assignment_type === "Remove" && frm.doc.employee) { + frappe.call({ + method: 'one_compliance.one_compliance.doctype.task_assigning_tool.task_assigning_tool.get_tasks_for_employee', + args: { + assign_from: frm.doc.employee, + assignment_type: frm.doc.assignment_type + }, + callback: function(r) { + if (r.message) { + frm.clear_table('task_removal'); + r.message.forEach(task => { + let assigned_task = frm.add_child('task_removal'); + assigned_task.task_id = task.task_id; + assigned_task.subject = task.subject; + assigned_task.project = task.project; + }); + frm.refresh_field('task_removal'); + } + } + }); + } + }, + + remove_assignments: function(frm) { + if (!frm.doc.employee) { + frappe.msgprint("Please select an employee."); + return; + } + const selectedTaskIds = (frm.doc.task_removal || []) + .filter(row => row.__checked === 1) + .map(row => row.task_id); + + if (selectedTaskIds.length === 0) { + frappe.msgprint("Please select at least one task to remove."); + return; + } + + frappe.call({ + method: "one_compliance.one_compliance.doctype.task_assigning_tool.task_assigning_tool.remove_task_assignments", + args: { + employee: frm.doc.employee, + selected_tasks_json: JSON.stringify(selectedTaskIds) + }, + freeze: true, + freeze_message: "Removing selected task assignments...", + callback: function(r) { + if (r.message === "Assignments removed successfully") { + frappe.msgprint("Selected assignments removed successfully."); + frm.trigger("employee"); + } else { + frappe.msgprint("Error: " + r.message); + } + } + }); + }, + + refresh: function(frm) { + frm.disable_save(); + frm.toggle_display('add_to_subcategories', false); + }, + + setup: function(frm) { + set_filters(frm); + }, + + assign_from: function(frm) { + if (frm.doc.assign_from) { + prepare_task_reassign(frm); + } + else { + clear_values(frm); + } + }, + + assign: function(frm) { + var assignFrom = frm.doc.assign_from; + var assignTo = frm.doc.assign_to; + + if (!assignFrom || !assignTo) { + frappe.msgprint('Please select both "Assign From" and "Assign To" users.'); + return; + } + + var selectedRows = frm.get_selected().task_reassigns || []; + + if (selectedRows.length === 0) { + frappe.msgprint('Please select tasks to reassign.'); + return; + } + var selectedTaskIds = selectedRows.map(function(rowName) { + var row = frm.doc.task_reassigns.find(r => r.name === rowName); + return row && row.task_id; + }).filter(Boolean); + + frappe.call({ + method: 'one_compliance.one_compliance.doctype.task_assigning_tool.task_assigning_tool.reassign_tasks', + args: { + assign_from: assignFrom, + assign_to: assignTo, + selected_tasks_json: JSON.stringify(selectedTaskIds) + }, + freeze: true, + freeze_message: 'Reassigning tasks...', + callback: function(r) { + if (r.message === 'Tasks reassigned successfully') { + frappe.msgprint('Tasks reassigned successfully.'); + frm.reload_doc(); + } else { + frappe.msgprint('Error: Unable to reassign tasks.'); + } + } + }); + }, + + compliance_categories: function(frm) { + var selectedCategory = frm.doc.compliance_categories; + + if (selectedCategory) { + frappe.call({ + method: 'one_compliance.one_compliance.doctype.task_assigning_tool.task_assigning_tool.get_compliance_executives', + args: { compliance_category: selectedCategory }, + callback: function(response) { + if (response.message) { + frm.clear_table('compliance_executives'); + + response.message.forEach(function(executive) { + var row = frappe.model.add_child(frm.doc, 'Compliance Executives', 'compliance_executives'); + row.employee = executive.employee; + row.designation = executive.designation; + row.employee_name = executive.employee_name; + }); + + frm.refresh_field('compliance_executives'); + } + var selectedEmployee = frm.doc.employee; + fetchEmployeeName(selectedEmployee, function(employeeName) { + var isEmployeeInExecutives = response.message.some(function(executive) { + return executive.employee === employeeName; + }); + frm.toggle_display('add_to_subcategories', isEmployeeInExecutives); + frm.toggle_display('add_employee', !isEmployeeInExecutives); + }); + } + }); + } + }, + + add_employee: function(frm) { + var selectedEmployee = frm.doc.employee; + var selectedCategory = frm.doc.compliance_categories; + + if (selectedEmployee && selectedCategory) { + frappe.call({ + method: 'one_compliance.one_compliance.doctype.task_assigning_tool.task_assigning_tool.add_employee_to_compliance_executive', + args: { + employee: selectedEmployee, + compliance_category: selectedCategory + }, + callback: function(response) { + if (response.message) { + frappe.msgprint('Employee added to Compliance Category.'); + frm.events.compliance_categories(frm); + + } else { + frappe.msgprint('Error adding employee.'); + } + } + }); + } else { + frappe.msgprint('Please select an employee and a Compliance Category.'); + } + }, + + add_to_subcategories: function(frm) { + get_available_subcategories(frm); + } }); let clear_values = function (frm) { - // clear field values - frm.clear_table('task_reassigns'); - frm.refresh_field('task_reassigns'); - frm.clear_table('compliance_executives'); - frm.refresh_field('compliance_executives'); + frm.clear_table('task_reassigns'); + frm.refresh_field('task_reassigns'); + frm.clear_table('compliance_executives'); + frm.refresh_field('compliance_executives'); } let set_filters = function(frm){ - frm.set_query('assign_from', function() { - return { - query: 'one_compliance.one_compliance.doctype.task_assigning_tool.task_assigning_tool.get_users_by_department', - filters: { - department: frm.doc.department, - exclude_email: frm.doc.assign_from - } - }; - }); - frm.set_query('assign_to', function() { - return { - query: 'one_compliance.one_compliance.doctype.task_assigning_tool.task_assigning_tool.get_users_by_department', - filters: { - department: frm.doc.department, - exclude_email: frm.doc.assign_from - - } - }; - }); - frm.set_query('compliance_categories', function() { - return { - query: 'one_compliance.one_compliance.doctype.task_assigning_tool.task_assigning_tool.get_compliance_categories_for_user', - filters: { - user_id: frm.doc.employee - } - }; -}); -frm.fields_dict.task_reassigns.grid.get_field("task_id").get_query = function () { - return { - filters: { - status: ["not in", ["Completed", "Cancelled", "Template"]] - } - }; -}; + frm.set_query('assign_from', function() { + return { + query: 'one_compliance.one_compliance.doctype.task_assigning_tool.task_assigning_tool.get_users_by_department', + filters: { + department: frm.doc.department, + exclude_email: frm.doc.assign_from + } + }; + }); + + frm.set_query('assign_to', function() { + return { + query: 'one_compliance.one_compliance.doctype.task_assigning_tool.task_assigning_tool.get_users_by_department', + filters: { + department: frm.doc.department, + exclude_email: frm.doc.assign_from + } + }; + }); + frm.set_query('compliance_categories', function() { + return { + query: 'one_compliance.one_compliance.doctype.task_assigning_tool.task_assigning_tool.get_compliance_categories_for_user', + filters: { + user_id: frm.doc.employee + } + }; + }); + + frm.fields_dict.task_reassigns.grid.get_field("task_id").get_query = function () { + return { + filters: { + status: ["not in", ["Completed", "Cancelled", "Template"]] + } + }; + }; } function prepare_task_reassign(frm) { - if (frm.doc.assign_from) { - frappe.call({ - method: 'one_compliance.one_compliance.doctype.task_assigning_tool.task_assigning_tool.get_tasks_for_user', - args: { - assign_from: frm.doc.assign_from - }, - freeze: true, - freeze_message: __("Preparing Task Reassign.."), - callback: (r) => { - frm.toggle_display('task_reassigns', r && r.message && r.message.length > 0); - - if (r && r.message && r.message.length > 0) { - frm.clear_table('task_reassigns'); - r.message.forEach(task => { - let task_reassign = frm.add_child('task_reassigns'); - task_reassign.task_id = task.task_id; - task_reassign.subject = task.subject; - task_reassign.project = task.project; - }); - frm.refresh_field('task_reassigns'); - } else { - frappe.msgprint(__("Currently, the user '{0}' has no tasks.", [frm.doc.assign_from])); - } - } - }) - } else { - frm.toggle_display('task_reassigns', false); - frappe.msgprint(__("Please select an 'assign_from' user.")); - } + if (frm.doc.assign_from) { + frappe.call({ + method: 'one_compliance.one_compliance.doctype.task_assigning_tool.task_assigning_tool.get_tasks_for_user', + args: { + assign_from: frm.doc.assign_from + }, + freeze: true, + freeze_message: __("Preparing Task Reassign.."), + callback: (r) => { + frm.toggle_display('task_reassigns', r && r.message && r.message.length > 0); + + if (r && r.message && r.message.length > 0) { + frm.clear_table('task_reassigns'); + r.message.forEach(task => { + let task_reassign = frm.add_child('task_reassigns'); + task_reassign.task_id = task.task_id; + task_reassign.subject = task.subject; + task_reassign.project = task.project; + }); + frm.refresh_field('task_reassigns'); + } else { + frappe.msgprint(__("Currently, the user '{0}' has no tasks.", [frm.doc.assign_from])); + } + } + }) + } else { + frm.toggle_display('task_reassigns', false); + frappe.msgprint(__("Please select an 'assign_from' user.")); + } } function get_available_subcategories(frm) { - if (frm.doc.compliance_categories) { - frappe.call({ - method: 'one_compliance.one_compliance.doctype.task_assigning_tool.task_assigning_tool.get_available_subcategories', - args: { - compliance_category: frm.doc.compliance_categories, - employee: frm.doc.employee - }, - freeze: true, - freeze_message: __("Preparing Subcategory Reassignment..."), - callback: (r) => { - if (r && r.message) { - console.log(r.message); - const subcategories = r.message; - const subcategoryOptions = subcategories.map(subcategory => ({ - - value: subcategory.name, - label: `${subcategory.name} (${subcategory.status})` - })); - - frappe.prompt({ - label: __("Select Subcategories for Reassignment"), - fieldname: "selected_subcategories", - fieldtype: "MultiSelectList", - options: subcategoryOptions, - reqd: true - }, function(values) { - const selectedSubcategories = values.selected_subcategories; - add_employee_to_subcategories(frm, selectedSubcategories); - }, __("Select Subcategories"), "Select"); - } - } - }); - } + if (frm.doc.compliance_categories) { + frappe.call({ + method: 'one_compliance.one_compliance.doctype.task_assigning_tool.task_assigning_tool.get_available_subcategories', + args: { + compliance_category: frm.doc.compliance_categories, + employee: frm.doc.employee + }, + freeze: true, + freeze_message: __("Preparing Subcategory Reassignment..."), + callback: (r) => { + if (r && r.message) { + console.log(r.message); + const subcategories = r.message; + const subcategoryOptions = subcategories.map(subcategory => ({ + value: subcategory.name, + label: `${subcategory.name} (${subcategory.status})` + })); + + frappe.prompt({ + label: __("Select Subcategories for Reassignment"), + fieldname: "selected_subcategories", + fieldtype: "MultiSelectList", + options: subcategoryOptions, + reqd: true + }, function(values) { + const selectedSubcategories = values.selected_subcategories; + add_employee_to_subcategories(frm, selectedSubcategories); + }, __("Select Subcategories"), "Select"); + } + } + }); + } } function add_employee_to_subcategories(frm, selectedSubcategories) { - frappe.call({ - method: 'one_compliance.one_compliance.doctype.task_assigning_tool.task_assigning_tool.add_to_subcategories', - args: { - employee: frm.doc.employee, - compliance_category: frm.doc.compliance_categories, - selected_subcategories: selectedSubcategories - }, - callback: function(response) { - if (response.message) { - frappe.msgprint('Employee added to selected subcategories.'); - } else { - frappe.msgprint('Error adding employee to subcategories.'); - } - } - }); + frappe.call({ + method: 'one_compliance.one_compliance.doctype.task_assigning_tool.task_assigning_tool.add_to_subcategories', + args: { + employee: frm.doc.employee, + compliance_category: frm.doc.compliance_categories, + selected_subcategories: selectedSubcategories + }, + callback: function(response) { + if (response.message) { + frappe.msgprint('Employee added to selected subcategories.'); + } else { + frappe.msgprint('Error adding employee to subcategories.'); + } + } + }); } function fetchEmployeeName(user_id, callback) { - frappe.call({ - method: 'frappe.client.get_value', - args: { - doctype: 'Employee', - fieldname: "name", - filters: { user_id: user_id } - }, - callback: function(res) { - if (res.message && res.message.name) { - callback(res.message.name); - } else { - callback(''); - } - } - }); + frappe.call({ + method: 'frappe.client.get_value', + args: { + doctype: 'Employee', + fieldname: "name", + filters: { user_id: user_id } + }, + callback: function(res) { + if (res.message && res.message.name) { + callback(res.message.name); + } else { + callback(''); + } + } + }); } \ No newline at end of file diff --git a/one_compliance/one_compliance/doctype/task_assigning_tool/task_assigning_tool.json b/one_compliance/one_compliance/doctype/task_assigning_tool/task_assigning_tool.json index 73481fa0..85e76c07 100644 --- a/one_compliance/one_compliance/doctype/task_assigning_tool/task_assigning_tool.json +++ b/one_compliance/one_compliance/doctype/task_assigning_tool/task_assigning_tool.json @@ -12,6 +12,9 @@ "employee", "add_employee", "add_to_subcategories", + "task_removal_section", + "task_removal", + "remove_assignments", "section_break_qky7g", "assign_from", "column_break_ru2vc", @@ -33,7 +36,7 @@ "options": "Department" }, { - "depends_on": "eval: doc.assignment_type == 'Assign'", + "depends_on": "eval: doc.assignment_type == 'Assign' || doc.assignment_type == 'Remove'", "fieldname": "employee", "fieldtype": "Link", "label": "Employee", @@ -43,7 +46,7 @@ "fieldname": "assignment_type", "fieldtype": "Select", "label": "Assignment Type", - "options": "\nTransfer\nAssign", + "options": "\nTransfer\nAssign\nRemove", "reqd": 1 }, { @@ -91,7 +94,7 @@ "label": "Assign" }, { - "depends_on": "eval: doc.employee", + "depends_on": "eval: doc.employee && doc.assignment_type == 'Assign'", "fieldname": "compliance_categories", "fieldtype": "Link", "label": "Compliance Categories", @@ -119,13 +122,32 @@ "fieldname": "add_to_subcategories", "fieldtype": "Button", "label": "Add To Subcategories" + }, + { + "fieldname": "task_removal_section", + "fieldtype": "Section Break", + "label": "Task Removal" + }, + { + "depends_on": "eval:doc.assignment_type == 'Remove' && doc.employee", + "fieldname": "task_removal", + "fieldtype": "Table", + "label": "Task Removal", + "options": "Task Reassign" + }, + { + "depends_on": "eval:doc.assignment_type == 'Remove' && doc.employee", + "fieldname": "remove_assignments", + "fieldtype": "Button", + "label": "Remove Assignments" } ], + "grid_page_length": 50, "hide_toolbar": 1, "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2023-11-01 12:05:00.127334", + "modified": "2025-12-04 10:28:16.355615", "modified_by": "Administrator", "module": "One Compliance", "name": "Task Assigning Tool", @@ -162,6 +184,8 @@ "write": 1 } ], + "row_format": "Dynamic", + "rows_threshold_for_grid_search": 20, "sort_field": "modified", "sort_order": "DESC", "states": [] diff --git a/one_compliance/one_compliance/doctype/task_assigning_tool/task_assigning_tool.py b/one_compliance/one_compliance/doctype/task_assigning_tool/task_assigning_tool.py index 1de4061a..b1f80562 100644 --- a/one_compliance/one_compliance/doctype/task_assigning_tool/task_assigning_tool.py +++ b/one_compliance/one_compliance/doctype/task_assigning_tool/task_assigning_tool.py @@ -10,233 +10,361 @@ class TaskAssigningTool(Document): - pass + pass @frappe.whitelist() def get_users_by_department(doctype, txt, searchfield, start, page_len, filters): - exclude_email = filters.get("exclude_email") - department = filters.get("department") - - employees = frappe.get_all( - "Employee", - filters={"department": department}, - fields=["user_id"], - ) - user_ids = [emp.user_id for emp in employees if emp.user_id] - - if not user_ids: - return [] - - if exclude_email in user_ids: - user_ids.remove(exclude_email) - - users = frappe.get_all( - "User", - filters={"name": ["in", user_ids], "enabled": 1}, - or_filters=[["name", "like", f"%{txt}%"], ["full_name", "like", f"%{txt}%"]], - fields=["name", "full_name"], - start=start, - page_length=page_len, - ) - - return [(user["name"], user["full_name"]) for user in users] + exclude_email = filters.get("exclude_email") + department = filters.get("department") + + employees = frappe.get_all( + "Employee", + filters={ + "department": department, + "status": ["!=", "Left"] + }, + fields=["user_id"] + ) + user_ids = [emp.user_id for emp in employees if emp.user_id] + + if exclude_email in user_ids: + user_ids.remove(exclude_email) + + if not user_ids: + return [] + + users = frappe.get_all( + "User", + filters={"name": ["in", user_ids]}, + or_filters=[ + ["name", "like", f"%{txt}%"], + ["full_name", "like", f"%{txt}%"] + ], + fields=["name", "full_name"], + start=start, + page_length=page_len, + ) + + return [(user["name"], user["full_name"]) for user in users] @frappe.whitelist() def reassign_tasks(assign_from, assign_to, selected_tasks_json): - selected_tasks = frappe.parse_json(selected_tasks_json) - assigned_projects = set() - projects_to_check = set() - - for task_id in selected_tasks: - task_doc = frappe.get_doc("Task", task_id) - frappe.db.set_value("Task", task_id, "assigned_to", assign_to) - - if task_doc.project: - projects_to_check.add(task_doc.project) - - if task_doc.project not in assigned_projects: - try: - exists = frappe.db.exists("ToDo", { - "reference_type": "Project", - "reference_name": task_doc.project, - "allocated_to": assign_to, - "status": "Open" - }) - if not exists: - frappe.desk.form.assign_to.add(args={ - "assign_to": json.dumps([assign_to]), - "doctype": "Project", - "name": task_doc.project, - "description": f"Project assigned due to task reassignment from {assign_from}" - }) - assigned_projects.add(task_doc.project) - except Exception as e: - frappe.log_error(f"Error assigning project {task_doc.project} to user {assign_to}: {str(e)}") - - old_todo_name = frappe.get_value("ToDo", { - "reference_type": "Task", - "reference_name": task_id, - "allocated_to": assign_from, - "status": ["!=", "Closed"] - }, "name") - - if old_todo_name: - frappe.get_doc({ - "doctype": "ToDo", - "owner": assign_to, - "allocated_to": assign_to, - "assigned_by": assign_from, - "reference_type": "Task", - "reference_name": task_id, - "description": f"Task reassigned from {assign_from}", - "status": "Open", - "date": now() - }).insert(ignore_permissions=True) - - old_todo = frappe.get_doc("ToDo", old_todo_name) - old_todo.status = "Closed" - old_todo.save(ignore_permissions=True) - - for project in projects_to_check: - remove_user_from_project_if_no_tasks(project, assign_from) - - frappe.db.commit() - return "Tasks reassigned successfully" + selected_tasks = frappe.parse_json(selected_tasks_json) + assigned_projects = set() + projects_to_check = set() + + for task_id in selected_tasks: + task_doc = frappe.get_doc("Task", task_id) + frappe.db.set_value("Task", task_id, "assigned_to", assign_to) + + if task_doc.project: + projects_to_check.add(task_doc.project) + + if task_doc.project not in assigned_projects: + try: + exists = frappe.db.exists("ToDo", { + "reference_type": "Project", + "reference_name": task_doc.project, + "allocated_to": assign_to, + "status": "Open" + }) + if not exists: + frappe.desk.form.assign_to.add(args={ + "assign_to": json.dumps([assign_to]), + "doctype": "Project", + "name": task_doc.project, + "description": f"Project assigned due to task reassignment from {assign_from}" + }) + assigned_projects.add(task_doc.project) + except Exception as e: + frappe.log_error(f"Error assigning project {task_doc.project} to user {assign_to}: {str(e)}") + + old_todo_name = frappe.get_value("ToDo", { + "reference_type": "Task", + "reference_name": task_id, + "allocated_to": assign_from, + "status": ["!=", "Closed"] + }, "name") + + if old_todo_name: + frappe.get_doc({ + "doctype": "ToDo", + "owner": assign_to, + "allocated_to": assign_to, + "assigned_by": frappe.session.user, + "reference_type": "Task", + "reference_name": task_id, + "description": f"Task reassigned from {assign_from}", + "status": "Open", + "date": now() + }).insert(ignore_permissions=True) + + old_todo = frappe.get_doc("ToDo", old_todo_name) + old_todo.status = "Closed" + old_todo.save(ignore_permissions=True) + + for project in projects_to_check: + remove_user_from_project_if_no_tasks(project, assign_from) + + frappe.db.commit() + return "Tasks reassigned successfully" def remove_user_from_project_if_no_tasks(project_name, user): - try: - remaining_tasks = frappe.db.count("Task", { - "project": project_name, - "assigned_to": user, - "status": ["not in", ["Completed", "Cancelled"]] - }) - - if remaining_tasks == 0: - todo_name = frappe.db.get_value("ToDo", { - "reference_type": "Project", - "reference_name": project_name, - "allocated_to": user, - "status": "Open" - }, "name") - - if todo_name: - todo_doc = frappe.get_doc("ToDo", todo_name) - todo_doc.status = "Closed" - todo_doc.save(ignore_permissions=True) - except Exception as e: - frappe.log_error(f"Error removing project {project_name} from user {user}: {str(e)}") + try: + remaining_tasks = frappe.db.count("Task", { + "project": project_name, + "assigned_to": user, + "status": ["not in", ["Completed", "Cancelled"]] + }) + + if remaining_tasks == 0: + todo_name = frappe.db.get_value("ToDo", { + "reference_type": "Project", + "reference_name": project_name, + "allocated_to": user, + "status": "Open" + }, "name") + + if todo_name: + todo_doc = frappe.get_doc("ToDo", todo_name) + todo_doc.status = "Closed" + todo_doc.save(ignore_permissions=True) + except Exception as e: + frappe.log_error(f"Error removing project {project_name} from user {user}: {str(e)}") @frappe.whitelist() def get_compliance_categories_for_user(doctype, txt, searchfield, start, page_len, filters): - user_id = filters.get("user_id") - employee = frappe.get_doc("Employee", {"user_id": user_id}) + user_id = filters.get("user_id") + employee = frappe.get_doc("Employee", {"user_id": user_id}) - if employee: - compliance_categories = frappe.get_all( - "Compliance Category", - filters={"department": employee.department}, - fields=["name", "department"] - ) - return [(cat["name"], cat["department"]) for cat in compliance_categories] + if employee: + compliance_categories = frappe.get_all( + "Compliance Category", + filters={"department": employee.department}, + fields=["name", "department"] + ) + return [(cat["name"], cat["department"]) for cat in compliance_categories] - return [] + return [] @frappe.whitelist() def get_compliance_executives(compliance_category): - return frappe.get_all( - "Compliance Executive", - filters={"parent": compliance_category}, - fields=["employee", "designation", "employee_name"] - ) + return frappe.get_all( + "Compliance Executive", + filters={"parent": compliance_category}, + fields=["employee", "designation", "employee_name"] + ) @frappe.whitelist() def add_employee_to_compliance_executive(employee, compliance_category): - try: - employee_doc = frappe.get_doc("Employee", {"user_id": employee}) - compliance_doc = frappe.get_doc("Compliance Category", compliance_category) + try: + employee_doc = frappe.get_doc("Employee", {"user_id": employee}) + compliance_doc = frappe.get_doc("Compliance Category", compliance_category) - if employee_doc and compliance_doc: - for executive in compliance_doc.compliance_executive: - if executive.employee == employee_doc.name: - frappe.msgprint("Employee already exists") - return False + if employee_doc and compliance_doc: + for executive in compliance_doc.compliance_executive: + if executive.employee == employee_doc.name: + frappe.msgprint("Employee already exists") + return False - new_exec = frappe.new_doc("Compliance Executive") - new_exec.employee = employee_doc.name - new_exec.designation = employee_doc.designation - new_exec.employee_name = employee_doc.employee_name + new_exec = frappe.new_doc("Compliance Executive") + new_exec.employee = employee_doc.name + new_exec.designation = employee_doc.designation + new_exec.employee_name = employee_doc.employee_name - compliance_doc.append("compliance_executive", new_exec) - compliance_doc.save() - frappe.db.commit() - return True + compliance_doc.append("compliance_executive", new_exec) + compliance_doc.save() + frappe.db.commit() + return True - return False - except Exception: - frappe.log_error(frappe.get_traceback(), _("Error in adding employee to Compliance Executive")) - return False + return False + except Exception: + frappe.log_error(frappe.get_traceback(), _("Error in adding employee to Compliance Executive")) + return False @frappe.whitelist() def get_available_subcategories(compliance_category, employee): - subcategories = frappe.get_all( - "Compliance Sub Category", - filters={"compliance_category": compliance_category}, - fields=["name"] - ) + subcategories = frappe.get_all( + "Compliance Sub Category", + filters={"compliance_category": compliance_category}, + fields=["name"] + ) - employee_doc = frappe.get_doc("Employee", {"user_id": employee}) + employee_doc = frappe.get_doc("Employee", {"user_id": employee}) - for subcat in subcategories: - doc = frappe.get_doc("Compliance Sub Category", subcat["name"]) - subcat["status"] = "added" if any(ex.employee == employee_doc.name for ex in doc.compliance_executive) else "not added" + for subcat in subcategories: + doc = frappe.get_doc("Compliance Sub Category", subcat["name"]) + subcat["status"] = "added" if any(ex.employee == employee_doc.name for ex in doc.compliance_executive) else "not added" - return subcategories + return subcategories @frappe.whitelist() def add_to_subcategories(employee, compliance_category, selected_subcategories): - try: - employee_doc = frappe.get_doc("Employee", {"user_id": employee}) - subcategories = json.loads(selected_subcategories) + try: + employee_doc = frappe.get_doc("Employee", {"user_id": employee}) + subcategories = json.loads(selected_subcategories) - for subcat_name in subcategories: - subcat_doc = frappe.get_doc("Compliance Sub Category", subcat_name) + for subcat_name in subcategories: + subcat_doc = frappe.get_doc("Compliance Sub Category", subcat_name) - if all(ex.employee != employee_doc.name for ex in subcat_doc.compliance_executive): - new_exec = frappe.new_doc("Compliance Executive") - new_exec.employee = employee_doc.name - new_exec.designation = employee_doc.designation - new_exec.employee_name = employee_doc.employee_name + if all(ex.employee != employee_doc.name for ex in subcat_doc.compliance_executive): + new_exec = frappe.new_doc("Compliance Executive") + new_exec.employee = employee_doc.name + new_exec.designation = employee_doc.designation + new_exec.employee_name = employee_doc.employee_name - subcat_doc.append("compliance_executive", new_exec) - subcat_doc.save() + subcat_doc.append("compliance_executive", new_exec) + subcat_doc.save() - frappe.db.commit() - return True - except Exception: - frappe.log_error(frappe.get_traceback(), _("Error in adding employee to Compliance Sub Categories")) - return False + frappe.db.commit() + return True + except Exception: + frappe.log_error(frappe.get_traceback(), _("Error in adding employee to Compliance Sub Categories")) + return False @frappe.whitelist() def get_tasks_for_user(assign_from): - tasks = frappe.db.get_all( - "Task", - filters={ - "_assign": ["like", f"%{assign_from}%"], - "status": ["not in", ["Completed", "Cancelled", "Template"]], - }, - fields=["name", "subject", "project"] - ) - - return [ - {"task_id": task.name, "subject": task.subject, "project": task.project} - for task in tasks - ] + tasks = frappe.db.get_all( + "Task", + filters={ + "_assign": ["like", f"%{assign_from}%"], + "status": ["not in", ["Completed", "Cancelled", "Template"]], + }, + fields=["name", "subject", "project"] + ) + + return [ + {"task_id": task.name, "subject": task.subject, "project": task.project} + for task in tasks + ] + +@frappe.whitelist() +def get_tasks_for_employee(assign_from, assignment_type=None): + """ + Fetch all tasks assigned to an employee through ToDo records. + """ + tasks = frappe.get_all('ToDo', filters={'allocated_to': assign_from, 'status': 'Open'}, fields=['reference_type', 'reference_name', 'description']) + + task_details = [] + for task in tasks: + reference_type = task.reference_type + reference_id = task.reference_name + task_description = task.description + if reference_type == 'Task': + task_details_query = frappe.get_all('Task', filters={'name': reference_id}, fields=['subject', 'project']) + if task_details_query: + task_details.append({ + 'task_id': reference_id, + 'subject': task_details_query[0]['subject'], + 'project': task_details_query[0]['project'] + }) + elif reference_type == 'Project' and assignment_type != 'Remove': + + tasks_for_project = frappe.get_all('Task', filters={'project': reference_id}, fields=['name', 'project', 'subject']) + project_task_details = [] + for task in tasks_for_project: + project_task_details.append({ + 'task_id': task.name, + 'subject': task.subject, + 'project': task.project + }) + task_details.extend(project_task_details) + return task_details + +@frappe.whitelist() +def remove_task_assignments(employee, selected_tasks_json): + """ + Remove Task-level ToDo assignments for a given employee. + If a project ends up with no more assigned tasks, its project-level + ToDo assignment is also cancelled. + """ + + try: + selected_tasks = json.loads(selected_tasks_json) + + if not selected_tasks: + return "No tasks selected." + + affected_projects = set() + + for task_id in selected_tasks: + + task_doc = frappe.get_value("Task", task_id, ["name", "project"], as_dict=True) + + if not task_doc: + frappe.log_error( + f"Task '{task_id}' not found while removing assignments", + "Task Removal Warning" + ) + continue + + project = task_doc.project + + if project: + affected_projects.add(project) + + todos = frappe.get_all( + "ToDo", + filters={ + "reference_type": "Task", + "reference_name": task_id, + "allocated_to": employee, + "status": "Open" + }, + fields=["name"] + ) + + for todo in todos: + todo_doc = frappe.get_doc("ToDo", todo.name) + todo_doc.status = "Cancelled" + todo_doc.save(ignore_permissions=True) + + for project in affected_projects: + + remaining_tasks = frappe.db.sql( + """ + SELECT t.name + FROM `tabToDo` td + JOIN `tabTask` t ON t.name = td.reference_name + WHERE + td.allocated_to = %s + AND td.status = 'Open' + AND td.reference_type = 'Task' + AND t.project = %s + """, + (employee, project), + as_dict=True + ) + + if not remaining_tasks: + + project_todos = frappe.get_all( + "ToDo", + filters={ + "reference_type": "Project", + "reference_name": project, + "allocated_to": employee, + "status": "Open" + }, + fields=["name"] + ) + + for ptd in project_todos: + todo_doc = frappe.get_doc("ToDo", ptd.name) + todo_doc.status = "Cancelled" + todo_doc.save(ignore_permissions=True) + + return "Assignments removed successfully" + + except Exception as e: + frappe.log_error(frappe.get_traceback(), "Task Assignment Removal Error") + return f"Error occurred: {str(e)}"