From 8e3bd72ef73eb777d814cd0829c734dea7e2a663 Mon Sep 17 00:00:00 2001 From: Sumit Jain Date: Thu, 12 Feb 2026 12:37:53 +0530 Subject: [PATCH 1/2] fix(workflow): add function to retrieve user who set workflow state --- .../workflow_action/workflow_action.py | 42 ++++++++++++++++++- 1 file changed, 41 insertions(+), 1 deletion(-) diff --git a/frappe/workflow/doctype/workflow_action/workflow_action.py b/frappe/workflow/doctype/workflow_action/workflow_action.py index 8e04f89500..b3e78f8efa 100644 --- a/frappe/workflow/doctype/workflow_action/workflow_action.py +++ b/frappe/workflow/doctype/workflow_action/workflow_action.py @@ -198,17 +198,57 @@ def return_action_confirmation_page(doc, action, action_link, alert_doc_change=F def return_link_expired_page(doc, doc_workflow_state): + user_full_name = get_user_who_set_workflow_state(doc, doc_workflow_state) or frappe.get_value( + "User", doc.get("modified_by"), "full_name" + ) frappe.respond_as_web_page( _("Link Expired"), _("Document {0} has been set to state {1} by {2}").format( frappe.bold(doc.get("name")), frappe.bold(doc_workflow_state), - frappe.bold(frappe.get_value("User", doc.get("modified_by"), "full_name")), + frappe.bold(user_full_name), ), indicator_color="blue", ) +def get_user_who_set_workflow_state(doc, doc_workflow_state): + """Get the full name of the user who triggered the workflow action that set the document to the given state. + Falls back to None if no completed Workflow Action is found (e.g. state was set without workflow). + """ + workflow_name = get_workflow_name(doc.get("doctype")) + if not workflow_name: + return None + + # Get states that have a transition to the current workflow state + from_states = frappe.get_all( + "Workflow Transition", + filters={"parent": workflow_name, "next_state": doc_workflow_state}, + pluck="state", + ) + if not from_states: + return None + + # Find the most recently completed Workflow Action that led to this state + WorkflowAction = DocType("Workflow Action") + completed_by = ( + frappe.qb.from_(WorkflowAction) + .select(WorkflowAction.completed_by) + .where( + (WorkflowAction.reference_doctype == doc.get("doctype")) + & (WorkflowAction.reference_name == doc.get("name")) + & (WorkflowAction.status == "Completed") + & (WorkflowAction.workflow_state.isin(from_states)) + ) + .orderby(WorkflowAction.modified, order=frappe.qb.desc) + .limit(1) + ).run() + + if completed_by and completed_by[0][0]: + return frappe.get_value("User", completed_by[0][0], "full_name") + return None + + def update_completed_workflow_actions(doc, user=None, workflow=None, workflow_state=None): allowed_roles = get_allowed_roles(user, workflow, workflow_state) # There is no transaction leading upto this state From 7758bc49fe10f9c9b743a75fd0e54f82e3720631 Mon Sep 17 00:00:00 2001 From: Sumit Jain Date: Wed, 25 Feb 2026 11:22:41 +0530 Subject: [PATCH 2/2] fix(workflow): improve user retrieval for workflow state changes --- frappe/workflow/doctype/workflow_action/workflow_action.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/frappe/workflow/doctype/workflow_action/workflow_action.py b/frappe/workflow/doctype/workflow_action/workflow_action.py index b3e78f8efa..86bd133fed 100644 --- a/frappe/workflow/doctype/workflow_action/workflow_action.py +++ b/frappe/workflow/doctype/workflow_action/workflow_action.py @@ -206,7 +206,11 @@ def return_link_expired_page(doc, doc_workflow_state): _("Document {0} has been set to state {1} by {2}").format( frappe.bold(doc.get("name")), frappe.bold(doc_workflow_state), - frappe.bold(user_full_name), + frappe.bold( + user_full_name + if user_full_name + else frappe.get_value("User", doc.get("modified_by"), "full_name") + ), ), indicator_color="blue", )