From 1a6a94b7604e9bfc75a13fdf2550d2a336dec125 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Tue, 18 Dec 2012 18:42:02 +0530 Subject: [PATCH] stock entry cleanup --- .../doctype/purchase_order/purchase_order.py | 6 +- .../production_order/production_order.js | 10 +- patches/december_2012/production_cleanup.py | 10 +- patches/december_2012/stock_entry_cleanup.py | 20 +- patches/patch_list.py | 4 + stock/doctype/stock_entry/stock_entry.js | 126 ++--- stock/doctype/stock_entry/stock_entry.py | 443 ++++++++---------- stock/doctype/stock_entry/stock_entry.txt | 104 ++-- .../stock_entry_detail/stock_entry_detail.txt | 29 +- 9 files changed, 325 insertions(+), 427 deletions(-) diff --git a/buying/doctype/purchase_order/purchase_order.py b/buying/doctype/purchase_order/purchase_order.py index e52499cfd0..7182306b6d 100644 --- a/buying/doctype/purchase_order/purchase_order.py +++ b/buying/doctype/purchase_order/purchase_order.py @@ -299,7 +299,11 @@ class DocType(TransactionBase): def add_bom(self, d): #----- fetching default bom from Bill of Materials instead of Item Master -- - bom_det = sql("select t1.item, t2.item_code, t2.qty_consumed_per_unit, t2.moving_avg_rate, t2.value_as_per_mar, t2.stock_uom, t2.name, t2.parent from `tabBOM` t1, `tabBOM Item` t2 where t2.parent = t1.name and t1.item = '%s' and ifnull(t1.is_default,0) = 1 and t1.docstatus = 1" % d.item_code) + bom_det = sql("""select t1.item, t2.item_code, t2.qty_consumed_per_unit, + t2.moving_avg_rate, t2.value_as_per_mar, t2.stock_uom, t2.name, t2.parent + from `tabBOM` t1, `tabBOM Item` t2 + where t2.parent = t1.name and t1.item = %s + and ifnull(t1.is_default,0) = 1 and t1.docstatus = 1""", (d.item_code,)) if not bom_det: msgprint("No default BOM exists for item: %s" % d.item_code) diff --git a/manufacturing/doctype/production_order/production_order.js b/manufacturing/doctype/production_order/production_order.js index d36c548805..dd452b6662 100644 --- a/manufacturing/doctype/production_order/production_order.js +++ b/manufacturing/doctype/production_order/production_order.js @@ -93,21 +93,19 @@ cur_frm.cscript['Unstop Production Order'] = function() { cur_frm.cscript['Issue Raw Materials'] = function() { var doc = cur_frm.doc; - cur_frm.cscript.make_se(doc, process = 'Material Transfer'); + cur_frm.cscript.make_se(doc, 'Material Transfer'); } cur_frm.cscript['Update Finished Goods'] = function() { var doc = cur_frm.doc; - cur_frm.cscript.make_se(doc, process = 'Backflush'); + cur_frm.cscript.make_se(doc, 'Manufacture/Repack'); } -cur_frm.cscript.make_se = function(doc, process) { +cur_frm.cscript.make_se = function(doc, purpose) { var se = wn.model.get_new_doc("Stock Entry"); - se.purpose = 'Production Order'; - se.process = process; + se.purpose = purpose; se.production_order = doc.name; se.company = doc.company; - loaddoc('Stock Entry', se.name); } diff --git a/patches/december_2012/production_cleanup.py b/patches/december_2012/production_cleanup.py index 99042ddda0..46eb9e584d 100644 --- a/patches/december_2012/production_cleanup.py +++ b/patches/december_2012/production_cleanup.py @@ -3,8 +3,8 @@ import webnotes def execute(): delete_doctypes() rename_module() - rebuilt_exploded_bom() cleanup_bom() + rebuild_exploded_bom() def delete_doctypes(): from webnotes.model import delete_doc @@ -36,12 +36,14 @@ def rename_module(): # set end of life to null if "0000-00-00" webnotes.conn.sql("""update `tabItem` set end_of_life=null where end_of_life='0000-00-00'""") -def rebuilt_exploded_bom(): +def rebuild_exploded_bom(): from webnotes.model.code import get_obj for bom in webnotes.conn.sql("""select name from `tabBOM` where docstatus < 2"""): get_obj("BOM", bom[0], with_children=1).on_update() def cleanup_bom(): - webnotes.conn.sql("""UPDATE `tabBOM` SET is_active = if(is_active in ('Yes', 1), 1, 0), - with_operations = 1""") + webnotes.conn.sql("""UPDATE `tabBOM` SET is_active = 1 where ifnull(is_active, 'No') = 'Yes'""") + webnotes.conn.sql("""UPDATE `tabBOM` SET is_active = 0 where ifnull(is_active, 'No') = 'No'""") + webnotes.reload_doc("manufacturing", "doctype", "bom") + webnotes.conn.sql("""update `tabBOM` set with_operations = 1""") \ No newline at end of file diff --git a/patches/december_2012/stock_entry_cleanup.py b/patches/december_2012/stock_entry_cleanup.py index be93a9c2f2..97101407b5 100644 --- a/patches/december_2012/stock_entry_cleanup.py +++ b/patches/december_2012/stock_entry_cleanup.py @@ -12,35 +12,35 @@ def custom_fields(): "fieldname": "is_excisable_goods", "fieldtype": "Select", "options": "\nYes\nNo", - "insert_after": "company" + "insert_after": "Company" }, { "label": "Excisable Goods", "fieldname": "excisable_goods", "fieldtype": "Select", "options": "\nReturnable\nNon-Returnable)", - "insert_after": "amended_from" + "insert_after": "Amended From" }, { "label": "Under Rule", "fieldname": "under_rule", "fieldtype": "Select", "options": "\nOrdinary\n57 AC (5) a\n57 F (2) Non-Exc.", - "insert_after": "remarks" + "insert_after": "Remarks" }, { "label": "Transporter", "fieldname": "transporter", "fieldtype": "Data", "options": "", - "insert_after": "project_name" + "insert_after": "Project Name" }, { "label": "Transfer Date", "fieldname": "transfer_date", "fieldtype": "Date", "options": "", - "insert_after": "select_print_heading" + "insert_after": "Select Print Heading" }, ] @@ -62,9 +62,13 @@ def create_custom_field(fld): def deprecate_process(): webnotes.conn.sql("""update `tabStock Entry` - set `purpose`="Production Order - Material Transfer" + set `purpose`="Material Transfer" where process="Material Transfer" and purpose="Production Order" """) webnotes.conn.sql("""update `tabStock Entry` - set `purpose`="Production Order - Update Finished Goods" - where process="Backflush" and purpose="Production Order" """) \ No newline at end of file + set `purpose`="Manufacture/Repack" + where (process="Backflush" and purpose="Production Order") or purpose="Other" """) + + webnotes.conn.sql("""update `tabStock Entry` + set `purpose`="Subcontract" + where process="Subcontracting" """) \ No newline at end of file diff --git a/patches/patch_list.py b/patches/patch_list.py index 9f7344394f..77deb00f2d 100644 --- a/patches/patch_list.py +++ b/patches/patch_list.py @@ -542,4 +542,8 @@ patch_list = [ 'patch_module': 'patches.december_2012', 'patch_file': 'clear_web_cache', }, + { + 'patch_module': 'patches.december_2012', + 'patch_file': 'stock_entry_cleanup', + }, ] \ No newline at end of file diff --git a/stock/doctype/stock_entry/stock_entry.js b/stock/doctype/stock_entry/stock_entry.js index db418f0755..a38d113c2b 100644 --- a/stock/doctype/stock_entry/stock_entry.js +++ b/stock/doctype/stock_entry/stock_entry.js @@ -19,71 +19,19 @@ cur_frm.cscript.refresh = function(doc) { cur_frm.cscript.toggle_related_fields(doc); } - - cur_frm.cscript.toggle_related_fields = function(doc) { - - - - - - - - if(doc.purpose.startswith("Production Order") || doc.purpose == "Other") { - - - - - } - - - if (doc.purpose == 'Production Order' || doc.purpose == 'Other') { - unhide_field('get_items'); - hide_field(['from_warehouse', 'to_warehouse','purchase_receipt_no', - 'delivery_note_no', 'sales_invoice_no','warehouse_html']); - if (doc.purpose=='Production Order') unhide_field(['production_order', 'process']); - else { - doc.production_order = doc.process = ''; - hide_field(['production_order', 'process']); - } - - doc.from_warehouse = ''; - doc.to_warehouse = ''; - refresh_field(['from_warehosue', 'to_warehouse']); - if (doc.process == 'Backflush' || doc.purpose == 'Other') { - unhide_field('fg_completed_qty'); - } - else{ - hide_field('fg_completed_qty'); - doc.fg_completed_qty = 0; - } - } else { - unhide_field(['from_warehouse', 'to_warehouse']); - hide_field(['production_order', 'process', 'get_items', 'fg_completed_qty', - 'purchase_receipt_no','delivery_note_no', 'sales_invoice_no']); - doc.production_order = ''; - doc.process = ''; - doc.fg_completed_qty = 0; - } - - if(doc.purpose == 'Purchase Return') { doc.customer = doc.customer_name = doc.customer_address = - doc.delivery_note_no = doc.sales_invoice_no = ''; - unhide_field(['supplier','supplier_name','supplier_address','purchase_receipt_no']); - $(cur_frm.fields_dict.contact_section.row.wrapper).toggle(true); - } - else if(doc.purpose == 'Sales Return'){ - doc.supplier=doc.supplier_name = doc.supplier_address=doc.purchase_receipt_no=''; - unhide_field(['customer', 'customer_name', 'customer_address', - 'delivery_note_no', 'sales_invoice_no']); - $(cur_frm.fields_dict.contact_section.row.wrapper).toggle(true); - } else{ + doc.delivery_note_no = doc.sales_invoice_no = null; + doc.bom_no = doc.production_order = doc.fg_completed_qty = null; + } else if(doc.purpose == 'Sales Return') { + doc.supplier=doc.supplier_name = doc.supplier_address = doc.purchase_receipt_no=null; + doc.bom_no = doc.production_order = doc.fg_completed_qty = null; + } else { doc.customer = doc.customer_name = doc.customer_address = doc.delivery_note_no = doc.sales_invoice_no = doc.supplier = - doc.supplier_name = doc.supplier_address = doc.purchase_receipt_no = ''; + doc.supplier_name = doc.supplier_address = doc.purchase_receipt_no = null; } - refresh_many(lst); } cur_frm.cscript.delivery_note_no = function(doc,cdt,cdn){ @@ -95,7 +43,7 @@ cur_frm.cscript.sales_invoice_no = function(doc,cdt,cdn){ } cur_frm.cscript.customer = function(doc,cdt,cdn){ - if(doc.customer) get_server_fields('get_cust_addr','','',doc,cdt,cdn,1); + if(doc.customer) get_server_fields('get_cust_addr','','',doc,cdt,cdn,1); } cur_frm.cscript.purchase_receipt_no = function(doc,cdt,cdn){ @@ -103,48 +51,46 @@ cur_frm.cscript.purchase_receipt_no = function(doc,cdt,cdn){ } cur_frm.cscript.supplier = function(doc,cdt,cdn){ - if(doc.supplier) get_server_fields('get_supp_addr','','',doc,cdt,cdn,1); + if(doc.supplier) get_server_fields('get_supp_addr','','',doc,cdt,cdn,1); } cur_frm.fields_dict['production_order'].get_query = function(doc) { - return 'SELECT DISTINCT `tabProduction Order`.`name` FROM `tabProduction Order` WHERE `tabProduction Order`.`docstatus` = 1 AND `tabProduction Order`.`qty` > ifnull(`tabProduction Order`.`produced_qty`,0) AND `tabProduction Order`.`name` like "%s" ORDER BY `tabProduction Order`.`name` DESC LIMIT 50'; + return 'select name from `tabProduction Order` \ + where docstatus = 1 and qty > ifnull(produced_qty,0) AND %(key)s like "%s%%" \ + order by name desc limit 50'; } cur_frm.cscript.purpose = function(doc, cdt, cdn) { cur_frm.cscript.toggle_related_fields(doc, cdt, cdn); } -cur_frm.cscript.process = function(doc, cdt, cdn) { - cur_frm.cscript.toggle_related_fields(doc, cdt, cdn); -} - // item code - only if quantity present in source warehosue -// var fld = cur_frm.fields_dict['mtn_details'].grid.get_field('item_code'); -fld.query_description = "If Source Warehouse is selected, only items present in the warehouse with actual qty > 0 will be selected" +fld.query_description = "If Source Warehouse is selected, items with existing stock \ + for that warehouse will be selected"; + fld.get_query = function(doc, cdt, cdn) { var d = locals[cdt][cdn]; - if(d.s_warehouse) { return 'SELECT tabItem.name, tabItem.description, tabBin.actual_qty ' - +'FROM tabItem, tabBin ' - +'WHERE tabItem.name = tabBin.item_code ' - +'AND ifnull(`tabBin`.`actual_qty`,0) > 0 ' - +'AND tabBin.warehouse="'+ d.s_warehouse +'" ' - +'AND tabItem.docstatus < 2 ' - +'AND (ifnull(`tabItem`.`end_of_life`,"") = "" OR `tabItem`.`end_of_life` > NOW() OR `tabItem`.`end_of_life`="0000-00-00") ' - +'AND tabItem.%(key)s LIKE "%s" ' - +'ORDER BY tabItem.name ASC ' - +'LIMIT 50' + + 'FROM tabItem, tabBin ' + + 'WHERE tabItem.name = tabBin.item_code ' + + 'AND ifnull(`tabBin`.`actual_qty`,0) > 0 ' + + 'AND tabBin.warehouse="'+ d.s_warehouse +'" ' + + 'AND tabItem.docstatus < 2 ' + + 'AND (ifnull(`tabItem`.`end_of_life`,"") = "" OR `tabItem`.`end_of_life` > NOW() OR `tabItem`.`end_of_life`="0000-00-00") ' + + 'AND tabItem.%(key)s LIKE "%s" ' + + 'ORDER BY tabItem.name ASC ' + + 'LIMIT 50' } else { return 'SELECT tabItem.name, tabItem.description ' - +'FROM tabItem ' - +'WHERE tabItem.docstatus < 2 ' - +'AND (ifnull(`tabItem`.`end_of_life`,"") = "" OR `tabItem`.`end_of_life` > NOW() OR `tabItem`.`end_of_life`="0000-00-00") ' - +'AND tabItem.%(key)s LIKE "%s" ' - +'ORDER BY tabItem.name ASC ' - +'LIMIT 50' + + 'FROM tabItem ' + + 'WHERE tabItem.docstatus < 2 ' + + 'AND (ifnull(`tabItem`.`end_of_life`,"") = "" OR `tabItem`.`end_of_life` > NOW() OR `tabItem`.`end_of_life`="0000-00-00") ' + + 'AND tabItem.%(key)s LIKE "%s" ' + + 'ORDER BY tabItem.name ASC ' + + 'LIMIT 50' } } @@ -190,7 +136,6 @@ cur_frm.cscript.item_code = function(doc, cdt, cdn) { 'warehouse' : cstr(d.s_warehouse) || cstr(d.t_warehouse), 'transfer_qty' : d.transfer_qty, 'serial_no' : d.serial_no, - 'fg_item' : d.fg_item, 'bom_no' : d.bom_no }; get_server_fields('get_item_details',JSON.stringify(args),'mtn_details',doc,cdt,cdn,1); @@ -203,7 +148,6 @@ cur_frm.cscript.s_warehouse = function(doc, cdt, cdn) { 'warehouse' : cstr(d.s_warehouse) || cstr(d.t_warehouse), 'transfer_qty' : d.transfer_qty, 'serial_no' : d.serial_no, - 'fg_item' : d.fg_item, 'bom_no' : d.bom_no } get_server_fields('get_warehouse_details', JSON.stringify(args), @@ -212,16 +156,10 @@ cur_frm.cscript.s_warehouse = function(doc, cdt, cdn) { cur_frm.cscript.t_warehouse = cur_frm.cscript.s_warehouse; -cur_frm.cscript.transfer_qty = function(doc,cdt,cdn) { - var d = locals[cdt][cdn]; - if (doc.from_warehouse && (flt(d.transfer_qty) > flt(d.actual_qty))) { - alert("Transfer Quantity is more than Available Qty"); - } -} - cur_frm.cscript.qty = function(doc, cdt, cdn) { var d = locals[cdt][cdn]; - set_multiple('Stock Entry Detail', d.name, {'transfer_qty': flt(d.qty) * flt(d.conversion_factor)}, 'mtn_details'); + set_multiple('Stock Entry Detail', d.name, + {'transfer_qty': flt(d.qty) * flt(d.conversion_factor)}, 'mtn_details'); refresh_field('mtn_details'); } diff --git a/stock/doctype/stock_entry/stock_entry.py b/stock/doctype/stock_entry/stock_entry.py index cf646433a3..e64e1b31b4 100644 --- a/stock/doctype/stock_entry/stock_entry.py +++ b/stock/doctype/stock_entry/stock_entry.py @@ -47,6 +47,18 @@ class DocType(TransactionBase): self.validate_incoming_rate() self.validate_bom() self.validate_finished_goods() + + def on_submit(self): + self.update_serial_no(1) + self.update_stock_ledger(0) + # update Production Order + self.update_production_order(1) + + def on_cancel(self): + self.update_serial_no(0) + self.update_stock_ledger(1) + # update Production Order + self.update_production_order(0) def validate_serial_nos(self): sl_obj = get_obj("Stock Ledger") @@ -56,10 +68,8 @@ class DocType(TransactionBase): def validate_warehouse(self, pro_obj): """perform various (sometimes conditional) validations on warehouse""" - source_mandatory = ["Material Issue", "Material Transfer", - "Production Order - Material Transfer", "Purchase Return"] - target_mandatory = ["Material Receipt", "Material Transfer", - "Production Order - Material Transfer", "Sales Return"] + source_mandatory = ["Material Issue", "Material Transfer", "Purchase Return"] + target_mandatory = ["Material Receipt", "Material Transfer", "Sales Return"] fg_qty = 0 for d in getlist(self.doclist, 'mtn_details'): @@ -89,11 +99,15 @@ class DocType(TransactionBase): if self.doc.purpose not in source_mandatory: d.s_warehouse = None - if self.doc.purpose == "Production Order - Update Finished Goods": - if d.item_code == pro_obj.doc.item: + if self.doc.purpose == "Manufacture/Repack": + if d.bom_no: d.s_warehouse = None - - if cstr(d.t_warehouse) != pro_obj.doc.fg_warehouse: + + if not d.t_warehouse: + msgprint(_("Row # ") + "%s: " % cint(d.idx) + + _("Target Warehouse") + _(" is mandatory"), raise_exception=1) + + elif pro_obj and cstr(d.t_warehouse) != pro_obj.doc.fg_warehouse: msgprint(_("Row # ") + "%s: " % cint(d.idx) + _("Target Warehouse") + _(" should be same as that in ") + _("Production Order"), raise_exception=1) @@ -104,18 +118,14 @@ class DocType(TransactionBase): msgprint(_("Row # ") + "%s: " % cint(d.idx) + _("Source Warehouse") + _(" is mandatory"), raise_exception=1) - # if self.doc.fg_completed_qty and flt(self.doc.fg_completed_qty) != flt(fg_qty): - # msgprint("The Total of FG Qty %s in Stock Entry Detail do not match with FG Completed Qty %s" % (flt(fg_qty), flt(self.doc.fg_completed_qty))) - # raise Exception - def validate_production_order(self, pro_obj=None): if not pro_obj: - pro_obj = get_obj('Production Order', self.doc.production_order) + if self.doc.production_order: + pro_obj = get_obj('Production Order', self.doc.production_order) + else: + return - if self.doc.purpose == "Production Order - Material Transfer": - self.doc.fg_completed_qty = 0 - - elif self.doc.purpose == "Production Order - Update Finished Goods": + if self.doc.purpose == "Manufacture/Repack": if not flt(self.doc.fg_completed_qty): msgprint(_("Manufacturing Quantity") + _(" is mandatory"), raise_exception=1) @@ -128,7 +138,7 @@ class DocType(TransactionBase): + _("Hence, maximum allowed Manufacturing Quantity") + " = %s." % flt(pro_obj.doc.qty) - flt(pro_obj.doc.produced_qty), raise_exception=1) - else: + elif self.doc.purpose != "Material Transfer": self.doc.production_order = None def get_stock_and_rate(self): @@ -184,16 +194,88 @@ class DocType(TransactionBase): + _(" or the BOM is cancelled or inactive"), raise_exception=1) def validate_finished_goods(self): + """validation: finished good quantity should be same as manufacturing quantity""" for d in getlist(self.doclist, 'mtn_details'): if d.bom_no and flt(d.transfer_qty) != flt(self.doc.fg_completed_qty): - - + msgprint(_("Row #") + " %s: " % d.idx + + _("Quantity as per Stock UOM should be equal to Manufacturing Quantity"), + raise_exception=1) + + def update_serial_no(self, is_submit): + """Create / Update Serial No""" + sl_obj = get_obj('Stock Ledger') + if is_submit: + sl_obj.validate_serial_no_warehouse(self, 'mtn_details') + + for d in getlist(self.doclist, 'mtn_details'): + if d.serial_no: + serial_nos = sl_obj.get_sr_no_list(d.serial_no) + for x in serial_nos: + serial_no = x.strip() + if d.s_warehouse: + sl_obj.update_serial_delivery_details(self, d, serial_no, is_submit) + if d.t_warehouse: + sl_obj.update_serial_purchase_details(self, d, serial_no, is_submit, + 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 + serial_doc.save() + + def update_stock_ledger(self, is_cancelled=0): + self.values = [] + for d in getlist(self.doclist, 'mtn_details'): + if cstr(d.s_warehouse): + self.add_to_values(d, cstr(d.s_warehouse), -flt(d.transfer_qty), is_cancelled) + if cstr(d.t_warehouse): + self.add_to_values(d, cstr(d.t_warehouse), flt(d.transfer_qty), is_cancelled) + get_obj('Stock Ledger', 'Stock Ledger').update_stock(self.values, + self.doc.amended_from and 'Yes' or 'No') + def update_production_order(self, is_submit): + if self.doc.production_order: + # first perform some validations + # (they are here coz this fn is also called during on_cancel) + pro_obj = get_obj("Production Order", self.doc.production_order) + if flt(pro_obj.doc.docstatus) != 1: + msgprint("""You cannot do any transaction against + Production Order : %s, as it's not submitted""" + % (pro_obj.doc.name), raise_exception=1) + + if pro_obj.doc.status == 'Stopped': + msgprint("""You cannot do any transaction against Production Order : %s, + as it's status is 'Stopped'"""% (pro_obj.doc.name), raise_exception=1) + + if getdate(pro_obj.doc.posting_date) > getdate(self.doc.posting_date): + msgprint("""Posting Date of Stock Entry cannot be before Posting Date of + Production Order: %s"""% cstr(self.doc.production_order), raise_exception=1) + + # update bin + if self.doc.purpose == "Manufacture/Repack": + pro_obj.doc.produced_qty = flt(pro_obj.doc.produced_qty) + \ + (is_submit and 1 or -1 ) * flt(self.doc.fg_completed_qty) + args = { + "item_code": pro_obj.doc.production_item, + "posting_date": self.doc.posting_date, + "planned_qty": (is_submit and -1 or 1 ) * flt(self.doc.fg_completed_qty) + } + get_obj('Warehouse', pro_obj.doc.fg_warehouse).update_bin(args) + + # update production order status + pro_obj.doc.status = (flt(pro_obj.doc.qty)==flt(pro_obj.doc.produced_qty)) \ + and 'Completed' or 'In Process' + pro_obj.doc.save() + def get_item_details(self, arg): import json arg, actual_qty, in_rate = json.loads(arg), 0, 0 - item = sql("select stock_uom, description, item_name from `tabItem` where name = %s and (ifnull(end_of_life,'')='' or end_of_life ='0000-00-00' or end_of_life > now())", (arg.get('item_code')), as_dict = 1) + item = sql("""select stock_uom, description, item_name from `tabItem` + where name = %s and (ifnull(end_of_life,'')='' or end_of_life ='0000-00-00' + or end_of_life > now())""", (arg.get('item_code')), as_dict = 1) if not item: msgprint("Item is not active", raise_exception=1) @@ -213,12 +295,13 @@ class DocType(TransactionBase): ret.update(stock_and_rate) return ret - def get_uom_details(self, arg = ''): arg, ret = eval(arg), {} - uom = sql("select conversion_factor from `tabUOM Conversion Detail` where parent = %s and uom = %s", (arg['item_code'],arg['uom']), as_dict = 1) + uom = sql("""select conversion_factor from `tabUOM Conversion Detail` + where parent = %s and uom = %s""", (arg['item_code'], arg['uom']), as_dict = 1) if not uom: - msgprint("There is no Conversion Factor for UOM '%s' in Item '%s'" % (arg['uom'], arg['item_code'])) + msgprint("There is no Conversion Factor for UOM '%s' in Item '%s'" % (arg['uom'], + arg['item_code'])) ret = {'uom' : ''} else: ret = { @@ -233,13 +316,87 @@ class DocType(TransactionBase): ret = { "actual_qty" : self.get_as_on_stock(arg.get('item_code'), arg.get('warehouse'), self.doc.posting_date, self.doc.posting_time), - "incoming_rate" : self.get_incoming_rate(arg.get('item_code'), + "incoming_rate" : self.get_incoming_rate(arg.get('item_code'), arg.get('warehouse'), self.doc.posting_date, self.doc.posting_time, - arg.get('transfer_qty'), arg.get('serial_no'), arg.get('fg_item'), - arg.get('bom_no')) or 0 + arg.get('transfer_qty'), arg.get('serial_no'), arg.get('bom_no')) or 0 } return ret + + def get_items(self): + self.doclist = self.doc.clear_table(self.doclist, 'mtn_details', 1) + + if self.doc.production_order: + # common validations + 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 + + if self.doc.bom_no: + if self.doc.purpose in ["Material Issue", "Material Transfer", "Manufacture/Repack", + "Subcontract"]: + self.get_raw_materials() + + # 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) + + # add finished good item to Stock Entry Detail table + if self.doc.production_order: + self.add_to_stock_entry_detail(None, pro_obj.doc.fg_warehouse, { + cstr(pro_obj.doc.production_item): + [self.doc.fg_completed_qty, pro_obj.doc.description, pro_obj.doc.stock_uom] + }) + elif self.doc.purpose in ["Material Receipt", "Manufacture/Repack", + "Subcontract"]: + item = webnotes.conn.sql("""select item, description, uom from `tabBOM` + where name=%s""", (self.doc.bom_no,), as_dict=1) + self.add_to_stock_entry_detail(None, None, { + item[0]["item"] : + [self.doc.fg_completed_qty, item[0]["description"], item[0]["uom"]] + }) + + self.get_stock_and_rate() + + def get_raw_materials(self): + """ + get all items from flat bom except + child items of sub-contracted and sub assembly items + and sub assembly items itself. + """ + 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 + 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 + where it.name = fb.item_code and ifnull(it.is_pro_applicable, 'No') = 'No' + 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) + 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', + 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)) + 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() def make_items_dict(self, items_list): """makes dict of unique items with it's qty""" @@ -248,17 +405,15 @@ class DocType(TransactionBase): self.item_dict[i[0]][0] = flt(self.item_dict[i[0]][0]) + flt(i[1]) else: self.item_dict[i[0]] = [flt(i[1]), cstr(i[2]), cstr(i[3])] - - def update_only_remaining_qty(self): """ Only pending raw material to be issued to shop floor """ already_issued_item = {} 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.purpose = 'Production Order - Material Transfer' - and t2.docstatus = 1 group by t1.item_code""", self.doc.production_order) + 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]) @@ -267,231 +422,38 @@ class DocType(TransactionBase): if self.item_dict[d][0] <= 0: del self.item_dict[d] - - - def get_raw_materials(self, bom_no, fg_qty, use_multi_level_bom): - """ - get all items from flat bom except - child items of sub-contracted and sub assembly items - and sub assembly items itself. - """ - if 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 - 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 - where it.name = fb.item_code and ifnull(it.is_pro_applicable, 'No') = 'No' - and ifnull(it.is_sub_contracted_item, 'No') = 'No' and fb.docstatus<2 and fb.parent=%s - ) a - group by item_code,stock_uom - """ , (fg_qty, bom_no)) - self.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', description, stock_uom - from `tabBOM Item` - where parent = '%s' and docstatus < 2 - group by item_code - """ % (fg_qty, bom_no)) - - self.make_items_dict(fl_bom_sa_items) - - # Update only qty remaining to be issued for production - if self.doc.purpose == 'Production Order - Material Transfer': - self.update_only_remaining_qty() - - - def add_to_stock_entry_detail(self, source_wh, target_wh, item_dict, fg_item = 0, bom_no = ''): for d in item_dict: se_child = addchild(self.doc, 'mtn_details', 'Stock Entry Detail', 0, self.doclist) se_child.s_warehouse = source_wh se_child.t_warehouse = target_wh - se_child.fg_item = fg_item se_child.item_code = cstr(d) se_child.description = item_dict[d][1] se_child.uom = item_dict[d][2] se_child.stock_uom = item_dict[d][2] - se_child.reqd_qty = flt(item_dict[d][0]) se_child.qty = flt(item_dict[d][0]) se_child.transfer_qty = flt(item_dict[d][0]) se_child.conversion_factor = 1.00 if fg_item: se_child.bom_no = bom_no - def get_items(self): - bom_no = self.doc.bom_no - fg_qty = self.doc.fg_completed_qty - - if self.doc.purpose.startswith('Production Order'): - if not self.doc.production_order: - webnotes.msgprint(_("Please specify Production Order"), raise_exception=1) - - # common validations - pro_obj = get_obj('Production Order', self.doc.production_order) - if pro_obj: - self.validate_production_order(pro_obj) - - bom_no = pro_obj.doc.bom_no - fg_qty = (self.doc.purpose == 'Production Order - Update Finished Goods') \ - and flt(self.doc.fg_completed_qty) or flt(pro_obj.doc.qty) - - self.get_raw_materials(bom_no, fg_qty, self.doc.use_multi_level_bom) - self.doclist = self.doc.clear_table(self.doclist, 'mtn_details', 1) - - # 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) - - # add finished good item to Stock Entry Detail table - if self.doc.production_order: - self.add_to_stock_entry_detail(None, pro_obj.doc.fg_warehouse, { - cstr(pro_obj.doc.production_item): - [self.doc.fg_completed_qty, pro_obj.doc.description, pro_obj.doc.stock_uom] - }) - elif self.doc.bom_no: - item = webnotes.conn.sql("""select item, description, uom from `tabBOM` - where name=%s""", (self.doc.bom_no,), as_dict=1) - self.add_to_stock_entry_detail(None, None, { - item[0]["item"] : - [self.doc.fg_completed_qty, item[0]["description"], item[0]["uom"]] - }) - - - - - fg_item_dict = {} - if self.doc.purpose == 'Production Order - Update Finished Goods': - sw = '' - tw = cstr(pro_obj.doc.fg_warehouse) - fg_item_dict = { - cstr(pro_obj.doc.production_item) : [self.doc.fg_completed_qty, - pro_obj.doc.description, pro_obj.doc.stock_uom] - } - elif self.doc.purpose == 'Other' and self.doc.bom_no: - sw, tw = '', '' - item = sql("select item, description, uom from `tabBOM` where name = %s", self.doc.bom_no, as_dict=1) - fg_item_dict = { - item[0]['item'] : [self.doc.fg_completed_qty, - item[0]['description'], item[0]['uom']] - } - - if fg_item_dict: - self.add_to_stock_entry_detail(sw, tw, fg_item_dict, fg_item = 1, bom_no = bom_no) - - self.get_stock_and_rate() - - def validate_qty_as_per_stock_uom(self): - for d in getlist(self.doclist, 'mtn_details'): - if flt(d.transfer_qty) <= 0: - msgprint("Row No #%s: Qty as per Stock UOM can not be less than \ - or equal to zero" % cint(d.idx), raise_exception=1) - - def add_to_values(self, d, wh, qty, is_cancelled): self.values.append({ - 'item_code' : d.item_code, - 'warehouse' : wh, - 'posting_date' : self.doc.posting_date, - 'posting_time' : self.doc.posting_time, - 'voucher_type' : 'Stock Entry', - 'voucher_no' : self.doc.name, - 'voucher_detail_no' : d.name, - 'actual_qty' : qty, - 'incoming_rate' : flt(d.incoming_rate) or 0, - 'stock_uom' : d.stock_uom, - 'company' : self.doc.company, - 'is_cancelled' : (is_cancelled ==1) and 'Yes' or 'No', - 'batch_no' : d.batch_no, - 'serial_no' : d.serial_no + 'item_code': d.item_code, + 'warehouse': wh, + 'posting_date': self.doc.posting_date, + 'posting_time': self.doc.posting_time, + 'voucher_type': 'Stock Entry', + 'voucher_no': self.doc.name, + 'voucher_detail_no': d.name, + 'actual_qty': qty, + 'incoming_rate': flt(d.incoming_rate) or 0, + 'stock_uom': d.stock_uom, + 'company': self.doc.company, + 'is_cancelled': (is_cancelled ==1) and 'Yes' or 'No', + 'batch_no': d.batch_no, + 'serial_no': d.serial_no }) - - def update_stock_ledger(self, is_cancelled=0): - self.values = [] - for d in getlist(self.doclist, 'mtn_details'): - if cstr(d.s_warehouse): - self.add_to_values(d, cstr(d.s_warehouse), -flt(d.transfer_qty), is_cancelled) - if cstr(d.t_warehouse): - self.add_to_values(d, cstr(d.t_warehouse), flt(d.transfer_qty), is_cancelled) - get_obj('Stock Ledger', 'Stock Ledger').update_stock(self.values, self.doc.amended_from and 'Yes' or 'No') - - def update_production_order(self, is_submit): - if self.doc.production_order: - pro_obj = get_obj("Production Order", self.doc.production_order) - if flt(pro_obj.doc.docstatus) != 1: - msgprint("""You cannot do any transaction against - Production Order : %s, as it's not submitted""" - % (pro_obj.doc.name), raise_exception=1) - - if pro_obj.doc.status == 'Stopped': - msgprint("""You cannot do any transaction against Production Order : %s, - as it's status is 'Stopped'"""% (pro_obj.doc.name), raise_exception=1) - - if getdate(pro_obj.doc.posting_date) > getdate(self.doc.posting_date): - msgprint("""Posting Date of Stock Entry cannot be before Posting Date of - Production Order: %s"""% cstr(self.doc.production_order), raise_exception=1) - - if self.doc.purpose == "Production Order - Update Finished Goods": - pro_obj.doc.produced_qty = flt(pro_obj.doc.produced_qty) + \ - (is_submit and 1 or -1 ) * flt(self.doc.fg_completed_qty) - args = { - "item_code": pro_obj.doc.production_item, - "posting_date": self.doc.posting_date, - "planned_qty": (is_submit and -1 or 1 ) * flt(self.doc.fg_completed_qty) - } - get_obj('Warehouse', pro_obj.doc.fg_warehouse).update_bin(args) - - pro_obj.doc.status = (flt(pro_obj.doc.qty)==flt(pro_obj.doc.produced_qty)) \ - and 'Completed' or 'In Process' - pro_obj.doc.save() - - - # Create / Update Serial No - # ---------------------------------- - def update_serial_no(self, is_submit): - sl_obj = get_obj('Stock Ledger') - if is_submit: - sl_obj.validate_serial_no_warehouse(self, 'mtn_details') - - for d in getlist(self.doclist, 'mtn_details'): - if d.serial_no: - serial_nos = sl_obj.get_sr_no_list(d.serial_no) - for x in serial_nos: - serial_no = x.strip() - if d.s_warehouse: - sl_obj.update_serial_delivery_details(self, d, serial_no, is_submit) - if d.t_warehouse: - sl_obj.update_serial_purchase_details(self, d, serial_no, is_submit, 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 - serial_doc.save() - - - def on_submit(self): - self.validate_qty_as_per_stock_uom() - self.update_serial_no(1) - self.update_stock_ledger(0) - # update Production Order - self.update_production_order(1) - - - def on_cancel(self): - self.update_serial_no(0) - self.update_stock_ledger(1) - # update Production Order - self.update_production_order(0) - - def get_cust_values(self): """fetches customer details""" if self.doc.delivery_note_no: @@ -525,7 +487,8 @@ class DocType(TransactionBase): return result and result[0] or {} def get_supp_addr(self): - res = sql("select supplier_name,address from `tabSupplier` where name = '%s'"%self.doc.supplier) + res = sql("""select supplier_name from `tabSupplier` + where name=%s""", self.doc.supplier) addr = self.get_address_text(supplier = self.doc.supplier) ret = { 'supplier_name' : res and res[0][0] or '', diff --git a/stock/doctype/stock_entry/stock_entry.txt b/stock/doctype/stock_entry/stock_entry.txt index 4426f103fa..89e2b69535 100644 --- a/stock/doctype/stock_entry/stock_entry.txt +++ b/stock/doctype/stock_entry/stock_entry.txt @@ -2,9 +2,9 @@ { "owner": "Administrator", "docstatus": 0, - "creation": "2012-12-17 11:42:09", + "creation": "2012-12-18 13:47:41", "modified_by": "Administrator", - "modified": "2012-12-17 18:19:44" + "modified": "2012-12-18 17:17:16" }, { "is_submittable": 1, @@ -74,8 +74,8 @@ "report_hide": 0 }, { - "permlevel": 0, "print_hide": 0, + "permlevel": 0, "no_copy": 0, "oldfieldtype": "Select", "allow_on_submit": 0, @@ -88,7 +88,7 @@ "search_index": 0, "reqd": 1, "hidden": 0, - "options": "Material Issue\nMaterial Receipt\nMaterial Transfer\nSales Return\nPurchase Return\nSubcontracting\nProduction Order - Material Transfer\nProduction Order - Update Finished Goods\nOther", + "options": "Material Issue\nMaterial Receipt\nMaterial Transfer\nManufacture/Repack\nSubcontract\nSales Return\nPurchase Return", "report_hide": 0, "in_filter": 1 }, @@ -231,7 +231,7 @@ }, { "print_hide": 1, - "depends_on": "eval:doc.purpose.startsWith(\"Production Order\")", + "depends_on": "eval:inList([\"Material Transfer\", \"Manufacture/Repack\"], doc.purpose)", "no_copy": 0, "search_index": 1, "allow_on_submit": 0, @@ -249,7 +249,7 @@ "in_filter": 1 }, { - "depends_on": "eval:!doc.purpose.startsWith(\"Production Order\")", + "depends_on": "eval:!inList([\"Sales Return\", \"Purchase Return\"], doc.purpose)", "doctype": "DocField", "label": "BOM No", "options": "BOM", @@ -259,52 +259,22 @@ }, { "print_hide": 1, + "depends_on": "eval:!inList([\"Sales Return\", \"Purchase Return\"], doc.purpose)", "no_copy": 0, - "oldfieldtype": "Currency", + "search_index": 0, "allow_on_submit": 0, "doctype": "DocField", "label": "Manufacturing Quantity", "oldfieldname": "fg_completed_qty", "fieldname": "fg_completed_qty", "fieldtype": "Currency", - "search_index": 0, + "oldfieldtype": "Currency", "reqd": 0, "hidden": 0, "permlevel": 0, "report_hide": 0, "in_filter": 0 }, - { - "description": "If checked, BOM for sub-assembly items will be considered for getting raw materials. Otherwise, all sub-assembly items will be treated as a raw material.", - "doctype": "DocField", - "label": "Use Multi-Level BOM", - "fieldname": "use_multi_level_bom", - "fieldtype": "Check", - "permlevel": 0 - }, - { - "print_hide": 1, - "no_copy": 0, - "oldfieldtype": "Button", - "allow_on_submit": 0, - "doctype": "DocField", - "label": "Get Items", - "permlevel": 0, - "fieldname": "get_items", - "fieldtype": "Button", - "search_index": 0, - "reqd": 0, - "hidden": 0, - "options": "get_items", - "report_hide": 0, - "in_filter": 0 - }, - { - "doctype": "DocField", - "fieldname": "cb1", - "fieldtype": "Column Break", - "permlevel": 0 - }, { "print_hide": 1, "depends_on": "eval:doc.purpose==\"Sales Return\"", @@ -324,17 +294,6 @@ "report_hide": 0, "in_filter": 0 }, - { - "print_hide": 1, - "depends_on": "eval:doc.purpose==\"Sales Return\"", - "doctype": "DocField", - "label": "Sales Invoice No", - "options": "Sales Invoice", - "fieldname": "sales_invoice_no", - "fieldtype": "Link", - "hidden": 1, - "permlevel": 0 - }, { "print_hide": 1, "depends_on": "eval:doc.purpose==\"Purchase Return\"", @@ -354,6 +313,51 @@ "report_hide": 0, "in_filter": 0 }, + { + "doctype": "DocField", + "fieldname": "cb1", + "fieldtype": "Column Break", + "permlevel": 0 + }, + { + "description": "If checked, BOM for sub-assembly items will be considered for getting raw materials. Otherwise, all sub-assembly items will be treated as a raw material.", + "default": "1", + "depends_on": "eval:!inList([\"Sales Return\", \"Purchase Return\"], doc.purpose)", + "doctype": "DocField", + "label": "Use Multi-Level BOM", + "fieldname": "use_multi_level_bom", + "fieldtype": "Check", + "permlevel": 0 + }, + { + "print_hide": 1, + "depends_on": "eval:!inList([\"Sales Return\", \"Purchase Return\"], doc.purpose)", + "no_copy": 0, + "search_index": 0, + "allow_on_submit": 0, + "doctype": "DocField", + "label": "Get Items", + "permlevel": 0, + "fieldname": "get_items", + "fieldtype": "Button", + "oldfieldtype": "Button", + "reqd": 0, + "hidden": 0, + "options": "get_items", + "report_hide": 0, + "in_filter": 0 + }, + { + "print_hide": 1, + "depends_on": "eval:doc.purpose==\"Sales Return\"", + "doctype": "DocField", + "label": "Sales Invoice No", + "options": "Sales Invoice", + "fieldname": "sales_invoice_no", + "fieldtype": "Link", + "hidden": 1, + "permlevel": 0 + }, { "depends_on": "eval:(doc.purpose==\"Sales Return\" || doc.purpose==\"Purchase Return\")", "doctype": "DocField", diff --git a/stock/doctype/stock_entry_detail/stock_entry_detail.txt b/stock/doctype/stock_entry_detail/stock_entry_detail.txt index 9e0e5df6a7..6926c9a7cf 100644 --- a/stock/doctype/stock_entry_detail/stock_entry_detail.txt +++ b/stock/doctype/stock_entry_detail/stock_entry_detail.txt @@ -2,9 +2,9 @@ { "owner": "Administrator", "docstatus": 0, - "creation": "2012-07-03 13:29:47", + "creation": "2012-12-18 13:47:41", "modified_by": "Administrator", - "modified": "2012-12-17 16:12:42" + "modified": "2012-12-18 17:08:52" }, { "istable": 1, @@ -132,17 +132,6 @@ "fieldtype": "Link", "permlevel": 0 }, - { - "print_hide": 1, - "oldfieldtype": "Currency", - "doctype": "DocField", - "label": "Reqd Qty", - "oldfieldname": "reqd_qty", - "fieldname": "reqd_qty", - "fieldtype": "Currency", - "permlevel": 3, - "in_filter": 0 - }, { "print_hide": 1, "no_copy": 1, @@ -191,23 +180,15 @@ "in_filter": 0 }, { + "print_hide": 1, "description": "BOM No. for a Finished Good Item", + "no_copy": 0, "doctype": "DocField", "label": "BOM No", "options": "BOM", "fieldname": "bom_no", "fieldtype": "Link", + "hidden": 1, "permlevel": 0 - }, - { - "print_hide": 1, - "oldfieldtype": "Check", - "doctype": "DocField", - "label": "FG Item", - "oldfieldname": "fg_item", - "fieldname": "fg_item", - "fieldtype": "Check", - "permlevel": 0, - "in_filter": 1 } ] \ No newline at end of file