diff --git a/erpnext/buying/doctype/purchase_order/test_purchase_order.py b/erpnext/buying/doctype/purchase_order/test_purchase_order.py index 08f5d8b4d00..1712369e60b 100644 --- a/erpnext/buying/doctype/purchase_order/test_purchase_order.py +++ b/erpnext/buying/doctype/purchase_order/test_purchase_order.py @@ -118,6 +118,73 @@ class TestPurchaseOrder(unittest.TestCase): self.assertEqual(po.get("items")[0].amount, 1400) self.assertEqual(get_ordered_qty(), existing_ordered_qty + 3) + + def test_add_new_item_in_update_child_qty_rate(self): + po = create_purchase_order(do_not_save=1) + po.items[0].qty = 4 + po.save() + po.submit() + pr = make_pr_against_po(po.name, 2) + + po.load_from_db() + first_item_of_po = po.get("items")[0] + + trans_item = json.dumps([ + { + 'item_code': first_item_of_po.item_code, + 'rate': first_item_of_po.rate, + 'qty': first_item_of_po.qty, + 'docname': first_item_of_po.name + }, + {'item_code' : '_Test Item', 'rate' : 200, 'qty' : 7} + ]) + update_child_qty_rate('Purchase Order', trans_item, po.name) + + po.reload() + self.assertEquals(len(po.get('items')), 2) + self.assertEqual(po.status, 'To Receive and Bill') + + + def test_remove_item_in_update_child_qty_rate(self): + po = create_purchase_order(do_not_save=1) + po.items[0].qty = 4 + po.save() + po.submit() + pr = make_pr_against_po(po.name, 2) + + po.reload() + first_item_of_po = po.get("items")[0] + # add an item + trans_item = json.dumps([ + { + 'item_code': first_item_of_po.item_code, + 'rate': first_item_of_po.rate, + 'qty': first_item_of_po.qty, + 'docname': first_item_of_po.name + }, + {'item_code' : '_Test Item', 'rate' : 200, 'qty' : 7}]) + update_child_qty_rate('Purchase Order', trans_item, po.name) + + po.reload() + # check if can remove received item + trans_item = json.dumps([{'item_code' : '_Test Item', 'rate' : 200, 'qty' : 7, 'docname': po.get("items")[1].name}]) + self.assertRaises(frappe.ValidationError, update_child_qty_rate, 'Purchase Order', trans_item, po.name) + + first_item_of_po = po.get("items")[0] + trans_item = json.dumps([ + { + 'item_code': first_item_of_po.item_code, + 'rate': first_item_of_po.rate, + 'qty': first_item_of_po.qty, + 'docname': first_item_of_po.name + } + ]) + update_child_qty_rate('Purchase Order', trans_item, po.name) + + po.reload() + self.assertEquals(len(po.get('items')), 1) + self.assertEqual(po.status, 'To Receive and Bill') + def test_update_qty(self): po = create_purchase_order() diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 6150516ac8c..86f5d53b33c 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -1155,6 +1155,25 @@ def set_purchase_order_defaults(parent_doctype, parent_doctype_name, child_docna child_item.base_amount = 1 # Initiallize value will update in parent validation return child_item +def check_and_delete_children(parent, data): + deleted_children = [] + updated_item_names = [d.get("docname") for d in data] + for item in parent.items: + if item.name not in updated_item_names: + deleted_children.append(item) + + for d in deleted_children: + if parent.doctype == "Sales Order" and flt(d.delivered_qty): + frappe.throw(_("Row #{0}: Cannot delete item {1} which has already been delivered").format(d.idx, d.item_code)) + + if parent.doctype == "Purchase Order" and flt(d.received_qty): + frappe.throw(_("Row #{0}: Cannot delete item {1} which has already been received").format(d.idx, d.item_code)) + + if flt(d.billed_amt): + frappe.throw(_("Row #{0}: Cannot delete item {1} which has already been billed.").format(d.idx, d.item_code)) + + d.cancel() + d.delete() @frappe.whitelist() def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, child_docname="items"): @@ -1163,6 +1182,8 @@ def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, chil sales_doctypes = ['Sales Order', 'Sales Invoice', 'Delivery Note', 'Quotation'] parent = frappe.get_doc(parent_doctype, parent_doctype_name) + check_and_delete_children(parent, data) + for d in data: new_child_flag = False if not d.get("docname"): diff --git a/erpnext/public/js/utils.js b/erpnext/public/js/utils.js index f363999ebe2..3f444f83879 100755 --- a/erpnext/public/js/utils.js +++ b/erpnext/public/js/utils.js @@ -458,7 +458,8 @@ erpnext.utils.update_child_items = function(opts) { fieldname:"item_code", options: 'Item', in_list_view: 1, - read_only: 1, + read_only: 0, + disabled: 0, label: __('Item Code') }, { fieldtype:'Float', diff --git a/erpnext/selling/doctype/sales_order/test_sales_order.py b/erpnext/selling/doctype/sales_order/test_sales_order.py index feb6b76c4d3..d8e9a635b3a 100644 --- a/erpnext/selling/doctype/sales_order/test_sales_order.py +++ b/erpnext/selling/doctype/sales_order/test_sales_order.py @@ -321,7 +321,12 @@ class TestSalesOrder(unittest.TestCase): create_dn_against_so(so.name, 4) make_sales_invoice(so.name) - trans_item = json.dumps([{'item_code' : '_Test Item 2', 'rate' : 200, 'qty' : 7}]) + first_item_of_so = so.get("items")[0] + trans_item = json.dumps([ + {'item_code' : first_item_of_so.item_code, 'rate' : first_item_of_so.rate, \ + 'qty' : first_item_of_so.qty, 'docname': first_item_of_so.name}, + {'item_code' : '_Test Item 2', 'rate' : 200, 'qty' : 7} + ]) update_child_qty_rate('Sales Order', trans_item, so.name) so.reload() @@ -330,6 +335,48 @@ class TestSalesOrder(unittest.TestCase): self.assertEqual(so.get("items")[-1].qty, 7) self.assertEqual(so.get("items")[-1].amount, 1400) self.assertEqual(so.status, 'To Deliver and Bill') + + def test_remove_item_in_update_child_qty_rate(self): + so = make_sales_order(**{ + "item_list": [{ + "item_code": '_Test Item', + "qty": 5, + "rate":1000 + }] + }) + create_dn_against_so(so.name, 2) + make_sales_invoice(so.name) + + # add an item so as to try removing items + trans_item = json.dumps([ + {"item_code": '_Test Item', "qty": 5, "rate":1000, "docname": so.get("items")[0].name}, + {"item_code": '_Test Item 2', "qty": 2, "rate":500} + ]) + update_child_qty_rate('Sales Order', trans_item, so.name) + so.reload() + self.assertEqual(len(so.get("items")), 2) + + # check if delivered items can be removed + trans_item = json.dumps([{ + "item_code": '_Test Item 2', + "qty": 2, + "rate":500, + "docname": so.get("items")[1].name + }]) + self.assertRaises(frappe.ValidationError, update_child_qty_rate, 'Sales Order', trans_item, so.name) + + #remove last added item + trans_item = json.dumps([{ + "item_code": '_Test Item', + "qty": 5, + "rate":1000, + "docname": so.get("items")[0].name + }]) + update_child_qty_rate('Sales Order', trans_item, so.name) + + so.reload() + self.assertEqual(len(so.get("items")), 1) + self.assertEqual(so.status, 'To Deliver and Bill') def test_update_child_qty_rate(self):