feat: allow adding & deleting of items in submitted SO & PO (#19911)

* feat: allow adding of items after quotation submission

* feat: allow deletion of items from submitted SO & PO

* fix: case when items are added and deleted at once

* fix: add test cases
* For deletion of items while Updating Items after submitting PO & SO
This commit is contained in:
Saqib 2019-12-24 16:42:30 +05:30 committed by Nabin Hait
parent 5557b16260
commit 0ad7449ae3
4 changed files with 138 additions and 2 deletions

View File

@ -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()

View File

@ -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"):

View File

@ -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',

View File

@ -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()
@ -331,6 +336,48 @@ class TestSalesOrder(unittest.TestCase):
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):
so = make_sales_order(item_code= "_Test Item", qty=4)