From 013267655340a8183c0e40baa776a240d3d096cc Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Thu, 12 Feb 2026 12:18:05 +0530 Subject: [PATCH 01/47] fix: consider email account for communication duplicate check --- frappe/core/doctype/communication/communication.py | 2 +- frappe/email/doctype/email_account/test_email_account.py | 6 ++++-- frappe/email/receive.py | 5 ++++- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/frappe/core/doctype/communication/communication.py b/frappe/core/doctype/communication/communication.py index 59aefc53bf..16dae174ec 100644 --- a/frappe/core/doctype/communication/communication.py +++ b/frappe/core/doctype/communication/communication.py @@ -419,7 +419,7 @@ class Communication(Document, CommunicationEmailMixin): # Skip timeline links if a "Sent" communication already exists # else will create duplicate timeline entries if self.sent_or_received == "Received" and self.find_one_by_filters( - message_id=self.message_id, sent_or_received="Sent" + message_id=self.message_id, email_account=self.email_account, sent_or_received="Sent" ): return diff --git a/frappe/email/doctype/email_account/test_email_account.py b/frappe/email/doctype/email_account/test_email_account.py index c6f88c2862..93b5c36209 100644 --- a/frappe/email/doctype/email_account/test_email_account.py +++ b/frappe/email/doctype/email_account/test_email_account.py @@ -521,12 +521,14 @@ class TestInboundMail(IntegrationTestCase): def test_mail_exist_validation(self): """Do not create communication record if the mail is already downloaded into the system.""" + email_account = frappe.get_doc("Email Account", "_Test Email Account 1") mail_content = self.get_test_mail(fname="incoming-1.raw") message_id = Email(mail_content).message_id # Create new communication record in DB - communication = self.new_communication(message_id=message_id, sent_or_received="Received") + communication = self.new_communication( + message_id=message_id, email_account=email_account.name, sent_or_received="Received" + ) - email_account = frappe.get_doc("Email Account", "_Test Email Account 1") inbound_mail = InboundMail(mail_content, email_account, 12345, 1) new_communication = inbound_mail.process() diff --git a/frappe/email/receive.py b/frappe/email/receive.py index 8d1c64217a..ce52e98188 100644 --- a/frappe/email/receive.py +++ b/frappe/email/receive.py @@ -721,7 +721,10 @@ class InboundMail(Email): return return Communication.find_one_by_filters( - message_id=self.message_id, sent_or_received="Received", order_by="creation DESC" + message_id=self.message_id, + email_account=self.email_account.name, + sent_or_received="Received", + order_by="creation DESC", ) def is_sender_same_as_receiver(self): From 8e3bd72ef73eb777d814cd0829c734dea7e2a663 Mon Sep 17 00:00:00 2001 From: Sumit Jain Date: Thu, 12 Feb 2026 12:37:53 +0530 Subject: [PATCH 02/47] 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 bbf8f4d75b7f5e9dcc3f2651b9b10c3dcb2a130f Mon Sep 17 00:00:00 2001 From: Safwan Samsudeen Date: Mon, 23 Feb 2026 14:41:36 +0530 Subject: [PATCH 03/47] fix: add id to file preview --- frappe/core/doctype/file/file.js | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/frappe/core/doctype/file/file.js b/frappe/core/doctype/file/file.js index f3f1380855..6495aa4c1b 100644 --- a/frappe/core/doctype/file/file.js +++ b/frappe/core/doctype/file/file.js @@ -54,14 +54,14 @@ frappe.ui.form.on("File", { `); } else if (frappe.utils.is_video_file(frm.doc.file_url)) { $preview = $(`
`); @@ -72,14 +72,16 @@ frappe.ui.form.on("File", { style="background:#323639;" width="100%" height="1190" - src="${frappe.utils.escape_html(frm.doc.file_url)}" type="application/pdf" + src="${frappe.utils.escape_html(frm.doc.file_url + "?fid=" + frm.doc.name)}" type="application/pdf" > `); } else if (file_extension === "mp3") { $preview = $(`
`); From 967edf99806c94fec83356c413310d4c8b7fe66d Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Mon, 23 Feb 2026 15:09:06 +0530 Subject: [PATCH 04/47] fix: user action designs --- .../js/frappe/form/sidebar/form_sidebar.js | 19 +++-- .../frappe/form/templates/form_sidebar.html | 10 ++- frappe/public/scss/desk/form_sidebar.scss | 80 +++++++++++++++++++ 3 files changed, 101 insertions(+), 8 deletions(-) diff --git a/frappe/public/js/frappe/form/sidebar/form_sidebar.js b/frappe/public/js/frappe/form/sidebar/form_sidebar.js index 29f26b76db..ab02e96ab1 100644 --- a/frappe/public/js/frappe/form/sidebar/form_sidebar.js +++ b/frappe/public/js/frappe/form/sidebar/form_sidebar.js @@ -26,6 +26,7 @@ frappe.ui.form.Sidebar = class { .appendTo(this.page.sidebar.empty()); this.user_actions = this.sidebar.find(".user-actions"); + this.user_actions_list = this.sidebar.find(".user-actions-list"); this.image_section = this.sidebar.find(".sidebar-image-section"); this.image_wrapper = this.image_section.find(".sidebar-image-wrapper"); this.make_assignments(); @@ -245,19 +246,23 @@ frappe.ui.form.Sidebar = class { } add_user_action(label, click) { - return $("") - .html(label) - .appendTo( - $('
').appendTo( - this.user_actions.removeClass("hidden") - ) + const parent = this.user_actions_list.length ? this.user_actions_list : this.user_actions; + this.user_actions.removeClass("hidden"); + const row = $('
').appendTo(parent); + + return $('
') + .html( + `${label} + ${frappe.utils.icon("external-link", "sm")}` ) + .appendTo(row) .on("click", click); } clear_user_actions() { this.user_actions.addClass("hidden"); - this.user_actions.find(".user-action-row").remove(); + const parent = this.user_actions_list.length ? this.user_actions_list : this.user_actions; + parent.find(".user-action-row").remove(); } refresh_image() {} diff --git a/frappe/public/js/frappe/form/templates/form_sidebar.html b/frappe/public/js/frappe/form/templates/form_sidebar.html index a43117a67d..30446fe72b 100644 --- a/frappe/public/js/frappe/form/templates/form_sidebar.html +++ b/frappe/public/js/frappe/form/templates/form_sidebar.html @@ -1,4 +1,3 @@ - +