From 949a9200226d1c8bf92a9fc91752b2e73291d1a0 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Wed, 5 Jul 2017 13:55:41 +0530 Subject: [PATCH] Production Order Enhancements (#9432) * Production Order Enhancements - Show required items child table - Source warehouse for each raw materials, in Pro Order Item and BOM Item table - Group warehouse allowed for source and wip warehouse - Patch to populate required items, to fix status and reserved qty for stopped pro order - Cleaned up existing codes - Test cases * Set available qty in source and wip warehouse * minor fix in bom query naming * Minor Fixes * Reload BOM doctypes in patch --- erpnext/manufacturing/doctype/bom/bom.js | 20 +- erpnext/manufacturing/doctype/bom/bom.py | 20 +- .../doctype/bom/test_records.json | 21 +- .../bom_explosion_item.json | 35 +- .../doctype/bom_item/bom_item.json | 37 +- .../production_order/production_order.js | 312 +++++++------ .../production_order/production_order.json | 426 ++++++++---------- .../production_order/production_order.py | 171 +++---- .../production_order/test_production_order.py | 51 ++- .../production_order_item.json | 244 +++++++++- .../production_order_operation.json | 25 +- erpnext/patches.txt | 1 + .../patches/v8_0/update_production_orders.py | 44 ++ erpnext/stock/doctype/bin/bin.py | 9 +- .../stock/doctype/stock_entry/stock_entry.py | 51 ++- erpnext/stock/stock_balance.py | 2 +- erpnext/stock/utils.py | 25 +- 17 files changed, 986 insertions(+), 508 deletions(-) create mode 100644 erpnext/patches/v8_0/update_production_orders.py diff --git a/erpnext/manufacturing/doctype/bom/bom.js b/erpnext/manufacturing/doctype/bom/bom.js index 87dd565599..347314b2c1 100644 --- a/erpnext/manufacturing/doctype/bom/bom.js +++ b/erpnext/manufacturing/doctype/bom/bom.js @@ -5,12 +5,24 @@ frappe.provide("erpnext.bom"); frappe.ui.form.on("BOM", { setup: function(frm) { - frm.add_fetch('buying_price_list', 'currency', 'currency'); - frm.fields_dict["items"].grid.get_field("bom_no").get_query = function(doc, cdt, cdn){ + frm.add_fetch('buying_price_list', 'currency', 'currency') + + frm.set_query("bom_no", "items", function() { return { - filters: {'currency': frm.doc.currency} + filters: { + 'currency': frm.doc.currency, + 'company': frm.doc.company + } } - } + }); + + frm.set_query("source_warehouse", "items", function() { + return { + filters: { + 'company': frm.doc.company, + } + } + }); }, onload_post_render: function(frm) { diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py index 41eec8d933..7aef4fcaad 100644 --- a/erpnext/manufacturing/doctype/bom/bom.py +++ b/erpnext/manufacturing/doctype/bom/bom.py @@ -408,6 +408,7 @@ class BOM(WebsiteGenerator): self.add_to_cur_exploded_items(frappe._dict({ 'item_code' : d.item_code, 'item_name' : d.item_name, + 'source_warehouse': d.source_warehouse, 'description' : d.description, 'image' : d.image, 'stock_uom' : d.stock_uom, @@ -427,7 +428,8 @@ class BOM(WebsiteGenerator): def get_child_exploded_items(self, bom_no, stock_qty): """ Add all items from Flat BOM of child BOM""" # Did not use qty_consumed_per_unit in the query, as it leads to rounding loss - child_fb_items = frappe.db.sql("""select bom_item.item_code, bom_item.item_name, bom_item.description, + child_fb_items = frappe.db.sql("""select bom_item.item_code, bom_item.item_name, + bom_item.description, bom_item.source_warehouse, bom_item.stock_uom, bom_item.stock_qty, bom_item.rate, bom_item.stock_qty / ifnull(bom.quantity, 1) as qty_consumed_per_unit from `tabBOM Explosion Item` bom_item, tabBOM bom @@ -437,9 +439,10 @@ class BOM(WebsiteGenerator): self.add_to_cur_exploded_items(frappe._dict({ 'item_code' : d['item_code'], 'item_name' : d['item_name'], + 'source_warehouse' : d['source_warehouse'], 'description' : d['description'], 'stock_uom' : d['stock_uom'], - 'stock_qty' : d['qty_consumed_per_unit']*stock_qty, + 'stock_qty' : d['qty_consumed_per_unit'] * stock_qty, 'rate' : flt(d['rate']), })) @@ -493,6 +496,7 @@ def get_bom_items_as_dict(bom, company, qty=1, fetch_exploded=1, fetch_scrap_ite item.default_warehouse, item.expense_account as expense_account, item.buying_cost_center as cost_center + {select_columns} from `tab{table}` bom_item, `tabBOM` bom, `tabItem` item where @@ -501,18 +505,20 @@ def get_bom_items_as_dict(bom, company, qty=1, fetch_exploded=1, fetch_scrap_ite and bom_item.parent = bom.name and item.name = bom_item.item_code and is_stock_item = 1 - {conditions} - group by item_code, stock_uom""" + {where_conditions} + group by item_code, stock_uom""" if fetch_exploded: query = query.format(table="BOM Explosion Item", - conditions="""and item.is_sub_contracted_item = 0""") + where_conditions="""and item.is_sub_contracted_item = 0""", + select_columns = ", bom_item.source_warehouse") items = frappe.db.sql(query, { "qty": qty, "bom": bom }, as_dict=True) elif fetch_scrap_items: - query = query.format(table="BOM Scrap Item", conditions="") + query = query.format(table="BOM Scrap Item", where_conditions="", select_columns="") items = frappe.db.sql(query, { "qty": qty, "bom": bom }, as_dict=True) else: - query = query.format(table="BOM Item", conditions="") + query = query.format(table="BOM Item", where_conditions="", + select_columns = ", bom_item.source_warehouse") items = frappe.db.sql(query, { "qty": qty, "bom": bom }, as_dict=True) for item in items: diff --git a/erpnext/manufacturing/doctype/bom/test_records.json b/erpnext/manufacturing/doctype/bom/test_records.json index 0f1143ef7d..6c24871977 100644 --- a/erpnext/manufacturing/doctype/bom/test_records.json +++ b/erpnext/manufacturing/doctype/bom/test_records.json @@ -8,7 +8,8 @@ "parentfield": "items", "stock_qty": 1.0, "rate": 5000.0, - "stock_uom": "_Test UOM" + "stock_uom": "_Test UOM", + "source_warehouse": "_Test Warehouse - _TC" }, { "amount": 2000.0, @@ -17,7 +18,8 @@ "parentfield": "items", "stock_qty": 2.0, "rate": 1000.0, - "stock_uom": "_Test UOM" + "stock_uom": "_Test UOM", + "source_warehouse": "_Test Warehouse - _TC" } ], "docstatus": 1, @@ -48,7 +50,8 @@ "parentfield": "items", "stock_qty": 1.0, "rate": 5000.0, - "stock_uom": "_Test UOM" + "stock_uom": "_Test UOM", + "source_warehouse": "_Test Warehouse - _TC" }, { "amount": 2000.0, @@ -57,7 +60,8 @@ "parentfield": "items", "stock_qty": 2.0, "rate": 1000.0, - "stock_uom": "_Test UOM" + "stock_uom": "_Test UOM", + "source_warehouse": "_Test Warehouse - _TC" } ], "docstatus": 1, @@ -86,7 +90,8 @@ "parentfield": "items", "stock_qty": 1.0, "rate": 5000.0, - "stock_uom": "_Test UOM" + "stock_uom": "_Test UOM", + "source_warehouse": "_Test Warehouse - _TC" }, { "amount": 2000.0, @@ -96,7 +101,8 @@ "parentfield": "items", "stock_qty": 3.0, "rate": 1000.0, - "stock_uom": "_Test UOM" + "stock_uom": "_Test UOM", + "source_warehouse": "_Test Warehouse - _TC" } ], "docstatus": 1, @@ -126,7 +132,8 @@ "parentfield": "items", "stock_qty": 2.0, "rate": 3000.0, - "stock_uom": "_Test UOM" + "stock_uom": "_Test UOM", + "source_warehouse": "_Test Warehouse - _TC" } ], "docstatus": 1, diff --git a/erpnext/manufacturing/doctype/bom_explosion_item/bom_explosion_item.json b/erpnext/manufacturing/doctype/bom_explosion_item/bom_explosion_item.json index e1a3d4da53..19fc37fa84 100644 --- a/erpnext/manufacturing/doctype/bom_explosion_item/bom_explosion_item.json +++ b/erpnext/manufacturing/doctype/bom_explosion_item/bom_explosion_item.json @@ -7,9 +7,9 @@ "beta": 0, "creation": "2013-03-07 11:42:57", "custom": 0, - "default_print_format": "Standard", "docstatus": 0, "doctype": "DocType", + "document_type": "Setup", "editable_grid": 1, "engine": "InnoDB", "fields": [ @@ -104,6 +104,37 @@ "set_only_once": 0, "unique": 0 }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "source_warehouse", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Source Warehouse", + "length": 0, + "no_copy": 0, + "options": "Warehouse", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 1, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, { "allow_bulk_edit": 0, "allow_on_submit": 0, @@ -481,7 +512,7 @@ "issingle": 0, "istable": 1, "max_attachments": 0, - "modified": "2017-06-02 19:29:34.498719", + "modified": "2017-05-29 17:51:18.151002", "modified_by": "Administrator", "module": "Manufacturing", "name": "BOM Explosion Item", diff --git a/erpnext/manufacturing/doctype/bom_item/bom_item.json b/erpnext/manufacturing/doctype/bom_item/bom_item.json index 966b89bd4c..d259dd55d8 100644 --- a/erpnext/manufacturing/doctype/bom_item/bom_item.json +++ b/erpnext/manufacturing/doctype/bom_item/bom_item.json @@ -22,7 +22,7 @@ "hidden": 0, "ignore_user_permissions": 0, "ignore_xss_filter": 0, - "in_filter": 0, + "in_filter": 1, "in_global_search": 0, "in_list_view": 1, "in_standard_filter": 0, @@ -113,7 +113,7 @@ "hidden": 0, "ignore_user_permissions": 0, "ignore_xss_filter": 0, - "in_filter": 0, + "in_filter": 1, "in_global_search": 0, "in_list_view": 0, "in_standard_filter": 0, @@ -136,6 +136,37 @@ "unique": 0, "width": "150px" }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "source_warehouse", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Source Warehouse", + "length": 0, + "no_copy": 0, + "options": "Warehouse", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, { "allow_bulk_edit": 0, "allow_on_submit": 0, @@ -729,7 +760,7 @@ "issingle": 0, "istable": 1, "max_attachments": 0, - "modified": "2017-05-23 15:59:37.946963", + "modified": "2017-05-29 17:42:37.216408", "modified_by": "Administrator", "module": "Manufacturing", "name": "BOM Item", diff --git a/erpnext/manufacturing/doctype/production_order/production_order.js b/erpnext/manufacturing/doctype/production_order/production_order.js index 6465cececb..283ebcd93d 100644 --- a/erpnext/manufacturing/doctype/production_order/production_order.js +++ b/erpnext/manufacturing/doctype/production_order/production_order.js @@ -7,7 +7,63 @@ frappe.ui.form.on("Production Order", { 'Timesheet': 'Make Timesheet', 'Stock Entry': 'Make Stock Entry', } + + // Set query for warehouses + frm.set_query("wip_warehouse", function(doc) { + return { + filters: { + 'company': frm.doc.company, + } + } + }); + + frm.set_query("source_warehouse", "required_items", function() { + return { + filters: { + 'company': frm.doc.company, + } + } + }); + + frm.set_query("fg_warehouse", function() { + return { + filters: { + 'company': frm.doc.company, + 'is_group': 0 + } + } + }); + + // Set query for BOM + frm.set_query("bom_no", function() { + if (frm.doc.production_item) { + return{ + query: "erpnext.controllers.queries.bom", + filters: {item: cstr(frm.doc.production_item)} + } + } else msgprint(__("Please enter Production Item first")); + }); + + // Set query for FG Item + frm.set_query("production_item", function() { + return { + query: "erpnext.controllers.queries.item_query", + filters:{ + 'is_stock_item': 1, + } + } + }); + + // Set query for FG Item + frm.set_query("project", function() { + return{ + filters:[ + ['Project', 'status', 'not in', 'Completed, Cancelled'] + ] + } + }); }, + onload: function(frm) { if (!frm.doc.status) frm.doc.status = 'Draft'; @@ -28,9 +84,8 @@ frappe.ui.form.on("Production Order", { function(doc) { return (frm.doc.qty==doc.completed_qty) ? "green" : "orange" }) erpnext.production_order.set_custom_buttons(frm); - erpnext.production_order.setup_company_filter(frm); - erpnext.production_order.setup_bom_filter(frm); }, + refresh: function(frm) { erpnext.toggle_naming_series(); erpnext.production_order.set_custom_buttons(frm); @@ -53,6 +108,7 @@ frappe.ui.form.on("Production Order", { }) } }, + show_progress: function(frm) { var bars = []; var message = ''; @@ -85,10 +141,83 @@ frappe.ui.form.on("Production Order", { } } frm.dashboard.add_progress(__('Status'), bars, message); + }, + + production_item: function(frm) { + frappe.call({ + method: "erpnext.manufacturing.doctype.production_order.production_order.get_item_details", + args: { + item: frm.doc.production_item, + project: frm.doc.project + }, + callback: function(r) { + if(r.message) { + erpnext.in_production_item_onchange = true; + $.each(["description", "stock_uom", "project", "bom_no"], function(i, field) { + frm.set_value(field, r.message[field]); + }); + + if(r.message["set_scrap_wh_mandatory"]){ + frm.toggle_reqd("scrap_warehouse", true); + } + erpnext.in_production_item_onchange = false; + } + } + }); + }, + + project: function(frm) { + if(!erpnext.in_production_item_onchange) { + frm.trigger("production_item"); + } + }, + + bom_no: function(frm) { + return frm.call({ + doc: frm.doc, + method: "get_items_and_operations_from_bom", + callback: function(r) { + if(r.message["set_scrap_wh_mandatory"]){ + frm.toggle_reqd("scrap_warehouse", true); + } + } + }); + }, + + use_multi_level_bom: function(frm) { + if(frm.doc.bom_no) { + frm.trigger("bom_no"); + } + }, + + qty: function(frm) { + frm.trigger('bom_no'); + }, + + before_submit: function(frm) { + frm.toggle_reqd(["fg_warehouse", "wip_warehouse"], true); + frm.fields_dict.required_items.grid.toggle_reqd("source_warehouse", true); } }); - +frappe.ui.form.on("Production Order Item", { + source_warehouse: function(frm, cdt, cdn) { + var row = locals[cdt][cdn]; + if(row.source_warehouse) { + frappe.call({ + "method": "erpnext.stock.utils.get_latest_stock_qty", + args: { + item_code: row.item_code, + warehouse: row.source_warehouse + }, + callback: function (r) { + frappe.model.set_value(row.doctype, row.name, + "available_qty_at_source_warehouse", r.message); + } + }) + } + } +}) frappe.ui.form.on("Production Order Operation", { workstation: function(frm, cdt, cdn) { @@ -119,38 +248,45 @@ erpnext.production_order = { var doc = frm.doc; if (doc.docstatus === 1) { if (doc.status != 'Stopped' && doc.status != 'Completed') { - frm.add_custom_button(__('Stop'), cur_frm.cscript['Stop Production Order'], __("Status")); + frm.add_custom_button(__('Stop'), function() { + erpnext.production_order.stop_production_order(frm, "Stopped"); + }, __("Status")); } else if (doc.status == 'Stopped') { - frm.add_custom_button(__('Re-open'), cur_frm.cscript['Unstop Production Order'], __("Status")); + frm.add_custom_button(__('Re-open'), function() { + erpnext.production_order.stop_production_order(frm, "Resumed"); + }, __("Status")); } - + if(!frm.doc.skip_transfer){ - if ((flt(doc.material_transferred_for_manufacturing) < flt(doc.qty)) && frm.doc.status != 'Stopped') { + if ((flt(doc.material_transferred_for_manufacturing) < flt(doc.qty)) + && frm.doc.status != 'Stopped') { frm.has_start_btn = true; - var btn = frm.add_custom_button(__('Start'), - cur_frm.cscript['Transfer Raw Materials']); - btn.addClass('btn-primary'); - } + var start_btn = frm.add_custom_button(__('Start'), function() { + erpnext.production_order.make_se(frm, 'Material Transfer for Manufacture'); + }); + start_btn.addClass('btn-primary'); + } } if(!frm.doc.skip_transfer){ - if ((flt(doc.produced_qty) < flt(doc.material_transferred_for_manufacturing)) && frm.doc.status != 'Stopped') { + if ((flt(doc.produced_qty) < flt(doc.material_transferred_for_manufacturing)) + && frm.doc.status != 'Stopped') { frm.has_finish_btn = true; - var btn = frm.add_custom_button(__('Finish'), - cur_frm.cscript['Update Finished Goods']); + var finish_btn = frm.add_custom_button(__('Finish'), function() { + erpnext.production_order.make_se(frm, 'Manufacture'); + }); if(doc.material_transferred_for_manufacturing==doc.qty) { - // all materials transferred for manufacturing, - // make this primary - btn.addClass('btn-primary'); + // all materials transferred for manufacturing, make this primary + finish_btn.addClass('btn-primary'); } } } else { if ((flt(doc.produced_qty) < flt(doc.qty)) && frm.doc.status != 'Stopped') { frm.has_finish_btn = true; - var btn = frm.add_custom_button(__('Finish'), + var finish_btn = frm.add_custom_button(__('Finish'), cur_frm.cscript['Update Finished Goods']); - btn.addClass('btn-primary'); + finish_btn.addClass('btn-primary'); } } } @@ -162,8 +298,8 @@ erpnext.production_order = { doc.planned_operating_cost = 0.0; for(var i=0;i so_qty + (allowance_percentage/100 * so_qty): - frappe.throw(_("Cannot produce more Item {0} than Sales Order quantity {1}").format(self.production_item, - so_qty), OverProductionError) - - def stop_unstop(self, status): - """ Called from client side on Stop/Unstop event""" - status = self.update_status(status) - self.update_planned_qty() - frappe.msgprint(_("Production Order status is {0}").format(status)) - self.notify_update() - + frappe.throw(_("Cannot produce more Item {0} than Sales Order quantity {1}") + .format(self.production_item, so_qty), OverProductionError) def update_status(self, status=None): '''Update status of production order if unknown''' - if not status: + if status != "Stopped": status = self.get_status(status) if status != self.status: @@ -167,7 +166,6 @@ class ProductionOrder(Document): self.db_set(fieldname, qty) def before_submit(self): - self.set_required_items() self.make_time_logs() def on_submit(self): @@ -184,10 +182,10 @@ class ProductionOrder(Document): self.validate_cancel() frappe.db.set(self,'status', 'Cancelled') - self.clear_required_items() self.delete_timesheet() self.update_completed_qty_in_material_request() self.update_planned_qty() + self.update_reserved_qty_for_production() def validate_cancel(self): if self.status == "Stopped": @@ -214,12 +212,11 @@ class ProductionOrder(Document): def set_production_order_operations(self): """Fetch operations from BOM and set in 'Production Order'""" + self.set('operations', []) if not self.bom_no \ or cint(frappe.db.get_single_value("Manufacturing Settings", "disable_capacity_planning")): return - - self.set('operations', []) if self.use_multi_level_bom: bom_list = frappe.get_doc("BOM", self.bom_no).traverse_tree() @@ -240,8 +237,6 @@ class ProductionOrder(Document): self.set('operations', operations) self.calculate_time() - return check_if_scrap_warehouse_mandatory(self.bom_no) - def calculate_time(self): bom_qty = frappe.db.get_value("BOM", self.bom_no, "quantity") @@ -403,62 +398,60 @@ class ProductionOrder(Document): update bin reserved_qty_for_production called from Stock Entry for production, after submit, cancel ''' - if self.docstatus==1 and self.source_warehouse: - if self.material_transferred_for_manufacturing == self.produced_qty: - # clear required items table and save document - self.clear_required_items() - else: - # calculate transferred qty based on submitted - # stock entries - self.update_transaferred_qty_for_required_items() + if self.docstatus==1: + # calculate transferred qty based on submitted stock entries + self.update_transaferred_qty_for_required_items() - # update in bin - self.update_reserved_qty_for_production() - - def clear_required_items(self): - '''Remove the required_items table and update the bins''' - items = [d.item_code for d in self.required_items] - self.required_items = [] - - self.update_child_table('required_items') - - # completed, update reserved qty in bin - self.update_reserved_qty_for_production(items) + # update in bin + self.update_reserved_qty_for_production() def update_reserved_qty_for_production(self, items=None): '''update reserved_qty_for_production in bins''' - if not self.source_warehouse: - return - - if not items: - items = [d.item_code for d in self.required_items] - - for item in items: - stock_bin = get_bin(item, self.source_warehouse) - stock_bin.update_reserved_qty_for_production() + for d in self.required_items: + if d.source_warehouse: + stock_bin = get_bin(d.item_code, d.source_warehouse) + stock_bin.update_reserved_qty_for_production() + + def get_items_and_operations_from_bom(self): + self.set_required_items() + self.set_production_order_operations() + + return check_if_scrap_warehouse_mandatory(self.bom_no) + + def set_available_qty(self): + for d in self.get("required_items"): + if d.source_warehouse: + d.available_qty_at_source_warehouse = get_latest_stock_qty(d.item_code, d.source_warehouse) + + if self.wip_warehouse: + d.available_qty_at_wip_warehouse = get_latest_stock_qty(d.item_code, self.wip_warehouse) def set_required_items(self): '''set required_items for production to keep track of reserved qty''' - if self.source_warehouse: + self.required_items = [] + if self.bom_no and self.qty: item_dict = get_bom_items_as_dict(self.bom_no, self.company, qty=self.qty, fetch_exploded = self.use_multi_level_bom) for item in item_dict.values(): - self.append('required_items', {'item_code': item.item_code, - 'required_qty': item.qty}) - - #print frappe.as_json(self.required_items) + self.append('required_items', { + 'item_code': item.item_code, + 'required_qty': item.qty, + 'source_warehouse': item.source_warehouse or item.default_warehouse + }) + + self.set_available_qty() def update_transaferred_qty_for_required_items(self): '''update transferred qty from submitted stock entries for that item against the production order''' for d in self.required_items: - transferred_qty = frappe.db.sql('''select count(qty) + transferred_qty = frappe.db.sql('''select sum(qty) from `tabStock Entry` entry, `tabStock Entry Detail` detail where entry.production_order = %s - entry.purpose = "Material Transfer for Manufacture" + and entry.purpose = "Material Transfer for Manufacture" and entry.docstatus = 1 and detail.parent = entry.name and detail.item_code = %s''', (self.name, d.item_code))[0][0] @@ -496,10 +489,12 @@ def get_item_details(item, project = None): if not res["bom_no"]: if project: - frappe.throw(_("Default BOM for {0} not found for Project {1}").format(item, project)) - frappe.throw(_("Default BOM for {0} not found").format(item)) + res = get_item_details(item) + frappe.msgprint(_("Default BOM not found for Item {0} and Project {1}").format(item, project)) + else: + frappe.throw(_("Default BOM for {0} not found").format(item)) - res['project'] = frappe.db.get_value('BOM', res['bom_no'], 'project') + res['project'] = project or frappe.db.get_value('BOM', res['bom_no'], 'project') res.update(check_if_scrap_warehouse_mandatory(res["bom_no"])) return res @@ -507,16 +502,21 @@ def get_item_details(item, project = None): @frappe.whitelist() def check_if_scrap_warehouse_mandatory(bom_no): res = {"set_scrap_wh_mandatory": False } - bom = frappe.get_doc("BOM", bom_no) + if bom_no: + bom = frappe.get_doc("BOM", bom_no) - if len(bom.scrap_items) > 0: - res["set_scrap_wh_mandatory"] = True + if len(bom.scrap_items) > 0: + res["set_scrap_wh_mandatory"] = True return res @frappe.whitelist() def make_stock_entry(production_order_id, purpose, qty=None): production_order = frappe.get_doc("Production Order", production_order_id) + if not frappe.db.get_value("Warehouse", production_order.wip_warehouse, "is_group"): + wip_warehouse = production_order.wip_warehouse + else: + wip_warehouse = None stock_entry = frappe.new_doc("Stock Entry") stock_entry.purpose = purpose @@ -528,12 +528,10 @@ def make_stock_entry(production_order_id, purpose, qty=None): stock_entry.fg_completed_qty = qty or (flt(production_order.qty) - flt(production_order.produced_qty)) if purpose=="Material Transfer for Manufacture": - if production_order.source_warehouse: - stock_entry.from_warehouse = production_order.source_warehouse - stock_entry.to_warehouse = production_order.wip_warehouse + stock_entry.to_warehouse = wip_warehouse stock_entry.project = production_order.project else: - stock_entry.from_warehouse = production_order.wip_warehouse + stock_entry.from_warehouse = wip_warehouse stock_entry.to_warehouse = production_order.fg_warehouse additional_costs = get_additional_costs(production_order, fg_qty=stock_entry.fg_completed_qty) stock_entry.project = production_order.project @@ -601,3 +599,18 @@ def make_new_timesheet(source_name, target_doc=None): frappe.throw(_("Already completed")) return ts + +@frappe.whitelist() +def stop_unstop(production_order, status): + """ Called from client side on Stop/Unstop event""" + + if not frappe.has_permission("Production Order", "write"): + frappe.throw(_("Not permitted"), frappe.PermissionError) + + pro_order = frappe.get_doc("Production Order", production_order) + pro_order.update_status(status) + pro_order.update_planned_qty() + frappe.msgprint(_("Production Order has been {0}").format(status)) + pro_order.notify_update() + + return pro_order.status \ No newline at end of file diff --git a/erpnext/manufacturing/doctype/production_order/test_production_order.py b/erpnext/manufacturing/doctype/production_order/test_production_order.py index cdadba4825..18aa51d874 100644 --- a/erpnext/manufacturing/doctype/production_order/test_production_order.py +++ b/erpnext/manufacturing/doctype/production_order/test_production_order.py @@ -8,7 +8,7 @@ import frappe from frappe.utils import flt, time_diff_in_hours, now, add_days, cint from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import set_perpetual_inventory from erpnext.manufacturing.doctype.production_order.production_order \ - import make_stock_entry, ItemHasVariantError + import make_stock_entry, ItemHasVariantError, stop_unstop from erpnext.stock.doctype.stock_entry import test_stock_entry from erpnext.stock.doctype.item.test_item import get_total_projected_qty from erpnext.stock.utils import get_bin @@ -228,10 +228,46 @@ class TestProductionOrder(unittest.TestCase): cint(bin1_on_start_production.reserved_qty_for_production)) self.assertEqual(cint(bin1_on_end_production.projected_qty), cint(bin1_on_end_production.projected_qty)) + + def test_reserved_qty_for_stopped_production(self): + test_stock_entry.make_stock_entry(item_code="_Test Item", + target= self.warehouse, qty=100, basic_rate=100) + test_stock_entry.make_stock_entry(item_code="_Test Item Home Desktop 100", + target= self.warehouse, qty=100, basic_rate=100) - # required_items removed - self.pro_order.reload() - self.assertEqual(len(self.pro_order.required_items), 0) + # 0 0 0 + + self.test_reserved_qty_for_production_submit() + + #2 0 -2 + + s = frappe.get_doc(make_stock_entry(self.pro_order.name, + "Material Transfer for Manufacture", 1)) + + s.submit() + + #1 -1 0 + + bin1_on_start_production = get_bin(self.item, self.warehouse) + + # reserved_qty_for_producion updated + self.assertEqual(cint(self.bin1_at_start.reserved_qty_for_production) + 1, + cint(bin1_on_start_production.reserved_qty_for_production)) + + # projected qty will now be 2 less (becuase of item movement) + self.assertEqual(cint(self.bin1_at_start.projected_qty), + cint(bin1_on_start_production.projected_qty) + 2) + + # STOP + stop_unstop(self.pro_order.name, "Stopped") + + bin1_on_stop_production = get_bin(self.item, self.warehouse) + + # no change in reserved / projected + self.assertEqual(cint(bin1_on_stop_production.reserved_qty_for_production), + cint(self.bin1_at_start.reserved_qty_for_production)) + self.assertEqual(cint(bin1_on_stop_production.projected_qty) + 1, + cint(self.bin1_at_start.projected_qty)) def test_scrap_material_qty(self): prod_order = make_prod_order_test_record(planned_start_date=now(), qty=2) @@ -286,10 +322,11 @@ def make_prod_order_test_record(**args): pro_order.company = args.company or "_Test Company" pro_order.stock_uom = args.stock_uom or "_Test UOM" pro_order.use_multi_level_bom=0 - pro_order.set_production_order_operations() - + pro_order.get_items_and_operations_from_bom() + if args.source_warehouse: - pro_order.source_warehouse = args.source_warehouse + for item in pro_order.get("required_items"): + item.source_warehouse = args.source_warehouse if args.planned_start_date: pro_order.planned_start_date = args.planned_start_date diff --git a/erpnext/manufacturing/doctype/production_order_item/production_order_item.json b/erpnext/manufacturing/doctype/production_order_item/production_order_item.json index a0fe7ab125..06a7876d40 100644 --- a/erpnext/manufacturing/doctype/production_order_item/production_order_item.json +++ b/erpnext/manufacturing/doctype/production_order_item/production_order_item.json @@ -13,6 +13,7 @@ "engine": "InnoDB", "fields": [ { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -43,6 +44,157 @@ "unique": 0 }, { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "source_warehouse", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 1, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Source Warehouse", + "length": 0, + "no_copy": 0, + "options": "Warehouse", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "column_break_3", + "fieldtype": "Column Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "item_name", + "fieldtype": "Data", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Item Name", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 1, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "description", + "fieldtype": "Text", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Description", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 1, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "qty_section", + "fieldtype": "Section Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Qty", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -72,6 +224,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -99,6 +252,95 @@ "search_index": 0, "set_only_once": 0, "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "column_break_9", + "fieldtype": "Column Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "available_qty_at_source_warehouse", + "fieldtype": "Float", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Available Qty at Source Warehouse", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 1, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "available_qty_at_wip_warehouse", + "fieldtype": "Float", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Available Qty at WIP Warehouse", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 1, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 } ], "has_web_view": 0, @@ -111,7 +353,7 @@ "issingle": 0, "istable": 1, "max_attachments": 0, - "modified": "2017-03-28 14:18:36.342161", + "modified": "2017-05-15 17:37:20.212361", "modified_by": "Administrator", "module": "Manufacturing", "name": "Production Order Item", diff --git a/erpnext/manufacturing/doctype/production_order_operation/production_order_operation.json b/erpnext/manufacturing/doctype/production_order_operation/production_order_operation.json index 618235f002..89306c47b2 100644 --- a/erpnext/manufacturing/doctype/production_order_operation/production_order_operation.json +++ b/erpnext/manufacturing/doctype/production_order_operation/production_order_operation.json @@ -13,6 +13,7 @@ "engine": "InnoDB", "fields": [ { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -42,6 +43,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -74,6 +76,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -85,7 +88,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_global_search": 0, - "in_list_view": 1, + "in_list_view": 0, "in_standard_filter": 0, "label": "BOM", "length": 0, @@ -104,6 +107,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -135,6 +139,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -163,6 +168,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -193,6 +199,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -224,6 +231,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -256,6 +264,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -285,6 +294,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -314,6 +324,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -343,6 +354,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -371,6 +383,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -403,6 +416,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -434,6 +448,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -464,6 +479,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -493,6 +509,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -522,6 +539,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -552,6 +570,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -580,6 +599,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -610,6 +630,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -651,7 +672,7 @@ "issingle": 0, "istable": 1, "max_attachments": 0, - "modified": "2017-03-27 15:56:29.010336", + "modified": "2017-05-29 18:02:04.252419", "modified_by": "Administrator", "module": "Manufacturing", "name": "Production Order Operation", diff --git a/erpnext/patches.txt b/erpnext/patches.txt index e7cf5f2f1b..4f5c238031 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -412,3 +412,4 @@ erpnext.patches.v8_1.setup_gst_india #2017-06-27 execute:frappe.reload_doc('regional', 'doctype', 'gst_hsn_code') erpnext.patches.v8_1.removed_roles_from_gst_report_non_indian_account erpnext.patches.v8_1.gst_fixes +erpnext.patches.v8_0.update_production_orders diff --git a/erpnext/patches/v8_0/update_production_orders.py b/erpnext/patches/v8_0/update_production_orders.py new file mode 100644 index 0000000000..317aed5e68 --- /dev/null +++ b/erpnext/patches/v8_0/update_production_orders.py @@ -0,0 +1,44 @@ +# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors +# License: GNU General Public License v3. See license.txt + +from __future__ import unicode_literals +import frappe + +def execute(): + # reload schema + for doctype in ("Production Order", "Production Order Item", "Production Order Operation", + "BOM Item", "BOM Explosion Item", "BOM"): + frappe.reload_doctype(doctype) + + # fetch all draft and submitted production orders + fields = ["name"] + if "source_warehouse" in frappe.db.get_table_columns("Production Order"): + fields.append("source_warehouse") + + pro_orders = frappe.get_all("Production Order", filters={"docstatus": ["!=", 2]}, fields=fields) + + for p in pro_orders: + pro_order = frappe.get_doc("Production Order", p.name) + + # set required items table + pro_order.set_required_items() + + for item in pro_order.get("required_items"): + # set source warehouse based on parent + if not item.source_warehouse and "source_warehouse" in fields: + item.source_warehouse = pro_order.get("source_warehouse") + item.db_update() + + if pro_order.docstatus == 1: + # update transferred qty based on Stock Entry, it also updates db + pro_order.update_transaferred_qty_for_required_items() + + # Set status where it was 'Unstopped', as it is deprecated + if pro_order.status == "Unstopped": + status = pro_order.get_status() + pro_order.db_set("status", status) + elif pro_order.status == "Stopped": + pro_order.update_reserved_qty_for_production() + + + \ No newline at end of file diff --git a/erpnext/stock/doctype/bin/bin.py b/erpnext/stock/doctype/bin/bin.py index 75510decd3..9b49b69209 100644 --- a/erpnext/stock/doctype/bin/bin.py +++ b/erpnext/stock/doctype/bin/bin.py @@ -3,7 +3,6 @@ from __future__ import unicode_literals import frappe -from frappe import _ from frappe.utils import flt, nowdate import frappe.defaults from frappe.model.document import Document @@ -15,7 +14,6 @@ class Bin(Document): self.validate_mandatory() self.set_projected_qty() - self.block_transactions_against_group_warehouse() def on_update(self): update_item_projected_qty(self.item_code) @@ -26,10 +24,6 @@ class Bin(Document): if (not getattr(self, f, None)) or (not self.get(f)): self.set(f, 0.0) - def block_transactions_against_group_warehouse(self): - from erpnext.stock.utils import is_group_warehouse - is_group_warehouse(self.warehouse) - def update_stock(self, args, allow_negative_stock=False, via_landed_cost_voucher=False): '''Called from erpnext.stock.utils.update_bin''' self.update_qty(args) @@ -91,7 +85,8 @@ class Bin(Document): item.item_code = %s and item.parent = pro.name and pro.docstatus = 1 - and pro.source_warehouse = %s''', (self.item_code, self.warehouse))[0][0] + and item.source_warehouse = %s + and pro.status not in ("Stopped", "Completed")''', (self.item_code, self.warehouse))[0][0] self.set_projected_qty() diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index b74be3924a..7c7e630f01 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -708,13 +708,11 @@ class StockEntry(StockController): issue (item quantity) that is pending to issue or desire to transfer, whichever is less """ - item_dict = self.get_bom_raw_materials(1) - issued_item_qty = self.get_issued_qty() - + item_dict = self.get_pro_order_required_items() max_qty = flt(self.pro_doc.qty) - for item in item_dict: - pending_to_issue = (max_qty * item_dict[item]["qty"]) - issued_item_qty.get(item, 0) - desire_to_transfer = flt(self.fg_completed_qty) * item_dict[item]["qty"] + for item, item_details in item_dict.items(): + pending_to_issue = flt(item_details.required_qty) - flt(item_details.transferred_qty) + desire_to_transfer = flt(self.fg_completed_qty) * flt(item_details.required_qty) / max_qty if desire_to_transfer <= pending_to_issue: item_dict[item]["qty"] = desire_to_transfer @@ -734,34 +732,43 @@ class StockEntry(StockController): return item_dict - def get_issued_qty(self): - issued_item_qty = {} - result = frappe.db.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 for Manufacture' - group by t1.item_code""", self.production_order) - for t in result: - issued_item_qty[t[0]] = flt(t[1]) - - return issued_item_qty + def get_pro_order_required_items(self): + item_dict = frappe._dict() + pro_order = frappe.get_doc("Production Order", self.production_order) + if not frappe.db.get_value("Warehouse", pro_order.wip_warehouse, "is_group"): + wip_warehouse = pro_order.wip_warehouse + else: + wip_warehouse = None + + for d in pro_order.get("required_items"): + if flt(d.required_qty) > flt(d.transferred_qty): + item_row = d.as_dict() + if d.source_warehouse and not frappe.db.get_value("Warehouse", d.source_warehouse, "is_group"): + item_row["from_warehouse"] = d.source_warehouse + + item_row["to_warehouse"] = wip_warehouse + item_dict.setdefault(d.item_code, item_row) + + return item_dict 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") + se_child = self.append('items') se_child.s_warehouse = item_dict[d].get("from_warehouse") se_child.t_warehouse = item_dict[d].get("to_warehouse") se_child.item_code = cstr(d) se_child.item_name = item_dict[d]["item_name"] se_child.description = item_dict[d]["description"] - se_child.uom = item_dict[d]["stock_uom"] - se_child.stock_uom = item_dict[d]["stock_uom"] + se_child.uom = stock_uom + se_child.stock_uom = stock_uom se_child.qty = flt(item_dict[d]["qty"]) - se_child.expense_account = item_dict[d]["expense_account"] or expense_account - se_child.cost_center = item_dict[d]["cost_center"] or cost_center + se_child.expense_account = item_dict[d].get("expense_account") or expense_account + se_child.cost_center = item_dict[d].get("cost_center") or cost_center if se_child.s_warehouse==None: se_child.s_warehouse = self.from_warehouse diff --git a/erpnext/stock/stock_balance.py b/erpnext/stock/stock_balance.py index a9d0522c03..403d5cbc30 100644 --- a/erpnext/stock/stock_balance.py +++ b/erpnext/stock/stock_balance.py @@ -132,7 +132,7 @@ def get_ordered_qty(item_code, warehouse): def get_planned_qty(item_code, warehouse): planned_qty = frappe.db.sql(""" select sum(qty - produced_qty) from `tabProduction Order` - where production_item = %s and fg_warehouse = %s and status != "Stopped" + where production_item = %s and fg_warehouse = %s and status not in ("Stopped", "Completed") and docstatus=1 and qty > produced_qty""", (item_code, warehouse)) return flt(planned_qty[0][0]) if planned_qty else 0 diff --git a/erpnext/stock/utils.py b/erpnext/stock/utils.py index 2b9def35a8..01a18b90a4 100644 --- a/erpnext/stock/utils.py +++ b/erpnext/stock/utils.py @@ -41,7 +41,8 @@ def get_stock_value_on(warehouse=None, posting_date=None, item_code=None): sle_map = {} for sle in stock_ledger_entries: - sle_map[sle.item_code] = sle_map.get(sle.item_code, 0.0) + flt(sle.stock_value) + if not sle_map.has_key((sle.item_code, sle.warehouse)): + sle_map[(sle.item_code, sle.warehouse)] = flt(sle.stock_value) return sum(sle_map.values()) @@ -67,6 +68,28 @@ def get_stock_balance(item_code, warehouse, posting_date=None, posting_time=None else: return last_entry.qty_after_transaction if last_entry else 0.0 +@frappe.whitelist() +def get_latest_stock_qty(item_code, warehouse=None): + values, condition = [item_code], "" + if warehouse: + lft, rgt, is_group = frappe.db.get_value("Warehouse", warehouse, ["lft", "rgt", "is_group"]) + + if is_group: + values.extend([lft, rgt]) + condition += "and exists (\ + select name from `tabWarehouse` wh where wh.name = tabBin.warehouse\ + and wh.lft >= %s and wh.rgt <= %s)" + + else: + values.append(warehouse) + condition += " AND warehouse = %s" + + actual_qty = frappe.db.sql("""select sum(actual_qty) from tabBin + where item_code=%s {0}""".format(condition), values)[0][0] + + return actual_qty + + def get_latest_stock_balance(): bin_map = {} for d in frappe.db.sql("""SELECT item_code, warehouse, stock_value as stock_value