diff --git a/home/page/latest_updates/latest_updates.js b/home/page/latest_updates/latest_updates.js index 818c979c985..f996b350c48 100644 --- a/home/page/latest_updates/latest_updates.js +++ b/home/page/latest_updates/latest_updates.js @@ -1,6 +1,13 @@ erpnext.updates = [ + ["21st December 2012", [ + "Manufacturing: For Material Transfer against Production Order, \ + fetch quantity pending to be transferred for each item." + ]], ["20th December 2012", [ - "Website: Create Product Search Page, Product Group Page on the website." + "Website: Create Product Search Page, Product Group Page on the website.", + "Manufacturing: \ + Refactored code, improved usability." ]], ["14th December 2012", [ "Website Module: Major Refactor - removed framework code from website." diff --git a/manufacturing/doctype/production_order/production_order.js b/manufacturing/doctype/production_order/production_order.js index 3ac277b46e6..2ec91e7b911 100644 --- a/manufacturing/doctype/production_order/production_order.js +++ b/manufacturing/doctype/production_order/production_order.js @@ -46,7 +46,7 @@ var cfn_set_fields = function(doc, dt, dn) { cur_frm.add_custom_button('Unstop', cur_frm.cscript['Unstop Production Order']); if (doc.status == 'Submitted' || doc.status == 'Material Transferred' || doc.status == 'In Process'){ - cur_frm.add_custom_button('Issue Raw Materials', cur_frm.cscript['Issue Raw Materials']); + cur_frm.add_custom_button('Transfer Raw Materials', cur_frm.cscript['Transfer Raw Materials']); cur_frm.add_custom_button('Update Finished Goods', cur_frm.cscript['Update Finished Goods']); } } @@ -74,7 +74,7 @@ cur_frm.cscript['Unstop Production Order'] = function() { $c_obj(make_doclist(doc.doctype, doc.name), 'stop_unstop', 'Unstopped', function(r, rt) {cur_frm.refresh();}); } -cur_frm.cscript['Issue Raw Materials'] = function() { +cur_frm.cscript['Transfer Raw Materials'] = function() { var doc = cur_frm.doc; cur_frm.cscript.make_se(doc, 'Material Transfer'); } diff --git a/public/js/utils.js b/public/js/utils.js index af3e57926f2..7e1a8ebf6e7 100644 --- a/public/js/utils.js +++ b/public/js/utils.js @@ -20,6 +20,7 @@ wn.provide('erpnext.utils'); erpnext.utils.Controller = Class.extend({ init: function(opts) { $.extend(this, opts); + this.setup && this.setup(); }, onload_post_render: function() { diff --git a/stock/doctype/stock_entry/stock_entry.js b/stock/doctype/stock_entry/stock_entry.js index 8e58e9a4d07..d1a0da8a1cc 100644 --- a/stock/doctype/stock_entry/stock_entry.js +++ b/stock/doctype/stock_entry/stock_entry.js @@ -29,6 +29,7 @@ erpnext.stock.StockEntry = erpnext.utils.Controller.extend({ refresh: function() { this._super(); this.toggle_related_fields(this.frm.doc); + this.toggle_enable_bom(); if (this.frm.doc.docstatus==1) this.frm.add_custom_button("Show Stock Ledger", this.show_stock_ledger) }, @@ -49,6 +50,18 @@ erpnext.stock.StockEntry = erpnext.utils.Controller.extend({ refresh_field('mtn_details'); }, + production_order: function() { + this.toggle_enable_bom(); + + this.frm.call({ + method: "get_production_order_details", + args: {production_order: this.frm.doc.production_order} + }); + }, + + toggle_enable_bom: function() { + this.frm.toggle_enable("bom_no", !this.frm.doc.production_order); + }, }); cur_frm.cscript = new erpnext.stock.StockEntry({frm: cur_frm}); @@ -62,7 +75,7 @@ cur_frm.cscript.toggle_related_fields = function(doc) { cur_frm.fields_dict["mtn_details"].grid.set_column_disp("s_warehouse", !disable_from_warehouse); cur_frm.fields_dict["mtn_details"].grid.set_column_disp("t_warehouse", !disable_to_warehouse); - + if(doc.purpose == 'Purchase Return') { doc.customer = doc.customer_name = doc.customer_address = doc.delivery_note_no = doc.sales_invoice_no = null; diff --git a/stock/doctype/stock_entry/stock_entry.py b/stock/doctype/stock_entry/stock_entry.py index 02528794892..1e4f3001fb5 100644 --- a/stock/doctype/stock_entry/stock_entry.py +++ b/stock/doctype/stock_entry/stock_entry.py @@ -32,7 +32,6 @@ class DocType(TransactionBase): def __init__(self, doc, doclist=[]): self.doc = doc self.doclist = doclist - self.item_dict = {} self.fname = 'mtn_details' def validate(self): @@ -229,7 +228,6 @@ class DocType(TransactionBase): self.doc.purpose) if self.doc.purpose == 'Purchase Return': - #delete_doc("Serial No", serial_no) serial_doc = Document("Serial No", serial_no) serial_doc.status = is_submit and 'Purchase Returned' or 'In Store' serial_doc.docstatus = is_submit and 2 or 0 @@ -336,11 +334,7 @@ class DocType(TransactionBase): pro_obj = get_obj('Production Order', self.doc.production_order) if pro_obj: self.validate_production_order(pro_obj) - self.doc.bom_no = pro_obj.doc.bom_no - self.doc.fg_completed_qty = (self.doc.purpose == "Manufacture/Repack") \ - and flt(self.doc.fg_completed_qty) \ - or flt(pro_obj.doc.qty) - flt(pro_obj.doc.produced_qty) else: # invalid production order self.doc.production_order = None @@ -348,12 +342,15 @@ class DocType(TransactionBase): if self.doc.bom_no: if self.doc.purpose in ["Material Issue", "Material Transfer", "Manufacture/Repack", "Subcontract"]: - self.get_raw_materials() + if self.doc.production_order and self.doc.purpose == "Material Transfer": + item_dict = self.get_pending_raw_materials(pro_obj) + else: + item_dict = self.get_bom_raw_materials(self.doc.fg_completed_qty) # add raw materials to Stock Entry Detail table self.add_to_stock_entry_detail(self.doc.from_warehouse, self.doc.to_warehouse, - self.item_dict) - + item_dict) + # add finished good item to Stock Entry Detail table -- along with bom_no if self.doc.production_order and self.doc.purpose == "Manufacture/Repack": self.add_to_stock_entry_detail(None, pro_obj.doc.fg_warehouse, { @@ -371,16 +368,28 @@ class DocType(TransactionBase): self.get_stock_and_rate() - def get_raw_materials(self): + def get_bom_raw_materials(self, qty): """ get all items from flat bom except child items of sub-contracted and sub assembly items and sub assembly items itself. """ + # item dict = { item_code: [qty, description, stock_uom] } + item_dict = {} + + def _make_items_dict(items_list): + """makes dict of unique items with it's qty""" + for item in items_list: + if item_dict.has_key(item.item_code): + item_dict[item.item_code][0] += flt(item.qty) + else: + item_dict[item.item_code] = [flt(item.qty), item.description, item.stock_uom] + if self.doc.use_multi_level_bom: # get all raw materials with sub assembly childs fl_bom_sa_child_item = sql("""select - item_code,ifnull(sum(qty_consumed_per_unit),0)*%s as qty,description,stock_uom + item_code,ifnull(sum(qty_consumed_per_unit),0)*%s as qty, + description,stock_uom from ( select distinct fb.name, fb.description, fb.item_code, fb.qty_consumed_per_unit, fb.stock_uom from `tabBOM Explosion Item` fb,`tabItem` it @@ -388,45 +397,73 @@ class DocType(TransactionBase): and ifnull(it.is_sub_contracted_item, 'No') = 'No' and fb.docstatus<2 and fb.parent=%s ) a - group by item_code, stock_uom""" , (self.doc.fg_completed_qty, self.doc.bom_no)) - self.make_items_dict(fl_bom_sa_child_item) + group by item_code, stock_uom""" , (qty, self.doc.bom_no), as_dict=1) + + if fl_bom_sa_child_item: + _make_items_dict(fl_bom_sa_child_item) else: # Get all raw materials considering multi level BOM, # if multi level bom consider childs of Sub-Assembly items - fl_bom_sa_items = sql("""select item_code, ifnull(sum(qty_consumed_per_unit), 0) * '%s', + fl_bom_sa_items = sql("""select item_code, + ifnull(sum(qty_consumed_per_unit), 0) * '%s' as qty, description, stock_uom from `tabBOM Item` where parent = '%s' and docstatus < 2 - group by item_code""" % (self.doc.fg_completed_qty, self.doc.bom_no)) + group by item_code""" % (qty, self.doc.bom_no), as_dict=1) - self.make_items_dict(fl_bom_sa_items) - - # Update only qty remaining to be issued for production - if self.doc.purpose == 'Material Transfer' and self.doc.production_order: - self.update_only_remaining_qty() + if fl_bom_sa_items: + _make_items_dict(fl_bom_sa_items) + + return item_dict - def make_items_dict(self, items_list): - """makes dict of unique items with it's qty""" - for i in items_list: - if self.item_dict.has_key(i[0]): - self.item_dict[i[0]][0] = flt(self.item_dict[i[0]][0]) + flt(i[1]) + def get_pending_raw_materials(self, pro_obj): + """ + issue (item quantity) that is pending to issue or desire to transfer, + whichever is less + """ + item_qty = self.get_bom_raw_materials(1) + issued_item_qty = self.get_issued_qty() + + max_qty = flt(pro_obj.doc.qty) + only_pending_fetched = [] + + for item in item_qty: + pending_to_issue = (max_qty * item_qty[item][0]) - issued_item_qty.get(item, 0) + desire_to_transfer = flt(self.doc.fg_completed_qty) * item_qty[item][0] + + if desire_to_transfer <= pending_to_issue: + item_qty[item][0] = desire_to_transfer else: - self.item_dict[i[0]] = [flt(i[1]), cstr(i[2]), cstr(i[3])] + item_qty[item][0] = pending_to_issue + if pending_to_issue: + only_pending_fetched.append(item) + + # delete items with 0 qty + for item in item_qty: + if not item_qty[item][0]: + del item_qty[item] + + # show some message + if not len(item_qty): + webnotes.msgprint(_("""All items have already been transferred \ + for this Production Order.""")) + + elif only_pending_fetched: + webnotes.msgprint(_("""Only quantities pending to be transferred \ + were fetched for the following items:\n""" + "\n".join(only_pending_fetched))) - def update_only_remaining_qty(self): - """ Only pending raw material to be issued to shop floor """ - already_issued_item = {} + return item_qty + + def get_issued_qty(self): + issued_item_qty = {} result = sql("""select t1.item_code, sum(t1.qty) from `tabStock Entry Detail` t1, `tabStock Entry` t2 where t1.parent = t2.name and t2.production_order = %s and t2.docstatus = 1 and t2.purpose = 'Material Transfer' group by t1.item_code""", self.doc.production_order) for t in result: - already_issued_item[t[0]] = flt(t[1]) - - for d in self.item_dict.keys(): - self.item_dict[d][0] -= already_issued_item.get(d, 0) - if self.item_dict[d][0] <= 0: - del self.item_dict[d] + issued_item_qty[t[0]] = flt(t[1]) + + return issued_item_qty def add_to_stock_entry_detail(self, source_wh, target_wh, item_dict, bom_no=None): for d in item_dict: @@ -440,7 +477,9 @@ class DocType(TransactionBase): se_child.qty = flt(item_dict[d][0]) se_child.transfer_qty = flt(item_dict[d][0]) se_child.conversion_factor = 1.00 - if bom_no: se_child.bom_no = bom_no + + # to be assigned for finished item + se_child.bom_no = bom_no def add_to_values(self, d, wh, qty, is_cancelled): self.values.append({ @@ -500,3 +539,10 @@ class DocType(TransactionBase): 'supplier_name' : res and res[0][0] or '', 'supplier_address' : addr and addr[0] or ''} return ret + +@webnotes.whitelist() +def get_production_order_details(production_order): + result = webnotes.conn.sql("""select bom_no, + ifnull(qty, 0) - ifnull(produced_qty, 0) as fg_completed_qty + from `tabProduction Order` where name = %s""", production_order, as_dict=1) + return result and result[0] or {} \ No newline at end of file