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
diff --git a/erpnext/manufacturing/doctype/bom/bom.js b/erpnext/manufacturing/doctype/bom/bom.js
index 87dd565..347314b 100644
--- a/erpnext/manufacturing/doctype/bom/bom.js
+++ b/erpnext/manufacturing/doctype/bom/bom.js
@@ -5,12 +5,24 @@
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 41eec8d..7aef4fc 100644
--- a/erpnext/manufacturing/doctype/bom/bom.py
+++ b/erpnext/manufacturing/doctype/bom/bom.py
@@ -408,6 +408,7 @@
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 @@
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 @@
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 @@
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 @@
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 0f1143e..6c24871 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 e1a3d4d..19fc37f 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": [
@@ -110,6 +110,37 @@
"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,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
"fieldname": "section_break_3",
"fieldtype": "Section Break",
"hidden": 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 966b89b..d259dd5 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,
@@ -142,6 +142,37 @@
"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,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
"fieldname": "section_break_5",
"fieldtype": "Section Break",
"hidden": 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 6465cec..283ebcd 100644
--- a/erpnext/manufacturing/doctype/production_order/production_order.js
+++ b/erpnext/manufacturing/doctype/production_order/production_order.js
@@ -7,7 +7,63 @@
'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 @@
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 @@
})
}
},
+
show_progress: function(frm) {
var bars = [];
var message = '';
@@ -85,10 +141,83 @@
}
}
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 @@
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 @@
doc.planned_operating_cost = 0.0;
for(var i=0;i<op.length;i++) {
var planned_operating_cost = flt(flt(op[i].hour_rate) * flt(op[i].time_in_mins) / 60, 2);
- frappe.model.set_value('Production Order Operation',op[i].name, "planned_operating_cost", planned_operating_cost);
-
+ frappe.model.set_value('Production Order Operation', op[i].name,
+ "planned_operating_cost", planned_operating_cost);
doc.planned_operating_cost += planned_operating_cost;
}
refresh_field('planned_operating_cost');
@@ -176,37 +312,10 @@
frm.set_value("total_operating_cost", (flt(frm.doc.additional_operating_cost) + variable_cost))
},
- setup_company_filter: function(frm) {
- var company_filter = function(doc) {
- return {
- filters: {
- 'company': frm.doc.company,
- 'is_group': 0
- }
- }
- }
-
- frm.fields_dict.source_warehouse.get_query = company_filter;
- frm.fields_dict.fg_warehouse.get_query = company_filter;
- frm.fields_dict.wip_warehouse.get_query = company_filter;
- },
-
- setup_bom_filter: function(frm) {
- frm.set_query("bom_no", function(doc) {
- if (doc.production_item) {
- return{
- query: "erpnext.controllers.queries.bom",
- filters: {item: cstr(doc.production_item)}
- }
- } else frappe.msgprint(__("Please enter Production Item first"));
- });
- },
-
set_default_warehouse: function(frm) {
if (!(frm.doc.wip_warehouse || frm.doc.fg_warehouse)) {
frappe.call({
method: "erpnext.manufacturing.doctype.production_order.production_order.get_default_warehouse",
-
callback: function(r) {
if(!r.exe) {
frm.set_value("wip_warehouse", r.message.wip_warehouse);
@@ -215,45 +324,15 @@
}
});
}
- }
-}
-
-$.extend(cur_frm.cscript, {
- before_submit: function() {
- cur_frm.toggle_reqd(["fg_warehouse", "wip_warehouse"], true);
},
-
- production_item: function(doc) {
- frappe.call({
- method: "erpnext.manufacturing.doctype.production_order.production_order.get_item_details",
- args: {
- item: doc.production_item,
- project: doc.project
- },
- callback: function(r) {
- $.each(["description", "stock_uom", "project", "bom_no"], function(i, field) {
- cur_frm.set_value(field, r.message[field]);
- });
-
- if(r.message["set_scrap_wh_mandatory"]){
- cur_frm.toggle_reqd("scrap_warehouse", true);
- }
- }
- });
- },
-
- project: function(doc) {
- cur_frm.cscript.production_item(doc)
- },
-
- make_se: function(purpose) {
- var me = this;
- if(!this.frm.doc.skip_transfer){
+
+ make_se: function(frm, purpose) {
+ if(!frm.doc.skip_transfer){
var max = (purpose === "Manufacture") ?
- flt(this.frm.doc.material_transferred_for_manufacturing) - flt(this.frm.doc.produced_qty) :
- flt(this.frm.doc.qty) - flt(this.frm.doc.material_transferred_for_manufacturing);
+ flt(frm.doc.material_transferred_for_manufacturing) - flt(frm.doc.produced_qty) :
+ flt(frm.doc.qty) - flt(frm.doc.material_transferred_for_manufacturing);
} else {
- var max = flt(this.frm.doc.qty) - flt(this.frm.doc.produced_qty);
+ var max = flt(frm.doc.qty) - flt(frm.doc.produced_qty);
}
frappe.prompt({fieldtype:"Float", label: __("Qty for {0}", [purpose]), fieldname:"qty",
@@ -266,7 +345,7 @@
frappe.call({
method:"erpnext.manufacturing.doctype.production_order.production_order.make_stock_entry",
args: {
- "production_order_id": me.frm.doc.name,
+ "production_order_id": frm.doc.name,
"purpose": purpose,
"qty": data.qty
},
@@ -277,59 +356,20 @@
});
}, __("Select Quantity"), __("Make"));
},
-
- bom_no: function() {
- return this.frm.call({
- doc: this.frm.doc,
- method: "set_production_order_operations",
+
+ stop_production_order: function(frm, status) {
+ frappe.call({
+ method: "erpnext.manufacturing.doctype.production_order.production_order.stop_unstop",
+ args: {
+ production_order: frm.doc.name,
+ status: status
+ },
callback: function(r) {
- if(r.message["set_scrap_wh_mandatory"]){
- cur_frm.toggle_reqd("scrap_warehouse", true);
+ if(r.message) {
+ frm.set_value("status", r.message);
+ frm.reload_doc();
}
}
- });
- },
-
- use_multi_level_bom: function() {
- if(this.frm.doc.bom_no) {
- this.frm.trigger("bom_no");
- }
- },
-
- qty: function() {
- frappe.ui.form.trigger("Production Order", 'bom_no')
- },
-});
-
-cur_frm.cscript['Stop Production Order'] = function() {
- $c_obj(cur_frm.doc, 'stop_unstop', 'Stopped', function(r, rt) {cur_frm.refresh();});
-}
-
-cur_frm.cscript['Unstop Production Order'] = function() {
- $c_obj(cur_frm.doc, 'stop_unstop', 'Unstopped', function(r, rt) {cur_frm.refresh();});
-}
-
-cur_frm.cscript['Transfer Raw Materials'] = function() {
- cur_frm.cscript.make_se('Material Transfer for Manufacture');
-}
-
-cur_frm.cscript['Update Finished Goods'] = function() {
- cur_frm.cscript.make_se('Manufacture');
-}
-
-cur_frm.fields_dict['production_item'].get_query = function(doc) {
- return {
- query: "erpnext.controllers.queries.item_query",
- filters:{
- 'is_stock_item': 1,
- }
- }
-}
-
-cur_frm.fields_dict['project'].get_query = function(doc, dt, dn) {
- return{
- filters:[
- ['Project', 'status', 'not in', 'Completed, Cancelled']
- ]
+ })
}
}
\ No newline at end of file
diff --git a/erpnext/manufacturing/doctype/production_order/production_order.json b/erpnext/manufacturing/doctype/production_order/production_order.json
index 94b6b95..ee033c4 100644
--- a/erpnext/manufacturing/doctype/production_order/production_order.json
+++ b/erpnext/manufacturing/doctype/production_order/production_order.json
@@ -95,7 +95,7 @@
"no_copy": 1,
"oldfieldname": "status",
"oldfieldtype": "Select",
- "options": "\nDraft\nSubmitted\nNot Started\nStopped\nUnstopped\nIn Process\nCompleted\nCancelled",
+ "options": "\nDraft\nSubmitted\nNot Started\nIn Process\nCompleted\nStopped\nCancelled",
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
@@ -145,38 +145,6 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
- "fieldname": "project",
- "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": "Project",
- "length": 0,
- "no_copy": 0,
- "oldfieldname": "project",
- "oldfieldtype": "Link",
- "options": "Project",
- "permlevel": 0,
- "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,
"depends_on": "",
"description": "",
"fieldname": "bom_no",
@@ -272,37 +240,6 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
- "description": "",
- "fieldname": "sales_order",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 1,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Sales Order",
- "length": 0,
- "no_copy": 0,
- "options": "Sales Order",
- "permlevel": 0,
- "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,
"depends_on": "",
"fieldname": "qty",
"fieldtype": "Float",
@@ -335,37 +272,6 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
- "description": "Check if material transfer entry is not required",
- "fieldname": "skip_transfer",
- "fieldtype": "Check",
- "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": "Skip Material Transfer",
- "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,
"default": "0",
"depends_on": "eval:doc.docstatus==1 && doc.skip_transfer==0",
"description": "",
@@ -433,6 +339,100 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
+ "description": "",
+ "fieldname": "sales_order",
+ "fieldtype": "Link",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_global_search": 1,
+ "in_list_view": 0,
+ "in_standard_filter": 0,
+ "label": "Sales Order",
+ "length": 0,
+ "no_copy": 0,
+ "options": "Sales Order",
+ "permlevel": 0,
+ "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": "project",
+ "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": "Project",
+ "length": 0,
+ "no_copy": 0,
+ "oldfieldname": "project",
+ "oldfieldtype": "Link",
+ "options": "Project",
+ "permlevel": 0,
+ "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,
+ "description": "Check if material transfer entry is not required",
+ "fieldname": "skip_transfer",
+ "fieldtype": "Check",
+ "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": "Skip Material Transfer",
+ "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": "warehouses",
"fieldtype": "Section Break",
"hidden": 0,
@@ -463,38 +463,6 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
- "description": "",
- "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 (for reserving Items)",
- "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": "wip_warehouse",
"fieldtype": "Link",
"hidden": 0,
@@ -525,34 +493,6 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
- "fieldname": "column_break_12",
- "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,
- "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,
"depends_on": "",
"description": "",
"fieldname": "fg_warehouse",
@@ -585,6 +525,34 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
+ "fieldname": "column_break_12",
+ "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,
+ "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": "scrap_warehouse",
"fieldtype": "Link",
"hidden": 0,
@@ -616,7 +584,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
- "fieldname": "time",
+ "fieldname": "required_items_section",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
@@ -625,10 +593,9 @@
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
- "label": "Time",
+ "label": "Required Items",
"length": 0,
"no_copy": 0,
- "options": "fa fa-time",
"permlevel": 0,
"precision": "",
"print_hide": 0,
@@ -647,9 +614,8 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
- "depends_on": "",
- "fieldname": "expected_delivery_date",
- "fieldtype": "Date",
+ "fieldname": "required_items",
+ "fieldtype": "Table",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
@@ -657,10 +623,43 @@
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
- "label": "Expected Delivery Date",
+ "label": "Required Items",
+ "length": 0,
+ "no_copy": 1,
+ "options": "Production Order Item",
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 1,
+ "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": "time",
+ "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": "Time",
"length": 0,
"no_copy": 0,
+ "options": "fa fa-time",
"permlevel": 0,
+ "precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
@@ -708,7 +707,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
- "fieldname": "planned_end_date",
+ "fieldname": "actual_start_date",
"fieldtype": "Datetime",
"hidden": 0,
"ignore_user_permissions": 0,
@@ -717,9 +716,9 @@
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
- "label": "Planned End Date",
+ "label": "Actual Start Date",
"length": 0,
- "no_copy": 1,
+ "no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
@@ -767,7 +766,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
- "fieldname": "actual_start_date",
+ "fieldname": "planned_end_date",
"fieldtype": "Datetime",
"hidden": 0,
"ignore_user_permissions": 0,
@@ -776,9 +775,9 @@
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
- "label": "Actual Start Date",
+ "label": "Planned End Date",
"length": 0,
- "no_copy": 0,
+ "no_copy": 1,
"permlevel": 0,
"precision": "",
"print_hide": 0,
@@ -828,6 +827,36 @@
"collapsible": 0,
"columns": 0,
"depends_on": "",
+ "fieldname": "expected_delivery_date",
+ "fieldtype": "Date",
+ "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": "Expected Delivery Date",
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "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,
+ "depends_on": "",
"fieldname": "operations_section",
"fieldtype": "Section Break",
"hidden": 0,
@@ -1076,67 +1105,6 @@
"bold": 0,
"collapsible": 1,
"columns": 0,
- "fieldname": "required_items_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": "Required Items",
- "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": "required_items",
- "fieldtype": "Table",
- "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": "Required Items",
- "length": 0,
- "no_copy": 1,
- "options": "Production Order Item",
- "permlevel": 0,
- "precision": "",
- "print_hide": 1,
- "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": 1,
- "columns": 0,
"fieldname": "more_info",
"fieldtype": "Section Break",
"hidden": 0,
diff --git a/erpnext/manufacturing/doctype/production_order/production_order.py b/erpnext/manufacturing/doctype/production_order/production_order.py
index 68da336..040a559 100644
--- a/erpnext/manufacturing/doctype/production_order/production_order.py
+++ b/erpnext/manufacturing/doctype/production_order/production_order.py
@@ -3,24 +3,21 @@
from __future__ import unicode_literals
import frappe
-
import json
-from frappe.utils import flt, get_datetime, getdate, date_diff, cint, nowdate
from frappe import _
-from frappe.utils import time_diff_in_seconds
+from frappe.utils import flt, get_datetime, getdate, date_diff, cint, nowdate
from frappe.model.document import Document
-from frappe.model.mapper import get_mapped_doc
-from erpnext.manufacturing.doctype.bom.bom import validate_bom_no
+from erpnext.manufacturing.doctype.bom.bom import validate_bom_no, get_bom_items_as_dict
from dateutil.relativedelta import relativedelta
from erpnext.stock.doctype.item.item import validate_end_of_life
-from erpnext.manufacturing.doctype.workstation.workstation import WorkstationHolidayError, NotInWorkingHoursError
+from erpnext.manufacturing.doctype.workstation.workstation import WorkstationHolidayError
from erpnext.projects.doctype.timesheet.timesheet import OverlapError
from erpnext.stock.doctype.stock_entry.stock_entry import get_additional_costs
from erpnext.manufacturing.doctype.manufacturing_settings.manufacturing_settings import get_mins_between_operations
from erpnext.stock.stock_balance import get_planned_qty, update_bin_qty
-from erpnext.manufacturing.doctype.bom.bom import get_bom_items_as_dict
-from erpnext.stock.utils import get_bin
from frappe.utils.csvutils import getlink
+from erpnext.stock.utils import get_bin, validate_warehouse_company, get_latest_stock_qty
+from erpnext.utilities.transaction_base import validate_uom_is_integer
class OverProductionError(frappe.ValidationError): pass
class StockOverProductionError(frappe.ValidationError): pass
@@ -38,15 +35,19 @@
validate_bom_no(self.production_item, self.bom_no)
self.validate_sales_order()
- self.validate_warehouse()
+ self.validate_warehouse_belongs_to_company()
self.calculate_operating_cost()
self.validate_qty()
self.validate_operation_time()
self.status = self.get_status()
- from erpnext.utilities.transaction_base import validate_uom_is_integer
validate_uom_is_integer(self, "stock_uom", ["qty", "produced_qty"])
+ if not self.get("required_items"):
+ self.set_required_items()
+ else:
+ self.set_available_qty()
+
def validate_sales_order(self):
if self.sales_order:
so = frappe.db.sql("""select name, delivery_date, project from `tabSales Order`
@@ -64,11 +65,14 @@
else:
frappe.throw(_("Sales Order {0} is not valid").format(self.sales_order))
- def validate_warehouse(self):
- from erpnext.stock.utils import validate_warehouse_company
+ def validate_warehouse_belongs_to_company(self):
+ warehouses = [self.fg_warehouse, self.wip_warehouse]
+ for d in self.get("required_items"):
+ if d.source_warehouse not in warehouses:
+ warehouses.append(d.source_warehouse)
- for w in [self.source_warehouse, self.fg_warehouse, self.wip_warehouse]:
- validate_warehouse_company(w, self.company)
+ for wh in warehouses:
+ validate_warehouse_company(wh, self.company)
def calculate_operating_cost(self):
self.planned_operating_cost, self.actual_operating_cost = 0.0, 0.0
@@ -79,7 +83,8 @@
self.planned_operating_cost += flt(d.planned_operating_cost)
self.actual_operating_cost += flt(d.actual_operating_cost)
- variable_cost = self.actual_operating_cost if self.actual_operating_cost else self.planned_operating_cost
+ variable_cost = self.actual_operating_cost if self.actual_operating_cost \
+ else self.planned_operating_cost
self.total_operating_cost = flt(self.additional_operating_cost) + flt(variable_cost)
def validate_production_order_against_so(self):
@@ -101,22 +106,16 @@
# total qty in SO
so_qty = flt(so_item_qty) + flt(dnpi_qty)
- allowance_percentage = flt(frappe.db.get_single_value("Manufacturing Settings", "over_production_allowance_percentage"))
+ allowance_percentage = flt(frappe.db.get_single_value("Manufacturing Settings",
+ "over_production_allowance_percentage"))
+
if total_qty > 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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
@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 @@
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 @@
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 cdadba4..18aa51d 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 @@
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 @@
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 @@
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 a0fe7ab..06a7876 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 618235f..89306c4 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 e7cf5f2..4f5c238 100644
--- a/erpnext/patches.txt
+++ b/erpnext/patches.txt
@@ -412,3 +412,4 @@
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 0000000..317aed5
--- /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 75510de..9b49b69 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 @@
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 @@
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 @@
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 b74be39..7c7e630 100644
--- a/erpnext/stock/doctype/stock_entry/stock_entry.py
+++ b/erpnext/stock/doctype/stock_entry/stock_entry.py
@@ -708,13 +708,11 @@
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 @@
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 a9d0522..403d5cb 100644
--- a/erpnext/stock/stock_balance.py
+++ b/erpnext/stock/stock_balance.py
@@ -132,7 +132,7 @@
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 2b9def3..01a18b9 100644
--- a/erpnext/stock/utils.py
+++ b/erpnext/stock/utils.py
@@ -41,7 +41,8 @@
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 @@
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