diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 110b4834611..49a950fb5bf 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -8,7 +8,7 @@ from collections import defaultdict import frappe from frappe import _, bold, qb, throw from frappe.contacts.doctype.address.address import get_address_display -from frappe.model.workflow import get_workflow_name, is_transition_condition_satisfied +from frappe.model.workflow import get_workflow_name from frappe.query_builder import Criterion, DocType from frappe.query_builder.custom import ConstantColumn from frappe.query_builder.functions import Abs, Sum @@ -3853,7 +3853,9 @@ def validate_and_delete_children(parent, data, ordered_item=None) -> bool: @frappe.whitelist() -def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, child_docname="items"): +def update_child_qty_rate( + parent_doctype: str, trans_items: str, parent_doctype_name: str, child_docname: str = "items" +): from erpnext.buying.doctype.supplier_quotation.supplier_quotation import get_purchased_items from erpnext.selling.doctype.quotation.quotation import get_ordered_items @@ -3879,14 +3881,12 @@ def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, chil current_state = doc.get(workflow_doc.workflow_state_field) roles = frappe.get_roles() - transitions = [] - for transition in workflow_doc.transitions: - if transition.next_state == current_state and transition.allowed in roles: - if not is_transition_condition_satisfied(transition, doc): - continue - transitions.append(transition.as_dict()) + allowed = any( + state.state == current_state and (not state.allow_edit or state.allow_edit in roles) + for state in workflow_doc.states + ) - if not transitions: + if not allowed: frappe.throw( _("You are not allowed to update as per the conditions set in {} Workflow.").format( get_link_to_form("Workflow", workflow) diff --git a/erpnext/selling/doctype/sales_order/test_sales_order.py b/erpnext/selling/doctype/sales_order/test_sales_order.py index c7585df93fd..3da58733edb 100644 --- a/erpnext/selling/doctype/sales_order/test_sales_order.py +++ b/erpnext/selling/doctype/sales_order/test_sales_order.py @@ -721,6 +721,40 @@ class TestSalesOrder(ERPNextTestSuite): workflow.is_active = 0 workflow.save() + def test_update_child_qty_rate_follows_allow_edit(self): + from frappe.model.workflow import apply_workflow + + workflow = make_sales_order_edit_perm_workflow() + so = make_sales_order(item_code="_Test Item", qty=1, rate=150, do_not_submit=1) + apply_workflow(so, "Approve") + + trans_item = json.dumps( + [{"item_code": "_Test Item", "rate": 150, "qty": 2, "docname": so.items[0].name}] + ) + + mover = "test@example.com" + mover_user = frappe.get_doc("User", mover) + mover_user.add_roles("Sales User", "Test Junior Approver") + with self.set_user(mover): + # transitioned the doc into Approved but is not the configured editor + self.assertRaises( + frappe.ValidationError, update_child_qty_rate, "Sales Order", trans_item, so.name + ) + + editor = "test2@example.com" + editor_user = frappe.get_doc("User", editor) + editor_user.add_roles("Sales User", "Test Approver") + with self.set_user(editor): + # Test Approver is the "Only Allow Edit For" role on Approved + update_child_qty_rate("Sales Order", trans_item, so.name) + so.reload() + self.assertEqual(so.items[0].qty, 2) + + mover_user.remove_roles("Sales User", "Test Junior Approver", "Test Approver") + editor_user.remove_roles("Sales User", "Test Junior Approver", "Test Approver") + workflow.is_active = 0 + workflow.save() + def test_material_request_for_product_bundle(self): # Create the Material Request from the sales order for the Packing Items # Check whether the material request has the correct packing item or not. @@ -2888,3 +2922,41 @@ def make_sales_order_workflow(): workflow.insert(ignore_permissions=True) return workflow + + +def make_sales_order_edit_perm_workflow(): + if frappe.db.exists("Workflow", "SO Edit Perm Workflow"): + doc = frappe.get_doc("Workflow", "SO Edit Perm Workflow") + doc.set("is_active", 1) + doc.save() + return doc + + frappe.get_doc(doctype="Role", role_name="Test Junior Approver").insert(ignore_if_duplicate=True) + frappe.get_doc(doctype="Role", role_name="Test Approver").insert(ignore_if_duplicate=True) + frappe.cache().hdel("roles", frappe.session.user) + + workflow = frappe.get_doc( + { + "doctype": "Workflow", + "workflow_name": "SO Edit Perm Workflow", + "document_type": "Sales Order", + "workflow_state_field": "workflow_state", + "is_active": 1, + "send_email_alert": 0, + } + ) + workflow.append("states", dict(state="Pending", allow_edit="All")) + workflow.append("states", dict(state="Approved", allow_edit="Test Approver", doc_status=1)) + workflow.append( + "transitions", + dict( + state="Pending", + action="Approve", + next_state="Approved", + allowed="Test Junior Approver", + allow_self_approval=1, + ), + ) + workflow.insert(ignore_permissions=True) + + return workflow