From 67867529c0f3122dc31626c0e7475506a7385a78 Mon Sep 17 00:00:00 2001 From: pawan Date: Mon, 22 Jan 2018 17:08:39 +0530 Subject: [PATCH 1/9] [fix] #8540 --- .../doctype/purchase_order/purchase_order.js | 144 ++++++++++++++++-- .../doctype/purchase_order/purchase_order.py | 54 ++++++- .../stock/doctype/stock_entry/stock_entry.py | 8 +- 3 files changed, 186 insertions(+), 20 deletions(-) diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.js b/erpnext/buying/doctype/purchase_order/purchase_order.js index 51cec6921b..501bab043b 100644 --- a/erpnext/buying/doctype/purchase_order/purchase_order.js +++ b/erpnext/buying/doctype/purchase_order/purchase_order.js @@ -140,18 +140,126 @@ erpnext.buying.PurchaseOrderController = erpnext.buying.BuyingController.extend( }, make_stock_entry: function() { - var items = $.map(cur_frm.doc.items, function(d) { return d.bom ? d.item_code : false; }); - var me = this; + 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; - } - 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")); - }, + 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:'Link', + fieldname:'item_code', + options: 'Item', + label: __('Item'), + read_only:1, + in_list_view:1 + }, + { + fieldtype:'Link', + fieldname:'rm_item_code', + options:'Item', + 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:'Link', + read_only:1, + fieldname:'warehouse', + label: __('Reserve Warehouse'), + read_only:1, + 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(); + } + }) + } + + 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) { frappe.call({ @@ -167,6 +275,20 @@ erpnext.buying.PurchaseOrderController = erpnext.buying.BuyingController.extend( }); }, + _make_rm_stock_entry: function(rm_items) { + frappe.call({ + method:"erpnext.buying.doctype.purchase_order.purchase_order.make_rm_stock_entry", + args: { + purchase_order: cur_frm.doc.name, + rm_items: rm_items + } + , + callback: function(r) { + frappe.set_route("List", "Stock Entry", "List",{"purchase_order":r.message,"doc_status":0}) + } + }); + }, + make_purchase_receipt: function() { frappe.model.open_mapped_doc({ method: "erpnext.buying.doctype.purchase_order.purchase_order.make_purchase_receipt", diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.py b/erpnext/buying/doctype/purchase_order/purchase_order.py index 8075ab32e4..b8e76b030f 100644 --- a/erpnext/buying/doctype/purchase_order/purchase_order.py +++ b/erpnext/buying/doctype/purchase_order/purchase_order.py @@ -34,12 +34,6 @@ class PurchaseOrder(BuyingController): 'overflow_type': 'order' }] - def onload(self): - super(PurchaseOrder, self).onload() - - self.set_onload('disable_fetch_last_purchase_rate', - cint(frappe.db.get_single_value("Buying Settings", "disable_fetch_last_purchase_rate"))) - def validate(self): super(PurchaseOrder, self).validate() @@ -399,7 +393,6 @@ def make_purchase_invoice(source_name, target_doc=None): @frappe.whitelist() def make_stock_entry(purchase_order, item_code): purchase_order = frappe.get_doc("Purchase Order", purchase_order) - stock_entry = frappe.new_doc("Stock Entry") stock_entry.purpose = "Subcontract" stock_entry.purchase_order = purchase_order.name @@ -415,6 +408,53 @@ def make_stock_entry(purchase_order, item_code): stock_entry.get_items() return stock_entry.as_dict() +@frappe.whitelist() +def make_rm_stock_entry(purchase_order, rm_items): + + 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)) + for item_code in 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 + 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 + 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"]}} + stock_entry.add_to_stock_entry_detail(items_dict) + stock_entry.save() + else: + frappe.throw(_("No Items selected for transfer")) + return purchase_order.name + @frappe.whitelist() def update_status(status, name): po = frappe.get_doc("Purchase Order", name) diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index dd8d99889c..beea97a8d0 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -126,7 +126,11 @@ class StockEntry(StockController): """perform various (sometimes conditional) validations on warehouse""" source_mandatory = ["Material Issue", "Material Transfer", "Subcontract", "Material Transfer for Manufacture"] - target_mandatory = ["Material Receipt", "Material Transfer", "Subcontract", "Material Transfer for Manufacture"] + #Allow creation of draft subcontract entries without target warehouse + if self.purpose == "Subcontract" and not frappe.db.exists("Stock Entry",{"name": self.name}): + target_mandatory = ["Material Receipt", "Material Transfer", "Material Transfer for Manufacture"] + else: + target_mandatory = ["Material Receipt", "Material Transfer", "Subcontract", "Material Transfer for Manufacture"] validate_for_manufacture_repack = any([d.bom_no for d in self.get("items")]) @@ -672,7 +676,7 @@ class StockEntry(StockController): # item dict = { item_code: {qty, description, stock_uom} } item_dict = get_bom_items_as_dict(self.bom_no, self.company, qty=qty, fetch_exploded = self.use_multi_level_bom) - + print item_dict for item in item_dict.values(): # if source warehouse presents in BOM set from_warehouse as bom source_warehouse item.from_warehouse = self.from_warehouse or item.source_warehouse or item.default_warehouse From 4ab6ef89a1bfc0d4edf4f5b6c75769f364ef1030 Mon Sep 17 00:00:00 2001 From: pawan Date: Mon, 22 Jan 2018 17:31:40 +0530 Subject: [PATCH 2/9] code alignment --- .../doctype/purchase_order/purchase_order.js | 222 +++++++++--------- .../doctype/purchase_order/purchase_order.py | 8 + .../stock/doctype/stock_entry/stock_entry.py | 2 +- 3 files changed, 120 insertions(+), 112 deletions(-) diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.js b/erpnext/buying/doctype/purchase_order/purchase_order.js index 501bab043b..f49f1103c3 100644 --- a/erpnext/buying/doctype/purchase_order/purchase_order.js +++ b/erpnext/buying/doctype/purchase_order/purchase_order.js @@ -140,126 +140,126 @@ erpnext.buying.PurchaseOrderController = erpnext.buying.BuyingController.extend( }, make_stock_entry: function() { - var items = $.map(cur_frm.doc.items, function(d) { return d.bom ? d.item_code : false; }); - var me = this; + 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._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:'Link', - fieldname:'item_code', - options: 'Item', - label: __('Item'), - read_only:1, - in_list_view:1 - }, - { - fieldtype:'Link', - fieldname:'rm_item_code', - options:'Item', - 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:'Link', - read_only:1, - fieldname:'warehouse', - label: __('Reserve Warehouse'), - read_only:1, - 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; - } + 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:'Link', + fieldname:'item_code', + options: 'Item', + label: __('Item'), + read_only:1, + in_list_view:1 + }, + { + fieldtype:'Link', + fieldname:'rm_item_code', + options:'Item', + 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:'Link', + read_only:1, + fieldname:'warehouse', + label: __('Reserve Warehouse'), + read_only:1, + 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 } - ] - - 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(); - } - }) + ], + data: me.raw_material_data, + get_data: function() { + return me.raw_material_data; } + } + ] - 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 = 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(); } + }) + } - me.dialog.get_close_btn().on('click', () => { - me.dialog.hide(); - }); + 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) { frappe.call({ diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.py b/erpnext/buying/doctype/purchase_order/purchase_order.py index b8e76b030f..372e8d3082 100644 --- a/erpnext/buying/doctype/purchase_order/purchase_order.py +++ b/erpnext/buying/doctype/purchase_order/purchase_order.py @@ -34,6 +34,12 @@ class PurchaseOrder(BuyingController): 'overflow_type': 'order' }] + def onload(self): + super(PurchaseOrder, self).onload() + + self.set_onload('disable_fetch_last_purchase_rate', + cint(frappe.db.get_single_value("Buying Settings", "disable_fetch_last_purchase_rate"))) + def validate(self): super(PurchaseOrder, self).validate() @@ -393,6 +399,7 @@ def make_purchase_invoice(source_name, target_doc=None): @frappe.whitelist() def make_stock_entry(purchase_order, item_code): purchase_order = frappe.get_doc("Purchase Order", purchase_order) + stock_entry = frappe.new_doc("Stock Entry") stock_entry.purpose = "Subcontract" stock_entry.purchase_order = purchase_order.name @@ -437,6 +444,7 @@ def make_rm_stock_entry(purchase_order, rm_items): stock_entry.supplier_address = purchase_order.supplier_address stock_entry.address_display = purchase_order.address_display stock_entry.company = purchase_order.company + stock_entry.docstatus = 0 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 diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index beea97a8d0..742f0fadfc 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -676,7 +676,7 @@ class StockEntry(StockController): # item dict = { item_code: {qty, description, stock_uom} } item_dict = get_bom_items_as_dict(self.bom_no, self.company, qty=qty, fetch_exploded = self.use_multi_level_bom) - print item_dict + for item in item_dict.values(): # if source warehouse presents in BOM set from_warehouse as bom source_warehouse item.from_warehouse = self.from_warehouse or item.source_warehouse or item.default_warehouse From afab0a64e534e9c25cac22abbb236a5ad971297a Mon Sep 17 00:00:00 2001 From: pawan Date: Mon, 22 Jan 2018 17:34:27 +0530 Subject: [PATCH 3/9] code alignment --- erpnext/buying/doctype/purchase_order/purchase_order.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.py b/erpnext/buying/doctype/purchase_order/purchase_order.py index 372e8d3082..a021690d23 100644 --- a/erpnext/buying/doctype/purchase_order/purchase_order.py +++ b/erpnext/buying/doctype/purchase_order/purchase_order.py @@ -38,7 +38,7 @@ class PurchaseOrder(BuyingController): super(PurchaseOrder, self).onload() self.set_onload('disable_fetch_last_purchase_rate', - cint(frappe.db.get_single_value("Buying Settings", "disable_fetch_last_purchase_rate"))) + cint(frappe.db.get_single_value("Buying Settings", "disable_fetch_last_purchase_rate"))) def validate(self): super(PurchaseOrder, self).validate() From 6a8eba891e89bb851e6d3de8d905cb759aef8102 Mon Sep 17 00:00:00 2001 From: pawan Date: Tue, 23 Jan 2018 00:57:44 +0530 Subject: [PATCH 4/9] Move target warehouse validation to submit --- .../stock/doctype/stock_entry/stock_entry.py | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index 742f0fadfc..ca133b4a7e 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -64,6 +64,13 @@ class StockEntry(StockController): self.calculate_rate_and_amount(update_finished_item_rate=False) def on_submit(self): + for d in self.items: + if self.purpose == "Subcontract" and not d.t_warehouse: + if self.to_warehouse: + d.t_warehouse = self.to_warehouse + else: + frappe.throw(_("Target warehouse is mandatory for row {0}").format(d.idx)) + self.update_stock_ledger() from erpnext.stock.doctype.serial_no.serial_no import update_serial_nos_after_submit @@ -126,11 +133,7 @@ class StockEntry(StockController): """perform various (sometimes conditional) validations on warehouse""" source_mandatory = ["Material Issue", "Material Transfer", "Subcontract", "Material Transfer for Manufacture"] - #Allow creation of draft subcontract entries without target warehouse - if self.purpose == "Subcontract" and not frappe.db.exists("Stock Entry",{"name": self.name}): - target_mandatory = ["Material Receipt", "Material Transfer", "Material Transfer for Manufacture"] - else: - target_mandatory = ["Material Receipt", "Material Transfer", "Subcontract", "Material Transfer for Manufacture"] + target_mandatory = ["Material Receipt", "Material Transfer", "Subcontract", "Material Transfer for Manufacture"] validate_for_manufacture_repack = any([d.bom_no for d in self.get("items")]) @@ -161,7 +164,9 @@ class StockEntry(StockController): if self.to_warehouse: d.t_warehouse = self.to_warehouse else: - frappe.throw(_("Target warehouse is mandatory for row {0}").format(d.idx)) + #move validation for sub contract to submit + if self.purpose != "Subcontract": + frappe.throw(_("Target warehouse is mandatory for row {0}").format(d.idx)) if self.purpose in ["Manufacture", "Repack"]: if validate_for_manufacture_repack: From 9b7093e4d6a412553370896cb5a48a6b6a528312 Mon Sep 17 00:00:00 2001 From: pawan Date: Tue, 23 Jan 2018 11:39:53 +0530 Subject: [PATCH 5/9] validation code improvement --- erpnext/stock/doctype/stock_entry/stock_entry.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index ca133b4a7e..4239751914 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -64,12 +64,13 @@ class StockEntry(StockController): self.calculate_rate_and_amount(update_finished_item_rate=False) def on_submit(self): - for d in self.items: - if self.purpose == "Subcontract" and not d.t_warehouse: - if self.to_warehouse: - d.t_warehouse = self.to_warehouse - else: - frappe.throw(_("Target warehouse is mandatory for row {0}").format(d.idx)) + if self.purpose == "Subcontract": + for d in self.items: + if not d.t_warehouse: + if self.to_warehouse: + d.t_warehouse = self.to_warehouse + else: + frappe.throw(_("Target warehouse is mandatory for row {0}").format(d.idx)) self.update_stock_ledger() From 2b885beaee494875d82f133023f5c6956939267c Mon Sep 17 00:00:00 2001 From: pawan Date: Wed, 7 Feb 2018 19:06:18 +0530 Subject: [PATCH 6/9] code changes for single stock entry --- .../doctype/purchase_order/purchase_order.js | 3 +- .../doctype/purchase_order/purchase_order.py | 37 +++++++++---------- erpnext/manufacturing/doctype/bom/bom.py | 11 ++++-- .../stock/doctype/stock_entry/stock_entry.py | 15 ++------ 4 files changed, 31 insertions(+), 35 deletions(-) diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.js b/erpnext/buying/doctype/purchase_order/purchase_order.js index f49f1103c3..f84feab61b 100644 --- a/erpnext/buying/doctype/purchase_order/purchase_order.js +++ b/erpnext/buying/doctype/purchase_order/purchase_order.js @@ -284,7 +284,8 @@ erpnext.buying.PurchaseOrderController = erpnext.buying.BuyingController.extend( } , callback: function(r) { - frappe.set_route("List", "Stock Entry", "List",{"purchase_order":r.message,"doc_status":0}) + 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 a021690d23..e78d4b72cf 100644 --- a/erpnext/buying/doctype/purchase_order/purchase_order.py +++ b/erpnext/buying/doctype/purchase_order/purchase_order.py @@ -435,30 +435,29 @@ def make_rm_stock_entry(purchase_order, rm_items): 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: - 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.docstatus = 0 - 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 + 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"]}} - stock_entry.add_to_stock_entry_detail(items_dict) - stock_entry.save() + {"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 diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py index a06645ad9f..81b899acbc 100644 --- a/erpnext/manufacturing/doctype/bom/bom.py +++ b/erpnext/manufacturing/doctype/bom/bom.py @@ -586,9 +586,14 @@ def validate_bom_no(item, bom_no): 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() or \ + d.item_code.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 4239751914..42ef79bbc1 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -64,13 +64,6 @@ class StockEntry(StockController): self.calculate_rate_and_amount(update_finished_item_rate=False) def on_submit(self): - if self.purpose == "Subcontract": - for d in self.items: - if not d.t_warehouse: - if self.to_warehouse: - d.t_warehouse = self.to_warehouse - else: - frappe.throw(_("Target warehouse is mandatory for row {0}").format(d.idx)) self.update_stock_ledger() @@ -165,9 +158,7 @@ class StockEntry(StockController): if self.to_warehouse: d.t_warehouse = self.to_warehouse else: - #move validation for sub contract to submit - if self.purpose != "Subcontract": - frappe.throw(_("Target warehouse is mandatory for row {0}").format(d.idx)) + frappe.throw(_("Target warehouse is mandatory for row {0}").format(d.idx)) if self.purpose in ["Manufacture", "Repack"]: if validate_for_manufacture_repack: @@ -417,7 +408,7 @@ class StockEntry(StockController): """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)) @@ -808,7 +799,7 @@ class StockEntry(StockController): 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") From 5f779eb8051b45b2d73b1f6ce2de96fc3b4f46ee Mon Sep 17 00:00:00 2001 From: pawan Date: Thu, 8 Feb 2018 09:07:58 +0530 Subject: [PATCH 7/9] validation fix --- erpnext/manufacturing/doctype/bom/bom.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py index 81b899acbc..ee30a49ef7 100644 --- a/erpnext/manufacturing/doctype/bom/bom.py +++ b/erpnext/manufacturing/doctype/bom/bom.py @@ -589,9 +589,11 @@ def validate_bom_no(item, bom_no): if item: rm_item_exists = False for d in bom.items: - if (d.item_code.lower() == item.lower() or \ - d.item_code.lower() == cstr(frappe.db.get_value("Item", item, "variant_of")).lower()): + 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)) From 1b8c13b17567d7a7643089fec6b118671744af2c Mon Sep 17 00:00:00 2001 From: pawan Date: Mon, 12 Feb 2018 16:59:59 +0530 Subject: [PATCH 8/9] call make_rm_stock_entry --- .../doctype/purchase_order/purchase_order.js | 30 +++---------------- 1 file changed, 4 insertions(+), 26 deletions(-) diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.js b/erpnext/buying/doctype/purchase_order/purchase_order.js index f84feab61b..a19d8a0f6d 100644 --- a/erpnext/buying/doctype/purchase_order/purchase_order.js +++ b/erpnext/buying/doctype/purchase_order/purchase_order.js @@ -143,12 +143,7 @@ erpnext.buying.PurchaseOrderController = erpnext.buying.BuyingController.extend( 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){ + if(items.length >= 1){ me.raw_material_data = []; me.show_dialog = 1; let title = ""; @@ -157,17 +152,15 @@ erpnext.buying.PurchaseOrderController = erpnext.buying.BuyingController.extend( {fieldname: 'sub_con_rm_items', fieldtype: 'Table', fields: [ { - fieldtype:'Link', + fieldtype:'Data', fieldname:'item_code', - options: 'Item', label: __('Item'), read_only:1, in_list_view:1 }, { - fieldtype:'Link', + fieldtype:'Data', fieldname:'rm_item_code', - options:'Item', label: __('Raw Material'), read_only:1, in_list_view:1 @@ -181,11 +174,10 @@ erpnext.buying.PurchaseOrderController = erpnext.buying.BuyingController.extend( in_list_view:1 }, { - fieldtype:'Link', + fieldtype:'Data', read_only:1, fieldname:'warehouse', label: __('Reserve Warehouse'), - read_only:1, in_list_view:1 }, { @@ -261,20 +253,6 @@ erpnext.buying.PurchaseOrderController = erpnext.buying.BuyingController.extend( }, - _make_stock_entry: function(item) { - frappe.call({ - method:"erpnext.buying.doctype.purchase_order.purchase_order.make_stock_entry", - args: { - purchase_order: cur_frm.doc.name, - item_code: item - }, - callback: function(r) { - var doclist = frappe.model.sync(r.message); - frappe.set_route("Form", doclist[0].doctype, doclist[0].name); - } - }); - }, - _make_rm_stock_entry: function(rm_items) { frappe.call({ method:"erpnext.buying.doctype.purchase_order.purchase_order.make_rm_stock_entry", From 471a018e6707075ada86915107c69e6756a27878 Mon Sep 17 00:00:00 2001 From: pawan Date: Mon, 12 Feb 2018 21:15:46 +0530 Subject: [PATCH 9/9] remove old stock entry method/rewrite test case --- .../doctype/purchase_order/purchase_order.py | 19 ------------------- .../purchase_order/test_purchase_order.py | 11 ++++++----- 2 files changed, 6 insertions(+), 24 deletions(-) diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.py b/erpnext/buying/doctype/purchase_order/purchase_order.py index e78d4b72cf..01427c2602 100644 --- a/erpnext/buying/doctype/purchase_order/purchase_order.py +++ b/erpnext/buying/doctype/purchase_order/purchase_order.py @@ -396,25 +396,6 @@ def make_purchase_invoice(source_name, target_doc=None): return doc -@frappe.whitelist() -def make_stock_entry(purchase_order, item_code): - purchase_order = frappe.get_doc("Purchase Order", purchase_order) - - 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() - @frappe.whitelist() def make_rm_stock_entry(purchase_order, rm_items): diff --git a/erpnext/buying/doctype/purchase_order/test_purchase_order.py b/erpnext/buying/doctype/purchase_order/test_purchase_order.py index 5ae4d3b344..86a133785f 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 unittest 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 @@ class TestPurchaseOrder(unittest.TestCase): 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()