Merge pull request #12602 from PawanMeh/fixes_8540
Select multiple raw material items for Sub Contract Stock Entries
diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.js b/erpnext/buying/doctype/purchase_order/purchase_order.js
index d500081..3635b0b 100644
--- a/erpnext/buying/doctype/purchase_order/purchase_order.js
+++ b/erpnext/buying/doctype/purchase_order/purchase_order.js
@@ -143,23 +143,124 @@
var items = $.map(cur_frm.doc.items, function(d) { return d.bom ? d.item_code : false; });
var me = this;
- if(items.length===1) {
- me._make_stock_entry(items[0]);
- return;
+ if(items.length >= 1){
+ me.raw_material_data = [];
+ me.show_dialog = 1;
+ let title = "";
+ let fields = [
+ {fieldtype:'Section Break', label: __('Raw Materials')},
+ {fieldname: 'sub_con_rm_items', fieldtype: 'Table',
+ fields: [
+ {
+ fieldtype:'Data',
+ fieldname:'item_code',
+ label: __('Item'),
+ read_only:1,
+ in_list_view:1
+ },
+ {
+ fieldtype:'Data',
+ fieldname:'rm_item_code',
+ label: __('Raw Material'),
+ read_only:1,
+ in_list_view:1
+ },
+ {
+ fieldtype:'Float',
+ read_only:1,
+ fieldname:'qty',
+ label: __('Quantity'),
+ read_only:1,
+ in_list_view:1
+ },
+ {
+ fieldtype:'Data',
+ read_only:1,
+ fieldname:'warehouse',
+ label: __('Reserve Warehouse'),
+ in_list_view:1
+ },
+ {
+ fieldtype:'Float',
+ read_only:1,
+ fieldname:'rate',
+ label: __('Rate'),
+ hidden:1
+ },
+ {
+ fieldtype:'Float',
+ read_only:1,
+ fieldname:'amount',
+ label: __('Amount'),
+ hidden:1
+ },
+ {
+ fieldtype:'Link',
+ read_only:1,
+ fieldname:'uom',
+ label: __('UOM'),
+ hidden:1
+ }
+ ],
+ data: me.raw_material_data,
+ get_data: function() {
+ return me.raw_material_data;
+ }
+ }
+ ]
+
+ me.dialog = new frappe.ui.Dialog({
+ title: title,fields: fields
+ });
+
+ if (me.frm.doc['supplied_items']) {
+ me.frm.doc['supplied_items'].forEach((item, index) => {
+ if (item.rm_item_code && item.main_item_code) {
+ me.raw_material_data.push ({
+ 'name':index,
+ 'item_code': item.main_item_code,
+ 'rm_item_code': item.rm_item_code,
+ 'item_name': item.rm_item_code,
+ 'qty': item.required_qty,
+ 'warehouse':item.reserve_warehouse,
+ 'rate':item.rate,
+ 'amount':item.amount,
+ 'stock_uom':item.stock_uom
+ });
+ me.dialog.fields_dict.sub_con_rm_items.grid.refresh();
+ }
+ })
}
- frappe.prompt({fieldname:"item", options: items, fieldtype:"Select",
- label: __("Select Item for Transfer"), reqd: 1}, function(data) {
- me._make_stock_entry(data.item);
- }, __("Select Item"), __("Make"));
+
+ me.dialog.show()
+ this.dialog.set_primary_action(__('Transfer'), function() {
+ me.values = me.dialog.get_values();
+ if(me.values) {
+ me.values.sub_con_rm_items.map((row,i) => {
+ if (!row.item_code || !row.rm_item_code || !row.warehouse || !row.qty || row.qty === 0) {
+ frappe.throw(__("Item Code, warehouse, quantity are required on row" + (i+1)));
+ }
+ })
+ me._make_rm_stock_entry(me.dialog.fields_dict.sub_con_rm_items.grid.get_selected_children())
+ me.dialog.hide()
+ }
+ });
+ }
+
+ me.dialog.get_close_btn().on('click', () => {
+ me.dialog.hide();
+ });
+
},
- _make_stock_entry: function(item) {
+ _make_rm_stock_entry: function(rm_items) {
frappe.call({
- method:"erpnext.buying.doctype.purchase_order.purchase_order.make_stock_entry",
+ method:"erpnext.buying.doctype.purchase_order.purchase_order.make_rm_stock_entry",
args: {
purchase_order: cur_frm.doc.name,
- item_code: item
- },
+ rm_items: rm_items
+ }
+ ,
callback: function(r) {
var doclist = frappe.model.sync(r.message);
frappe.set_route("Form", doclist[0].doctype, doclist[0].name);
diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.py b/erpnext/buying/doctype/purchase_order/purchase_order.py
index b448988..ce0b89b 100644
--- a/erpnext/buying/doctype/purchase_order/purchase_order.py
+++ b/erpnext/buying/doctype/purchase_order/purchase_order.py
@@ -401,23 +401,51 @@
return doc
@frappe.whitelist()
-def make_stock_entry(purchase_order, item_code):
- purchase_order = frappe.get_doc("Purchase Order", purchase_order)
+def make_rm_stock_entry(purchase_order, rm_items):
- stock_entry = frappe.new_doc("Stock Entry")
- stock_entry.purpose = "Subcontract"
- stock_entry.purchase_order = purchase_order.name
- stock_entry.supplier = purchase_order.supplier
- stock_entry.supplier_name = purchase_order.supplier_name
- stock_entry.supplier_address = purchase_order.supplier_address
- stock_entry.address_display = purchase_order.address_display
- stock_entry.company = purchase_order.company
- stock_entry.from_bom = 1
- po_item = [d for d in purchase_order.items if d.item_code == item_code][0]
- stock_entry.fg_completed_qty = po_item.qty
- stock_entry.bom_no = po_item.bom
- stock_entry.get_items()
- return stock_entry.as_dict()
+ if isinstance(rm_items, basestring):
+ rm_items_list = json.loads(rm_items)
+ else:
+ frappe.throw(_("No Items available for transfer"))
+
+ if rm_items_list:
+ item_code_list = list(set(d["item_code"] for d in rm_items_list))
+ else:
+ frappe.throw(_("No Items selected for transfer"))
+
+ if purchase_order:
+ purchase_order = frappe.get_doc("Purchase Order", purchase_order)
+
+ if item_code_list:
+ item_wh = frappe._dict(frappe.db.sql("""select item_code, description
+ from `tabItem` where name in ({0})""".
+ format(", ".join(["%s"] * len(item_code_list))), item_code_list))
+ stock_entry = frappe.new_doc("Stock Entry")
+ stock_entry.purpose = "Subcontract"
+ stock_entry.purchase_order = purchase_order.name
+ stock_entry.supplier = purchase_order.supplier
+ stock_entry.supplier_name = purchase_order.supplier_name
+ stock_entry.supplier_address = purchase_order.supplier_address
+ stock_entry.address_display = purchase_order.address_display
+ stock_entry.company = purchase_order.company
+ stock_entry.from_bom = 1
+ for item_code in item_code_list:
+ po_item = [d for d in purchase_order.items if d.item_code == item_code][0]
+ bom_no = po_item.bom
+ for rm_item_data in rm_items_list:
+ if rm_item_data["item_code"] == item_code:
+ items_dict = {rm_item_data["rm_item_code"]:
+ {"item_name":rm_item_data["item_name"],
+ "description":item_wh.get(rm_item_data["rm_item_code"]),
+ 'qty':rm_item_data["qty"],
+ 'from_warehouse':rm_item_data["warehouse"],
+ 'stock_uom':rm_item_data["stock_uom"],
+ 'bom_no':bom_no}}
+ stock_entry.add_to_stock_entry_detail(items_dict, bom_no)
+ return stock_entry.as_dict()
+ else:
+ frappe.throw(_("No Items selected for transfer"))
+ return purchase_order.name
@frappe.whitelist()
def update_status(status, name):
diff --git a/erpnext/buying/doctype/purchase_order/test_purchase_order.py b/erpnext/buying/doctype/purchase_order/test_purchase_order.py
index 5ae4d3b..86a1337 100644
--- a/erpnext/buying/doctype/purchase_order/test_purchase_order.py
+++ b/erpnext/buying/doctype/purchase_order/test_purchase_order.py
@@ -6,8 +6,9 @@
import frappe
import frappe.defaults
from frappe.utils import flt, add_days, nowdate
-from erpnext.buying.doctype.purchase_order.purchase_order import (make_purchase_receipt, make_purchase_invoice, make_stock_entry as make_subcontract_transfer_entry)
+from erpnext.buying.doctype.purchase_order.purchase_order import (make_purchase_receipt, make_purchase_invoice, make_rm_stock_entry as make_subcontract_transfer_entry)
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
+import json
class TestPurchaseOrder(unittest.TestCase):
def test_make_purchase_receipt(self):
@@ -202,11 +203,11 @@
self.assertEquals(bin2.projected_qty, bin1.projected_qty - 10)
# Create stock transfer
- se = frappe.get_doc(make_subcontract_transfer_entry(po.name, "_Test FG Item"))
+ rm_item = [{"item_code":"_Test FG Item","rm_item_code":"_Test Item","item_name":"_Test Item",
+ "qty":6,"warehouse":"_Test Warehouse - _TC","rate":100,"amount":600,"stock_uom":"Nos"}]
+ rm_item_string = json.dumps(rm_item)
+ se = frappe.get_doc(make_subcontract_transfer_entry(po.name, rm_item_string))
se.to_warehouse = "_Test Warehouse 1 - _TC"
- for d in se.get("items"):
- if d.item_code == "_Test Item":
- d.qty = 6
se.save()
se.submit()
diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py
index 58fe442..8fa6517 100644
--- a/erpnext/manufacturing/doctype/bom/bom.py
+++ b/erpnext/manufacturing/doctype/bom/bom.py
@@ -588,9 +588,16 @@
if bom.docstatus != 1:
if not getattr(frappe.flags, "in_test", False):
frappe.throw(_("BOM {0} must be submitted").format(bom_no))
- if item and not (bom.item.lower() == item.lower() or \
- bom.item.lower() == cstr(frappe.db.get_value("Item", item, "variant_of")).lower()):
- frappe.throw(_("BOM {0} does not belong to Item {1}").format(bom_no, item))
+ if item:
+ rm_item_exists = False
+ for d in bom.items:
+ if (d.item_code.lower() == item.lower()):
+ rm_item_exists = True
+ if bom.item.lower() == item.lower() or \
+ bom.item.lower() == cstr(frappe.db.get_value("Item", item, "variant_of")).lower():
+ rm_item_exists = True
+ if not rm_item_exists:
+ frappe.throw(_("BOM {0} does not belong to Item {1}").format(bom_no, item))
@frappe.whitelist()
def get_children(doctype, parent=None, is_root=False, **filters):
diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py
index 4de2002..9bccfad 100644
--- a/erpnext/stock/doctype/stock_entry/stock_entry.py
+++ b/erpnext/stock/doctype/stock_entry/stock_entry.py
@@ -66,6 +66,7 @@
self.calculate_rate_and_amount(update_finished_item_rate=False)
def on_submit(self):
+
self.update_stock_ledger()
from erpnext.stock.doctype.serial_no.serial_no import update_serial_nos_after_submit
@@ -412,7 +413,7 @@
"""validation: finished good quantity should be same as manufacturing quantity"""
items_with_target_warehouse = []
for d in self.get('items'):
- if d.bom_no and flt(d.transfer_qty) != flt(self.fg_completed_qty) and (d.t_warehouse != getattr(self, "pro_doc", frappe._dict()).scrap_warehouse):
+ if self.purpose != "Subcontract" and d.bom_no and flt(d.transfer_qty) != flt(self.fg_completed_qty) and (d.t_warehouse != getattr(self, "pro_doc", frappe._dict()).scrap_warehouse):
frappe.throw(_("Quantity in row {0} ({1}) must be same as manufactured quantity {2}"). \
format(d.idx, d.transfer_qty, self.fg_completed_qty))
@@ -804,7 +805,7 @@
def add_to_stock_entry_detail(self, item_dict, bom_no=None):
expense_account, cost_center = frappe.db.get_values("Company", self.company, \
["default_expense_account", "cost_center"])[0]
-
+
for d in item_dict:
stock_uom = item_dict[d].get("stock_uom") or frappe.db.get_value("Item", d, "stock_uom")