Update Items in Submit state can add new row (#15644)
* Update Items in Sumbit state can add new row
In Sales Order and Purchase Order when docstatus is submitted user can
use Update Item btn to add new child Items
* Remove unused code line
* Remove blocking db save thread line
* Remove Company as not standard Field in Purchase Order Item
* Minor indentation fix
* Add Unit Test, add new row in update_child_qty_rate
* Codacy fix
diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.js b/erpnext/buying/doctype/purchase_order/purchase_order.js
index a505e49..4b07895 100644
--- a/erpnext/buying/doctype/purchase_order/purchase_order.js
+++ b/erpnext/buying/doctype/purchase_order/purchase_order.js
@@ -42,6 +42,7 @@
frm: frm,
child_docname: "items",
child_doctype: "Purchase Order Detail",
+ cannot_add_row: false,
})
});
}
diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py
index 86ceb2e..df48d20 100644
--- a/erpnext/controllers/accounts_controller.py
+++ b/erpnext/controllers/accounts_controller.py
@@ -6,6 +6,7 @@
import json
from frappe import _, throw
from frappe.utils import today, flt, cint, fmt_money, formatdate, getdate, add_days, add_months, get_last_day, nowdate
+from erpnext.stock.get_item_details import get_conversion_factor
from erpnext.setup.utils import get_exchange_rate
from erpnext.accounts.utils import get_fiscal_years, validate_fiscal_year, get_account_currency
from erpnext.utilities.transaction_base import TransactionBase
@@ -1067,24 +1068,68 @@
}
return info
+def set_sales_order_defaults(parent_doctype, parent_doctype_name, child_docname, item_code):
+ """
+ Returns a Sales Order Item child item containing the default values
+ """
+ p_doctype = frappe.get_doc(parent_doctype, parent_doctype_name)
+ child_item = frappe.new_doc('Sales Order Item', p_doctype, child_docname)
+ item = frappe.get_doc("Item", item_code)
+ child_item.item_code = item.item_code
+ child_item.item_name = item.item_name
+ child_item.description = item.description
+ child_item.reqd_by_date = p_doctype.delivery_date
+ child_item.uom = item.stock_uom
+ child_item.conversion_factor = get_conversion_factor(item_code, item.stock_uom).get("conversion_factor") or 1.0
+ return child_item
+
+
+def set_purchase_order_defaults(parent_doctype, parent_doctype_name, child_docname, item_code):
+ """
+ Returns a Purchase Order Item child item containing the default values
+ """
+ p_doctype = frappe.get_doc(parent_doctype, parent_doctype_name)
+ child_item = frappe.new_doc('Purchase Order Item', p_doctype, child_docname)
+ item = frappe.get_doc("Item", item_code)
+ child_item.item_code = item.item_code
+ child_item.item_name = item.item_name
+ child_item.description = item.description
+ child_item.schedule_date = p_doctype.schedule_date
+ child_item.uom = item.stock_uom
+ child_item.conversion_factor = get_conversion_factor(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
+
@frappe.whitelist()
-def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name):
+def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, child_docname="items"):
data = json.loads(trans_items)
- for d in data:
- child_item = frappe.get_doc(parent_doctype + ' Item', d.get("docname"))
- child_item.qty = flt(d.get("qty"))
+ for d in data:
+ 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.get("item_code"))
+ if parent_doctype == "Purchase Order":
+ child_item = set_purchase_order_defaults(parent_doctype, parent_doctype_name, child_docname, d.get("item_code"))
+ else:
+ child_item = frappe.get_doc(parent_doctype + ' Item', d.get("docname"))
+
+ child_item.qty = flt(d.get("qty"))
if child_item.billed_amt > (flt(d.get("rate")) * flt(d.get("qty"))):
frappe.throw(_("Row #{0}: Cannot set Rate if amount is greater than billed amount for Item {1}.")
.format(child_item.idx, child_item.item_code))
else:
child_item.rate = flt(d.get("rate"))
child_item.flags.ignore_validate_update_after_submit = True
- child_item.save()
+ if new_child_flag:
+ child_item.insert()
+ else:
+ child_item.save()
p_doctype = frappe.get_doc(parent_doctype, parent_doctype_name)
-
p_doctype.flags.ignore_validate_update_after_submit = True
p_doctype.set_qty_as_per_stock_uom()
p_doctype.calculate_taxes_and_totals()
diff --git a/erpnext/public/js/utils.js b/erpnext/public/js/utils.js
index e293321..391323d 100644
--- a/erpnext/public/js/utils.js
+++ b/erpnext/public/js/utils.js
@@ -406,15 +406,19 @@
erpnext.utils.update_child_items = function(opts) {
const frm = opts.frm;
-
+ const cannot_add_row = (typeof opts.cannot_add_row === 'undefined') ? true : opts.cannot_add_row;
+ const child_docname = (typeof opts.cannot_add_row === 'undefined') ? "items" : opts.child_docname;
this.data = [];
const dialog = new frappe.ui.Dialog({
title: __("Update Items"),
fields: [
{fieldtype:'Section Break', label: __('Items')},
{
- fieldname: "trans_items", fieldtype: "Table", cannot_add_rows: true,
- in_place_edit: true, data: this.data,
+ fieldname: "trans_items",
+ fieldtype: "Table",
+ cannot_add_rows: cannot_add_row,
+ in_place_edit: true,
+ data: this.data,
get_data: () => {
return this.data;
},
@@ -450,10 +454,12 @@
const trans_items = this.get_values()["trans_items"];
frappe.call({
method: 'erpnext.controllers.accounts_controller.update_child_qty_rate',
+ freeze: true,
args: {
'parent_doctype': frm.doc.doctype,
'trans_items': trans_items,
- 'parent_doctype_name': frm.doc.name
+ 'parent_doctype_name': frm.doc.name,
+ 'child_docname': child_docname
},
callback: function() {
frm.reload_doc();
diff --git a/erpnext/selling/doctype/sales_order/sales_order.js b/erpnext/selling/doctype/sales_order/sales_order.js
index 54d7654..18f28a8 100644
--- a/erpnext/selling/doctype/sales_order/sales_order.js
+++ b/erpnext/selling/doctype/sales_order/sales_order.js
@@ -25,6 +25,7 @@
frm: frm,
child_docname: "items",
child_doctype: "Sales Order Detail",
+ cannot_add_row: false,
})
});
}
diff --git a/erpnext/selling/doctype/sales_order/test_sales_order.py b/erpnext/selling/doctype/sales_order/test_sales_order.py
index 65e91bc..64d543a 100644
--- a/erpnext/selling/doctype/sales_order/test_sales_order.py
+++ b/erpnext/selling/doctype/sales_order/test_sales_order.py
@@ -12,6 +12,8 @@
from erpnext.controllers.accounts_controller import update_child_qty_rate
import json
from erpnext.selling.doctype.sales_order.sales_order import make_raw_material_request
+
+
class TestSalesOrder(unittest.TestCase):
def tearDown(self):
frappe.set_user("Administrator")
@@ -268,6 +270,22 @@
self.assertEqual(get_reserved_qty("_Test Item Home Desktop 100"),
existing_reserved_qty_item2 + 20)
+ def test_add_new_item_in_update_child_qty_rate(self):
+ so = make_sales_order(item_code= "_Test Item", qty=4)
+ 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}])
+ update_child_qty_rate('Sales Order', trans_item, so.name)
+
+ so.reload()
+ self.assertEqual(so.get("items")[-1].item_code, '_Test Item 2')
+ self.assertEqual(so.get("items")[-1].rate, 200)
+ 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_update_child_qty_rate(self):
so = make_sales_order(item_code= "_Test Item", qty=4)
create_dn_against_so(so.name, 4)
@@ -760,7 +778,7 @@
})
so.delivery_date = add_days(so.transaction_date, 10)
-
+
if not args.do_not_save:
so.insert()
if not args.do_not_submit: