From 2c3c8aaa2dc9599522beb2e3d4d53ede68ca70d8 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Fri, 29 May 2020 21:55:38 +0530 Subject: [PATCH] fix: submitted sales order can be updated with no permission --- erpnext/controllers/accounts_controller.py | 46 ++++++++++++++----- erpnext/public/js/utils.js | 7 +++ .../doctype/sales_order/test_sales_order.py | 16 +++++++ .../sales_order_item/sales_order_item.json | 28 +++++------ 4 files changed, 71 insertions(+), 26 deletions(-) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index eecb143d55..ca78070bf4 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -1137,8 +1137,8 @@ def set_sales_order_defaults(parent_doctype, parent_doctype_name, child_docname, child_item.item_name = item.item_name child_item.description = item.description child_item.delivery_date = trans_item.get('delivery_date') or p_doc.delivery_date + child_item.conversion_factor = flt(d.get('conversion_factor')) or get_conversion_factor(item.item_code, item.stock_uom).get("conversion_factor") or 1.0 child_item.uom = item.stock_uom - child_item.conversion_factor = get_conversion_factor(item.item_code, item.stock_uom).get("conversion_factor") or 1.0 child_item.warehouse = get_item_warehouse(item, p_doc, overwrite_warehouse=True) if not child_item.warehouse: frappe.throw(_("Cannot find {} for item {}. Please set the same in Item Master or Stock Settings.") @@ -1157,8 +1157,8 @@ def set_purchase_order_defaults(parent_doctype, parent_doctype_name, child_docna child_item.item_name = item.item_name child_item.description = item.description child_item.schedule_date = trans_item.get('schedule_date') or p_doc.schedule_date + child_item.conversion_factor = flt(d.get('conversion_factor')) or get_conversion_factor(item.item_code, item.stock_uom).get("conversion_factor") or 1.0 child_item.uom = item.stock_uom - child_item.conversion_factor = get_conversion_factor(item.item_code, item.stock_uom).get("conversion_factor") or 1.0 child_item.base_rate = 1 # Initiallize value will update in parent validation child_item.base_amount = 1 # Initiallize value will update in parent validation return child_item @@ -1190,6 +1190,26 @@ def check_and_delete_children(parent, data): @frappe.whitelist() def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, child_docname="items"): + def check_permissions(doc, perm_type='create'): + try: + doc.check_permission(perm_type) + except: + action = "add" if perm_type == 'create' else "update" + frappe.throw(_("You do not have permissions to {} items in a Sales Order.").format(action), title=_("Insufficient Permissions")) + + def get_new_child_item(): + if parent_doctype == "Sales Order": + return set_sales_order_defaults(parent_doctype, parent_doctype_name, child_docname, d) + if parent_doctype == "Purchase Order": + return set_purchase_order_defaults(parent_doctype, parent_doctype_name, child_docname, d) + + def validate_quantity(child_item, d): + if parent_doctype == "Sales Order" and flt(d.get("qty")) < flt(child_item.delivered_qty): + frappe.throw(_("Cannot set quantity less than delivered quantity")) + + if parent_doctype == "Purchase Order" and flt(d.get("qty")) < flt(child_item.received_qty): + frappe.throw(_("Cannot set quantity less than received quantity")) + data = json.loads(trans_items) sales_doctypes = ['Sales Order', 'Sales Invoice', 'Delivery Note', 'Quotation'] @@ -1201,20 +1221,19 @@ def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, chil new_child_flag = False if not d.get("docname"): new_child_flag = True - if parent_doctype == "Sales Order": - child_item = set_sales_order_defaults(parent_doctype, parent_doctype_name, child_docname, d) - if parent_doctype == "Purchase Order": - child_item = set_purchase_order_defaults(parent_doctype, parent_doctype_name, child_docname, d) + check_permissions(parent, 'create') + child_item = get_new_child_item() else: + check_permissions(parent, 'write') child_item = frappe.get_doc(parent_doctype + ' Item', d.get("docname")) - if flt(child_item.get("rate")) == flt(d.get("rate")) and flt(child_item.get("qty")) == flt(d.get("qty")): + + rate_unchanged = flt(child_item.get("rate")) == flt(d.get("rate")) + qty_unchanged = flt(child_item.get("qty")) == flt(d.get("qty")) + conversion_factor_unchanged = flt(child_item.get("conversion_factor")) == flt(d.get("conversion_factor")) + if rate_unchanged and qty_unchanged and conversion_factor_changed: continue - if parent_doctype == "Sales Order" and flt(d.get("qty")) < flt(child_item.delivered_qty): - frappe.throw(_("Cannot set quantity less than delivered quantity")) - - if parent_doctype == "Purchase Order" and flt(d.get("qty")) < flt(child_item.received_qty): - frappe.throw(_("Cannot set quantity less than received quantity")) + validate_quantity(child_item, d) child_item.qty = flt(d.get("qty")) precision = child_item.precision("rate") or 2 @@ -1224,6 +1243,9 @@ def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, chil .format(child_item.idx, child_item.item_code)) else: child_item.rate = flt(d.get("rate")) + + if d.get("conversion_factor"): + child_item.conversion_factor = flt(d.get('conversion_factor')) if flt(child_item.price_list_rate): if flt(child_item.rate) > flt(child_item.price_list_rate): diff --git a/erpnext/public/js/utils.js b/erpnext/public/js/utils.js index 58969f2a9f..a487f83fe6 100755 --- a/erpnext/public/js/utils.js +++ b/erpnext/public/js/utils.js @@ -472,6 +472,12 @@ erpnext.utils.update_child_items = function(opts) { in_list_view: 1, label: frm.doc.doctype == 'Sales Order' ? __("Delivery Date") : __("Reqd by date") }) + fields.splice(3, 0, { + fieldtype: 'Float', + fieldname: "conversion_factor", + in_list_view: 1, + label: __("UOM Conversion Factor") + }) } const dialog = new frappe.ui.Dialog({ @@ -519,6 +525,7 @@ erpnext.utils.update_child_items = function(opts) { "item_code": d.item_code, "delivery_date": d.delivery_date, "schedule_date": d.schedule_date, + "conversion_factor": d.conversion_factor, "qty": d.qty, "rate": d.rate, }); diff --git a/erpnext/selling/doctype/sales_order/test_sales_order.py b/erpnext/selling/doctype/sales_order/test_sales_order.py index b8b0d404e5..90f9b094d1 100644 --- a/erpnext/selling/doctype/sales_order/test_sales_order.py +++ b/erpnext/selling/doctype/sales_order/test_sales_order.py @@ -400,6 +400,22 @@ class TestSalesOrder(unittest.TestCase): trans_item = json.dumps([{'item_code' : '_Test Item', 'rate' : 200, 'qty' : 2, 'docname': so.items[0].name}]) self.assertRaises(frappe.ValidationError, update_child_qty_rate,'Sales Order', trans_item, so.name) + def test_update_child_qty_rate_perm(self): + so = make_sales_order(item_code= "_Test Item", qty=4) + + user = 'test@example.com' + test_user = frappe.get_doc('User', user) + test_user.add_roles("Accounts User") + frappe.set_user(user) + + # update qty + trans_item = json.dumps([{'item_code' : '_Test Item', 'rate' : 200, 'qty' : 7, 'docname': so.items[0].name}]) + self.assertRaises(frappe.ValidationError, update_child_qty_rate,'Sales Order', trans_item, so.name) + + # add new item + trans_item = json.dumps([{'item_code' : '_Test Item', 'rate' : 100, 'qty' : 2}]) + self.assertRaises(frappe.ValidationError, update_child_qty_rate,'Sales Order', trans_item, so.name) + def test_warehouse_user(self): frappe.permissions.add_user_permission("Warehouse", "_Test Warehouse 1 - _TC", "test@example.com") frappe.permissions.add_user_permission("Warehouse", "_Test Warehouse 2 - _TC1", "test2@example.com") diff --git a/erpnext/selling/doctype/sales_order_item/sales_order_item.json b/erpnext/selling/doctype/sales_order_item/sales_order_item.json index e59349926e..18691591cd 100644 --- a/erpnext/selling/doctype/sales_order_item/sales_order_item.json +++ b/erpnext/selling/doctype/sales_order_item/sales_order_item.json @@ -214,7 +214,6 @@ "fieldtype": "Link", "label": "UOM", "options": "UOM", - "print_hide": 0, "reqd": 1 }, { @@ -769,22 +768,23 @@ "label": "Against Blanket Order" }, { - "fieldname": "bom_no", - "fieldtype": "Link", - "label": "BOM No", - "no_copy": 1, - "options": "BOM", - "print_hide": 1 - }, - { - "fieldname": "manufacturing_section_section", - "fieldtype": "Section Break", - "label": "Manufacturing Section" - } + "fieldname": "bom_no", + "fieldtype": "Link", + "label": "BOM No", + "no_copy": 1, + "options": "BOM", + "print_hide": 1 + }, + { + "fieldname": "manufacturing_section_section", + "fieldtype": "Section Break", + "label": "Manufacturing Section" + } ], "idx": 1, "istable": 1, - "modified": "2020-05-15 18:13:43.006493", + "links": [], + "modified": "2020-05-29 20:54:32.309460", "modified_by": "Administrator", "module": "Selling", "name": "Sales Order Item",