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
This commit is contained in:
Nabin Hait 2017-07-05 13:55:41 +05:30 committed by Makarand Bauskar
parent 852cb64e4f
commit 949a920022
17 changed files with 986 additions and 508 deletions

View File

@ -5,12 +5,24 @@ frappe.provide("erpnext.bom");
frappe.ui.form.on("BOM", { frappe.ui.form.on("BOM", {
setup: function(frm) { setup: function(frm) {
frm.add_fetch('buying_price_list', 'currency', 'currency'); frm.add_fetch('buying_price_list', 'currency', 'currency')
frm.fields_dict["items"].grid.get_field("bom_no").get_query = function(doc, cdt, cdn){
frm.set_query("bom_no", "items", function() {
return { 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) { onload_post_render: function(frm) {

View File

@ -408,6 +408,7 @@ class BOM(WebsiteGenerator):
self.add_to_cur_exploded_items(frappe._dict({ self.add_to_cur_exploded_items(frappe._dict({
'item_code' : d.item_code, 'item_code' : d.item_code,
'item_name' : d.item_name, 'item_name' : d.item_name,
'source_warehouse': d.source_warehouse,
'description' : d.description, 'description' : d.description,
'image' : d.image, 'image' : d.image,
'stock_uom' : d.stock_uom, 'stock_uom' : d.stock_uom,
@ -427,7 +428,8 @@ class BOM(WebsiteGenerator):
def get_child_exploded_items(self, bom_no, stock_qty): def get_child_exploded_items(self, bom_no, stock_qty):
""" Add all items from Flat BOM of child BOM""" """ 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 # 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_uom, bom_item.stock_qty, bom_item.rate,
bom_item.stock_qty / ifnull(bom.quantity, 1) as qty_consumed_per_unit bom_item.stock_qty / ifnull(bom.quantity, 1) as qty_consumed_per_unit
from `tabBOM Explosion Item` bom_item, tabBOM bom from `tabBOM Explosion Item` bom_item, tabBOM bom
@ -437,9 +439,10 @@ class BOM(WebsiteGenerator):
self.add_to_cur_exploded_items(frappe._dict({ self.add_to_cur_exploded_items(frappe._dict({
'item_code' : d['item_code'], 'item_code' : d['item_code'],
'item_name' : d['item_name'], 'item_name' : d['item_name'],
'source_warehouse' : d['source_warehouse'],
'description' : d['description'], 'description' : d['description'],
'stock_uom' : d['stock_uom'], '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']), '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.default_warehouse,
item.expense_account as expense_account, item.expense_account as expense_account,
item.buying_cost_center as cost_center item.buying_cost_center as cost_center
{select_columns}
from from
`tab{table}` bom_item, `tabBOM` bom, `tabItem` item `tab{table}` bom_item, `tabBOM` bom, `tabItem` item
where 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 bom_item.parent = bom.name
and item.name = bom_item.item_code and item.name = bom_item.item_code
and is_stock_item = 1 and is_stock_item = 1
{conditions} {where_conditions}
group by item_code, stock_uom""" group by item_code, stock_uom"""
if fetch_exploded: if fetch_exploded:
query = query.format(table="BOM Explosion Item", 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) items = frappe.db.sql(query, { "qty": qty, "bom": bom }, as_dict=True)
elif fetch_scrap_items: 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) items = frappe.db.sql(query, { "qty": qty, "bom": bom }, as_dict=True)
else: 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) items = frappe.db.sql(query, { "qty": qty, "bom": bom }, as_dict=True)
for item in items: for item in items:

View File

@ -8,7 +8,8 @@
"parentfield": "items", "parentfield": "items",
"stock_qty": 1.0, "stock_qty": 1.0,
"rate": 5000.0, "rate": 5000.0,
"stock_uom": "_Test UOM" "stock_uom": "_Test UOM",
"source_warehouse": "_Test Warehouse - _TC"
}, },
{ {
"amount": 2000.0, "amount": 2000.0,
@ -17,7 +18,8 @@
"parentfield": "items", "parentfield": "items",
"stock_qty": 2.0, "stock_qty": 2.0,
"rate": 1000.0, "rate": 1000.0,
"stock_uom": "_Test UOM" "stock_uom": "_Test UOM",
"source_warehouse": "_Test Warehouse - _TC"
} }
], ],
"docstatus": 1, "docstatus": 1,
@ -48,7 +50,8 @@
"parentfield": "items", "parentfield": "items",
"stock_qty": 1.0, "stock_qty": 1.0,
"rate": 5000.0, "rate": 5000.0,
"stock_uom": "_Test UOM" "stock_uom": "_Test UOM",
"source_warehouse": "_Test Warehouse - _TC"
}, },
{ {
"amount": 2000.0, "amount": 2000.0,
@ -57,7 +60,8 @@
"parentfield": "items", "parentfield": "items",
"stock_qty": 2.0, "stock_qty": 2.0,
"rate": 1000.0, "rate": 1000.0,
"stock_uom": "_Test UOM" "stock_uom": "_Test UOM",
"source_warehouse": "_Test Warehouse - _TC"
} }
], ],
"docstatus": 1, "docstatus": 1,
@ -86,7 +90,8 @@
"parentfield": "items", "parentfield": "items",
"stock_qty": 1.0, "stock_qty": 1.0,
"rate": 5000.0, "rate": 5000.0,
"stock_uom": "_Test UOM" "stock_uom": "_Test UOM",
"source_warehouse": "_Test Warehouse - _TC"
}, },
{ {
"amount": 2000.0, "amount": 2000.0,
@ -96,7 +101,8 @@
"parentfield": "items", "parentfield": "items",
"stock_qty": 3.0, "stock_qty": 3.0,
"rate": 1000.0, "rate": 1000.0,
"stock_uom": "_Test UOM" "stock_uom": "_Test UOM",
"source_warehouse": "_Test Warehouse - _TC"
} }
], ],
"docstatus": 1, "docstatus": 1,
@ -126,7 +132,8 @@
"parentfield": "items", "parentfield": "items",
"stock_qty": 2.0, "stock_qty": 2.0,
"rate": 3000.0, "rate": 3000.0,
"stock_uom": "_Test UOM" "stock_uom": "_Test UOM",
"source_warehouse": "_Test Warehouse - _TC"
} }
], ],
"docstatus": 1, "docstatus": 1,

View File

@ -7,9 +7,9 @@
"beta": 0, "beta": 0,
"creation": "2013-03-07 11:42:57", "creation": "2013-03-07 11:42:57",
"custom": 0, "custom": 0,
"default_print_format": "Standard",
"docstatus": 0, "docstatus": 0,
"doctype": "DocType", "doctype": "DocType",
"document_type": "Setup",
"editable_grid": 1, "editable_grid": 1,
"engine": "InnoDB", "engine": "InnoDB",
"fields": [ "fields": [
@ -104,6 +104,37 @@
"set_only_once": 0, "set_only_once": 0,
"unique": 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_bulk_edit": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
@ -481,7 +512,7 @@
"issingle": 0, "issingle": 0,
"istable": 1, "istable": 1,
"max_attachments": 0, "max_attachments": 0,
"modified": "2017-06-02 19:29:34.498719", "modified": "2017-05-29 17:51:18.151002",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Manufacturing", "module": "Manufacturing",
"name": "BOM Explosion Item", "name": "BOM Explosion Item",

View File

@ -22,7 +22,7 @@
"hidden": 0, "hidden": 0,
"ignore_user_permissions": 0, "ignore_user_permissions": 0,
"ignore_xss_filter": 0, "ignore_xss_filter": 0,
"in_filter": 0, "in_filter": 1,
"in_global_search": 0, "in_global_search": 0,
"in_list_view": 1, "in_list_view": 1,
"in_standard_filter": 0, "in_standard_filter": 0,
@ -113,7 +113,7 @@
"hidden": 0, "hidden": 0,
"ignore_user_permissions": 0, "ignore_user_permissions": 0,
"ignore_xss_filter": 0, "ignore_xss_filter": 0,
"in_filter": 0, "in_filter": 1,
"in_global_search": 0, "in_global_search": 0,
"in_list_view": 0, "in_list_view": 0,
"in_standard_filter": 0, "in_standard_filter": 0,
@ -136,6 +136,37 @@
"unique": 0, "unique": 0,
"width": "150px" "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_bulk_edit": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
@ -729,7 +760,7 @@
"issingle": 0, "issingle": 0,
"istable": 1, "istable": 1,
"max_attachments": 0, "max_attachments": 0,
"modified": "2017-05-23 15:59:37.946963", "modified": "2017-05-29 17:42:37.216408",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Manufacturing", "module": "Manufacturing",
"name": "BOM Item", "name": "BOM Item",

View File

@ -7,7 +7,63 @@ frappe.ui.form.on("Production Order", {
'Timesheet': 'Make Timesheet', 'Timesheet': 'Make Timesheet',
'Stock Entry': 'Make Stock Entry', '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) { onload: function(frm) {
if (!frm.doc.status) if (!frm.doc.status)
frm.doc.status = 'Draft'; 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" }) function(doc) { return (frm.doc.qty==doc.completed_qty) ? "green" : "orange" })
erpnext.production_order.set_custom_buttons(frm); erpnext.production_order.set_custom_buttons(frm);
erpnext.production_order.setup_company_filter(frm);
erpnext.production_order.setup_bom_filter(frm);
}, },
refresh: function(frm) { refresh: function(frm) {
erpnext.toggle_naming_series(); erpnext.toggle_naming_series();
erpnext.production_order.set_custom_buttons(frm); erpnext.production_order.set_custom_buttons(frm);
@ -53,6 +108,7 @@ frappe.ui.form.on("Production Order", {
}) })
} }
}, },
show_progress: function(frm) { show_progress: function(frm) {
var bars = []; var bars = [];
var message = ''; var message = '';
@ -85,10 +141,83 @@ frappe.ui.form.on("Production Order", {
} }
} }
frm.dashboard.add_progress(__('Status'), bars, message); 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", { frappe.ui.form.on("Production Order Operation", {
workstation: function(frm, cdt, cdn) { workstation: function(frm, cdt, cdn) {
@ -119,38 +248,45 @@ erpnext.production_order = {
var doc = frm.doc; var doc = frm.doc;
if (doc.docstatus === 1) { if (doc.docstatus === 1) {
if (doc.status != 'Stopped' && doc.status != 'Completed') { 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') { } 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(!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; frm.has_start_btn = true;
var btn = frm.add_custom_button(__('Start'), var start_btn = frm.add_custom_button(__('Start'), function() {
cur_frm.cscript['Transfer Raw Materials']); erpnext.production_order.make_se(frm, 'Material Transfer for Manufacture');
btn.addClass('btn-primary'); });
} start_btn.addClass('btn-primary');
}
} }
if(!frm.doc.skip_transfer){ 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; frm.has_finish_btn = true;
var btn = frm.add_custom_button(__('Finish'), var finish_btn = frm.add_custom_button(__('Finish'), function() {
cur_frm.cscript['Update Finished Goods']); erpnext.production_order.make_se(frm, 'Manufacture');
});
if(doc.material_transferred_for_manufacturing==doc.qty) { if(doc.material_transferred_for_manufacturing==doc.qty) {
// all materials transferred for manufacturing, // all materials transferred for manufacturing, make this primary
// make this primary finish_btn.addClass('btn-primary');
btn.addClass('btn-primary');
} }
} }
} else { } else {
if ((flt(doc.produced_qty) < flt(doc.qty)) && frm.doc.status != 'Stopped') { if ((flt(doc.produced_qty) < flt(doc.qty)) && frm.doc.status != 'Stopped') {
frm.has_finish_btn = true; 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']); 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; doc.planned_operating_cost = 0.0;
for(var i=0;i<op.length;i++) { 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); 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; doc.planned_operating_cost += planned_operating_cost;
} }
refresh_field('planned_operating_cost'); refresh_field('planned_operating_cost');
@ -176,37 +312,10 @@ erpnext.production_order = {
frm.set_value("total_operating_cost", (flt(frm.doc.additional_operating_cost) + variable_cost)) 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) { set_default_warehouse: function(frm) {
if (!(frm.doc.wip_warehouse || frm.doc.fg_warehouse)) { if (!(frm.doc.wip_warehouse || frm.doc.fg_warehouse)) {
frappe.call({ frappe.call({
method: "erpnext.manufacturing.doctype.production_order.production_order.get_default_warehouse", method: "erpnext.manufacturing.doctype.production_order.production_order.get_default_warehouse",
callback: function(r) { callback: function(r) {
if(!r.exe) { if(!r.exe) {
frm.set_value("wip_warehouse", r.message.wip_warehouse); frm.set_value("wip_warehouse", r.message.wip_warehouse);
@ -215,45 +324,15 @@ erpnext.production_order = {
} }
}); });
} }
}
}
$.extend(cur_frm.cscript, {
before_submit: function() {
cur_frm.toggle_reqd(["fg_warehouse", "wip_warehouse"], true);
}, },
production_item: function(doc) { make_se: function(frm, purpose) {
frappe.call({ if(!frm.doc.skip_transfer){
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){
var max = (purpose === "Manufacture") ? var max = (purpose === "Manufacture") ?
flt(this.frm.doc.material_transferred_for_manufacturing) - flt(this.frm.doc.produced_qty) : flt(frm.doc.material_transferred_for_manufacturing) - flt(frm.doc.produced_qty) :
flt(this.frm.doc.qty) - flt(this.frm.doc.material_transferred_for_manufacturing); flt(frm.doc.qty) - flt(frm.doc.material_transferred_for_manufacturing);
} else { } 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", frappe.prompt({fieldtype:"Float", label: __("Qty for {0}", [purpose]), fieldname:"qty",
@ -266,7 +345,7 @@ $.extend(cur_frm.cscript, {
frappe.call({ frappe.call({
method:"erpnext.manufacturing.doctype.production_order.production_order.make_stock_entry", method:"erpnext.manufacturing.doctype.production_order.production_order.make_stock_entry",
args: { args: {
"production_order_id": me.frm.doc.name, "production_order_id": frm.doc.name,
"purpose": purpose, "purpose": purpose,
"qty": data.qty "qty": data.qty
}, },
@ -278,58 +357,19 @@ $.extend(cur_frm.cscript, {
}, __("Select Quantity"), __("Make")); }, __("Select Quantity"), __("Make"));
}, },
bom_no: function() { stop_production_order: function(frm, status) {
return this.frm.call({ frappe.call({
doc: this.frm.doc, method: "erpnext.manufacturing.doctype.production_order.production_order.stop_unstop",
method: "set_production_order_operations", args: {
production_order: frm.doc.name,
status: status
},
callback: function(r) { callback: function(r) {
if(r.message["set_scrap_wh_mandatory"]){ if(r.message) {
cur_frm.toggle_reqd("scrap_warehouse", true); 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']
]
} }
} }

View File

@ -95,7 +95,7 @@
"no_copy": 1, "no_copy": 1,
"oldfieldname": "status", "oldfieldname": "status",
"oldfieldtype": "Select", "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, "permlevel": 0,
"print_hide": 0, "print_hide": 0,
"print_hide_if_no_value": 0, "print_hide_if_no_value": 0,
@ -139,38 +139,6 @@
"set_only_once": 0, "set_only_once": 0,
"unique": 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_bulk_edit": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
@ -266,37 +234,6 @@
"unique": 0, "unique": 0,
"width": "50%" "width": "50%"
}, },
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"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_bulk_edit": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
@ -329,37 +266,6 @@
"set_only_once": 0, "set_only_once": 0,
"unique": 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_bulk_edit": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
@ -427,6 +333,100 @@
"set_only_once": 0, "set_only_once": 0,
"unique": 0 "unique": 0
}, },
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"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_bulk_edit": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
@ -457,38 +457,6 @@
"set_only_once": 0, "set_only_once": 0,
"unique": 0 "unique": 0
}, },
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"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_bulk_edit": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
@ -519,34 +487,6 @@
"set_only_once": 0, "set_only_once": 0,
"unique": 0 "unique": 0
}, },
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"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_bulk_edit": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
@ -579,6 +519,34 @@
"set_only_once": 0, "set_only_once": 0,
"unique": 0 "unique": 0
}, },
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"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_bulk_edit": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
@ -616,7 +584,7 @@
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
"columns": 0, "columns": 0,
"fieldname": "time", "fieldname": "required_items_section",
"fieldtype": "Section Break", "fieldtype": "Section Break",
"hidden": 0, "hidden": 0,
"ignore_user_permissions": 0, "ignore_user_permissions": 0,
@ -625,10 +593,9 @@
"in_global_search": 0, "in_global_search": 0,
"in_list_view": 0, "in_list_view": 0,
"in_standard_filter": 0, "in_standard_filter": 0,
"label": "Time", "label": "Required Items",
"length": 0, "length": 0,
"no_copy": 0, "no_copy": 0,
"options": "fa fa-time",
"permlevel": 0, "permlevel": 0,
"precision": "", "precision": "",
"print_hide": 0, "print_hide": 0,
@ -647,9 +614,8 @@
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
"columns": 0, "columns": 0,
"depends_on": "", "fieldname": "required_items",
"fieldname": "expected_delivery_date", "fieldtype": "Table",
"fieldtype": "Date",
"hidden": 0, "hidden": 0,
"ignore_user_permissions": 0, "ignore_user_permissions": 0,
"ignore_xss_filter": 0, "ignore_xss_filter": 0,
@ -657,10 +623,43 @@
"in_global_search": 0, "in_global_search": 0,
"in_list_view": 0, "in_list_view": 0,
"in_standard_filter": 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, "length": 0,
"no_copy": 0, "no_copy": 0,
"options": "fa fa-time",
"permlevel": 0, "permlevel": 0,
"precision": "",
"print_hide": 0, "print_hide": 0,
"print_hide_if_no_value": 0, "print_hide_if_no_value": 0,
"read_only": 0, "read_only": 0,
@ -708,7 +707,7 @@
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
"columns": 0, "columns": 0,
"fieldname": "planned_end_date", "fieldname": "actual_start_date",
"fieldtype": "Datetime", "fieldtype": "Datetime",
"hidden": 0, "hidden": 0,
"ignore_user_permissions": 0, "ignore_user_permissions": 0,
@ -717,9 +716,9 @@
"in_global_search": 0, "in_global_search": 0,
"in_list_view": 0, "in_list_view": 0,
"in_standard_filter": 0, "in_standard_filter": 0,
"label": "Planned End Date", "label": "Actual Start Date",
"length": 0, "length": 0,
"no_copy": 1, "no_copy": 0,
"permlevel": 0, "permlevel": 0,
"precision": "", "precision": "",
"print_hide": 0, "print_hide": 0,
@ -767,7 +766,7 @@
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
"columns": 0, "columns": 0,
"fieldname": "actual_start_date", "fieldname": "planned_end_date",
"fieldtype": "Datetime", "fieldtype": "Datetime",
"hidden": 0, "hidden": 0,
"ignore_user_permissions": 0, "ignore_user_permissions": 0,
@ -776,9 +775,9 @@
"in_global_search": 0, "in_global_search": 0,
"in_list_view": 0, "in_list_view": 0,
"in_standard_filter": 0, "in_standard_filter": 0,
"label": "Actual Start Date", "label": "Planned End Date",
"length": 0, "length": 0,
"no_copy": 0, "no_copy": 1,
"permlevel": 0, "permlevel": 0,
"precision": "", "precision": "",
"print_hide": 0, "print_hide": 0,
@ -821,6 +820,36 @@
"set_only_once": 0, "set_only_once": 0,
"unique": 0 "unique": 0
}, },
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"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_bulk_edit": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
@ -1070,67 +1099,6 @@
"set_only_once": 0, "set_only_once": 0,
"unique": 0 "unique": 0
}, },
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"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_bulk_edit": 0,
"allow_on_submit": 0, "allow_on_submit": 0,

View File

@ -3,24 +3,21 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import frappe import frappe
import json import json
from frappe.utils import flt, get_datetime, getdate, date_diff, cint, nowdate
from frappe import _ 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.document import Document
from frappe.model.mapper import get_mapped_doc from erpnext.manufacturing.doctype.bom.bom import validate_bom_no, get_bom_items_as_dict
from erpnext.manufacturing.doctype.bom.bom import validate_bom_no
from dateutil.relativedelta import relativedelta from dateutil.relativedelta import relativedelta
from erpnext.stock.doctype.item.item import validate_end_of_life 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.projects.doctype.timesheet.timesheet import OverlapError
from erpnext.stock.doctype.stock_entry.stock_entry import get_additional_costs 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.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.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 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 OverProductionError(frappe.ValidationError): pass
class StockOverProductionError(frappe.ValidationError): pass class StockOverProductionError(frappe.ValidationError): pass
@ -38,15 +35,19 @@ class ProductionOrder(Document):
validate_bom_no(self.production_item, self.bom_no) validate_bom_no(self.production_item, self.bom_no)
self.validate_sales_order() self.validate_sales_order()
self.validate_warehouse() self.validate_warehouse_belongs_to_company()
self.calculate_operating_cost() self.calculate_operating_cost()
self.validate_qty() self.validate_qty()
self.validate_operation_time() self.validate_operation_time()
self.status = self.get_status() 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"]) 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): def validate_sales_order(self):
if self.sales_order: if self.sales_order:
so = frappe.db.sql("""select name, delivery_date, project from `tabSales Order` so = frappe.db.sql("""select name, delivery_date, project from `tabSales Order`
@ -64,11 +65,14 @@ class ProductionOrder(Document):
else: else:
frappe.throw(_("Sales Order {0} is not valid").format(self.sales_order)) frappe.throw(_("Sales Order {0} is not valid").format(self.sales_order))
def validate_warehouse(self): def validate_warehouse_belongs_to_company(self):
from erpnext.stock.utils import validate_warehouse_company 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]: for wh in warehouses:
validate_warehouse_company(w, self.company) validate_warehouse_company(wh, self.company)
def calculate_operating_cost(self): def calculate_operating_cost(self):
self.planned_operating_cost, self.actual_operating_cost = 0.0, 0.0 self.planned_operating_cost, self.actual_operating_cost = 0.0, 0.0
@ -79,7 +83,8 @@ class ProductionOrder(Document):
self.planned_operating_cost += flt(d.planned_operating_cost) self.planned_operating_cost += flt(d.planned_operating_cost)
self.actual_operating_cost += flt(d.actual_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) self.total_operating_cost = flt(self.additional_operating_cost) + flt(variable_cost)
def validate_production_order_against_so(self): def validate_production_order_against_so(self):
@ -101,22 +106,16 @@ class ProductionOrder(Document):
# total qty in SO # total qty in SO
so_qty = flt(so_item_qty) + flt(dnpi_qty) 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): 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, frappe.throw(_("Cannot produce more Item {0} than Sales Order quantity {1}")
so_qty), OverProductionError) .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()
def update_status(self, status=None): def update_status(self, status=None):
'''Update status of production order if unknown''' '''Update status of production order if unknown'''
if not status: if status != "Stopped":
status = self.get_status(status) status = self.get_status(status)
if status != self.status: if status != self.status:
@ -167,7 +166,6 @@ class ProductionOrder(Document):
self.db_set(fieldname, qty) self.db_set(fieldname, qty)
def before_submit(self): def before_submit(self):
self.set_required_items()
self.make_time_logs() self.make_time_logs()
def on_submit(self): def on_submit(self):
@ -184,10 +182,10 @@ class ProductionOrder(Document):
self.validate_cancel() self.validate_cancel()
frappe.db.set(self,'status', 'Cancelled') frappe.db.set(self,'status', 'Cancelled')
self.clear_required_items()
self.delete_timesheet() self.delete_timesheet()
self.update_completed_qty_in_material_request() self.update_completed_qty_in_material_request()
self.update_planned_qty() self.update_planned_qty()
self.update_reserved_qty_for_production()
def validate_cancel(self): def validate_cancel(self):
if self.status == "Stopped": if self.status == "Stopped":
@ -214,13 +212,12 @@ class ProductionOrder(Document):
def set_production_order_operations(self): def set_production_order_operations(self):
"""Fetch operations from BOM and set in 'Production Order'""" """Fetch operations from BOM and set in 'Production Order'"""
self.set('operations', [])
if not self.bom_no \ if not self.bom_no \
or cint(frappe.db.get_single_value("Manufacturing Settings", "disable_capacity_planning")): or cint(frappe.db.get_single_value("Manufacturing Settings", "disable_capacity_planning")):
return return
self.set('operations', [])
if self.use_multi_level_bom: if self.use_multi_level_bom:
bom_list = frappe.get_doc("BOM", self.bom_no).traverse_tree() bom_list = frappe.get_doc("BOM", self.bom_no).traverse_tree()
else: else:
@ -240,8 +237,6 @@ class ProductionOrder(Document):
self.set('operations', operations) self.set('operations', operations)
self.calculate_time() self.calculate_time()
return check_if_scrap_warehouse_mandatory(self.bom_no)
def calculate_time(self): def calculate_time(self):
bom_qty = frappe.db.get_value("BOM", self.bom_no, "quantity") bom_qty = frappe.db.get_value("BOM", self.bom_no, "quantity")
@ -403,62 +398,60 @@ class ProductionOrder(Document):
update bin reserved_qty_for_production update bin reserved_qty_for_production
called from Stock Entry for production, after submit, cancel called from Stock Entry for production, after submit, cancel
''' '''
if self.docstatus==1 and self.source_warehouse: if self.docstatus==1:
if self.material_transferred_for_manufacturing == self.produced_qty: # calculate transferred qty based on submitted stock entries
# clear required items table and save document self.update_transaferred_qty_for_required_items()
self.clear_required_items()
else:
# calculate transferred qty based on submitted
# stock entries
self.update_transaferred_qty_for_required_items()
# update in bin # update in bin
self.update_reserved_qty_for_production() 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)
def update_reserved_qty_for_production(self, items=None): def update_reserved_qty_for_production(self, items=None):
'''update reserved_qty_for_production in bins''' '''update reserved_qty_for_production in bins'''
if not self.source_warehouse: for d in self.required_items:
return if d.source_warehouse:
stock_bin = get_bin(d.item_code, d.source_warehouse)
stock_bin.update_reserved_qty_for_production()
if not items: def get_items_and_operations_from_bom(self):
items = [d.item_code for d in self.required_items] self.set_required_items()
self.set_production_order_operations()
for item in items: return check_if_scrap_warehouse_mandatory(self.bom_no)
stock_bin = get_bin(item, self.source_warehouse)
stock_bin.update_reserved_qty_for_production() 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): def set_required_items(self):
'''set required_items for production to keep track of reserved qty''' '''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, item_dict = get_bom_items_as_dict(self.bom_no, self.company, qty=self.qty,
fetch_exploded = self.use_multi_level_bom) fetch_exploded = self.use_multi_level_bom)
for item in item_dict.values(): for item in item_dict.values():
self.append('required_items', {'item_code': item.item_code, self.append('required_items', {
'required_qty': item.qty}) 'item_code': item.item_code,
'required_qty': item.qty,
'source_warehouse': item.source_warehouse or item.default_warehouse
})
#print frappe.as_json(self.required_items) self.set_available_qty()
def update_transaferred_qty_for_required_items(self): def update_transaferred_qty_for_required_items(self):
'''update transferred qty from submitted stock entries for that item against '''update transferred qty from submitted stock entries for that item against
the production order''' the production order'''
for d in self.required_items: 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 from `tabStock Entry` entry, `tabStock Entry Detail` detail
where where
entry.production_order = %s entry.production_order = %s
entry.purpose = "Material Transfer for Manufacture" and entry.purpose = "Material Transfer for Manufacture"
and entry.docstatus = 1 and entry.docstatus = 1
and detail.parent = entry.name and detail.parent = entry.name
and detail.item_code = %s''', (self.name, d.item_code))[0][0] 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 not res["bom_no"]:
if project: if project:
frappe.throw(_("Default BOM for {0} not found for Project {1}").format(item, project)) res = get_item_details(item)
frappe.throw(_("Default BOM for {0} not found").format(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"])) res.update(check_if_scrap_warehouse_mandatory(res["bom_no"]))
return res return res
@ -507,16 +502,21 @@ def get_item_details(item, project = None):
@frappe.whitelist() @frappe.whitelist()
def check_if_scrap_warehouse_mandatory(bom_no): def check_if_scrap_warehouse_mandatory(bom_no):
res = {"set_scrap_wh_mandatory": False } 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: if len(bom.scrap_items) > 0:
res["set_scrap_wh_mandatory"] = True res["set_scrap_wh_mandatory"] = True
return res return res
@frappe.whitelist() @frappe.whitelist()
def make_stock_entry(production_order_id, purpose, qty=None): def make_stock_entry(production_order_id, purpose, qty=None):
production_order = frappe.get_doc("Production Order", production_order_id) 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 = frappe.new_doc("Stock Entry")
stock_entry.purpose = purpose 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)) stock_entry.fg_completed_qty = qty or (flt(production_order.qty) - flt(production_order.produced_qty))
if purpose=="Material Transfer for Manufacture": if purpose=="Material Transfer for Manufacture":
if production_order.source_warehouse: stock_entry.to_warehouse = wip_warehouse
stock_entry.from_warehouse = production_order.source_warehouse
stock_entry.to_warehouse = production_order.wip_warehouse
stock_entry.project = production_order.project stock_entry.project = production_order.project
else: else:
stock_entry.from_warehouse = production_order.wip_warehouse stock_entry.from_warehouse = wip_warehouse
stock_entry.to_warehouse = production_order.fg_warehouse stock_entry.to_warehouse = production_order.fg_warehouse
additional_costs = get_additional_costs(production_order, fg_qty=stock_entry.fg_completed_qty) additional_costs = get_additional_costs(production_order, fg_qty=stock_entry.fg_completed_qty)
stock_entry.project = production_order.project stock_entry.project = production_order.project
@ -601,3 +599,18 @@ def make_new_timesheet(source_name, target_doc=None):
frappe.throw(_("Already completed")) frappe.throw(_("Already completed"))
return ts 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

View File

@ -8,7 +8,7 @@ import frappe
from frappe.utils import flt, time_diff_in_hours, now, add_days, cint 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.stock.doctype.purchase_receipt.test_purchase_receipt import set_perpetual_inventory
from erpnext.manufacturing.doctype.production_order.production_order \ 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.stock_entry import test_stock_entry
from erpnext.stock.doctype.item.test_item import get_total_projected_qty from erpnext.stock.doctype.item.test_item import get_total_projected_qty
from erpnext.stock.utils import get_bin from erpnext.stock.utils import get_bin
@ -229,9 +229,45 @@ class TestProductionOrder(unittest.TestCase):
self.assertEqual(cint(bin1_on_end_production.projected_qty), self.assertEqual(cint(bin1_on_end_production.projected_qty),
cint(bin1_on_end_production.projected_qty)) cint(bin1_on_end_production.projected_qty))
# required_items removed def test_reserved_qty_for_stopped_production(self):
self.pro_order.reload() test_stock_entry.make_stock_entry(item_code="_Test Item",
self.assertEqual(len(self.pro_order.required_items), 0) 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)
# 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): def test_scrap_material_qty(self):
prod_order = make_prod_order_test_record(planned_start_date=now(), qty=2) 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.company = args.company or "_Test Company"
pro_order.stock_uom = args.stock_uom or "_Test UOM" pro_order.stock_uom = args.stock_uom or "_Test UOM"
pro_order.use_multi_level_bom=0 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: 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: if args.planned_start_date:
pro_order.planned_start_date = args.planned_start_date pro_order.planned_start_date = args.planned_start_date

View File

@ -13,6 +13,7 @@
"engine": "InnoDB", "engine": "InnoDB",
"fields": [ "fields": [
{ {
"allow_bulk_edit": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
@ -43,6 +44,157 @@
"unique": 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": 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, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
@ -72,6 +224,7 @@
"unique": 0 "unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
@ -99,6 +252,95 @@
"search_index": 0, "search_index": 0,
"set_only_once": 0, "set_only_once": 0,
"unique": 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, "has_web_view": 0,
@ -111,7 +353,7 @@
"issingle": 0, "issingle": 0,
"istable": 1, "istable": 1,
"max_attachments": 0, "max_attachments": 0,
"modified": "2017-03-28 14:18:36.342161", "modified": "2017-05-15 17:37:20.212361",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Manufacturing", "module": "Manufacturing",
"name": "Production Order Item", "name": "Production Order Item",

View File

@ -13,6 +13,7 @@
"engine": "InnoDB", "engine": "InnoDB",
"fields": [ "fields": [
{ {
"allow_bulk_edit": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
@ -42,6 +43,7 @@
"unique": 0 "unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
@ -74,6 +76,7 @@
"unique": 0 "unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
@ -85,7 +88,7 @@
"ignore_xss_filter": 0, "ignore_xss_filter": 0,
"in_filter": 0, "in_filter": 0,
"in_global_search": 0, "in_global_search": 0,
"in_list_view": 1, "in_list_view": 0,
"in_standard_filter": 0, "in_standard_filter": 0,
"label": "BOM", "label": "BOM",
"length": 0, "length": 0,
@ -104,6 +107,7 @@
"unique": 0 "unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
@ -135,6 +139,7 @@
"unique": 0 "unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
@ -163,6 +168,7 @@
"unique": 0 "unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
@ -193,6 +199,7 @@
"unique": 0 "unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
@ -224,6 +231,7 @@
"unique": 0 "unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
@ -256,6 +264,7 @@
"unique": 0 "unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
@ -285,6 +294,7 @@
"unique": 0 "unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
@ -314,6 +324,7 @@
"unique": 0 "unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
@ -343,6 +354,7 @@
"unique": 0 "unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
@ -371,6 +383,7 @@
"unique": 0 "unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
@ -403,6 +416,7 @@
"unique": 0 "unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
@ -434,6 +448,7 @@
"unique": 0 "unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
@ -464,6 +479,7 @@
"unique": 0 "unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
@ -493,6 +509,7 @@
"unique": 0 "unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
@ -522,6 +539,7 @@
"unique": 0 "unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
@ -552,6 +570,7 @@
"unique": 0 "unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
@ -580,6 +599,7 @@
"unique": 0 "unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
@ -610,6 +630,7 @@
"unique": 0 "unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
@ -651,7 +672,7 @@
"issingle": 0, "issingle": 0,
"istable": 1, "istable": 1,
"max_attachments": 0, "max_attachments": 0,
"modified": "2017-03-27 15:56:29.010336", "modified": "2017-05-29 18:02:04.252419",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Manufacturing", "module": "Manufacturing",
"name": "Production Order Operation", "name": "Production Order Operation",

View File

@ -412,3 +412,4 @@ erpnext.patches.v8_1.setup_gst_india #2017-06-27
execute:frappe.reload_doc('regional', 'doctype', 'gst_hsn_code') 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.removed_roles_from_gst_report_non_indian_account
erpnext.patches.v8_1.gst_fixes erpnext.patches.v8_1.gst_fixes
erpnext.patches.v8_0.update_production_orders

View File

@ -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()

View File

@ -3,7 +3,6 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import frappe import frappe
from frappe import _
from frappe.utils import flt, nowdate from frappe.utils import flt, nowdate
import frappe.defaults import frappe.defaults
from frappe.model.document import Document from frappe.model.document import Document
@ -15,7 +14,6 @@ class Bin(Document):
self.validate_mandatory() self.validate_mandatory()
self.set_projected_qty() self.set_projected_qty()
self.block_transactions_against_group_warehouse()
def on_update(self): def on_update(self):
update_item_projected_qty(self.item_code) 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)): if (not getattr(self, f, None)) or (not self.get(f)):
self.set(f, 0.0) 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): def update_stock(self, args, allow_negative_stock=False, via_landed_cost_voucher=False):
'''Called from erpnext.stock.utils.update_bin''' '''Called from erpnext.stock.utils.update_bin'''
self.update_qty(args) self.update_qty(args)
@ -91,7 +85,8 @@ class Bin(Document):
item.item_code = %s item.item_code = %s
and item.parent = pro.name and item.parent = pro.name
and pro.docstatus = 1 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() self.set_projected_qty()

View File

@ -708,13 +708,11 @@ class StockEntry(StockController):
issue (item quantity) that is pending to issue or desire to transfer, issue (item quantity) that is pending to issue or desire to transfer,
whichever is less whichever is less
""" """
item_dict = self.get_bom_raw_materials(1) item_dict = self.get_pro_order_required_items()
issued_item_qty = self.get_issued_qty()
max_qty = flt(self.pro_doc.qty) max_qty = flt(self.pro_doc.qty)
for item in item_dict: for item, item_details in item_dict.items():
pending_to_issue = (max_qty * item_dict[item]["qty"]) - issued_item_qty.get(item, 0) pending_to_issue = flt(item_details.required_qty) - flt(item_details.transferred_qty)
desire_to_transfer = flt(self.fg_completed_qty) * item_dict[item]["qty"] desire_to_transfer = flt(self.fg_completed_qty) * flt(item_details.required_qty) / max_qty
if desire_to_transfer <= pending_to_issue: if desire_to_transfer <= pending_to_issue:
item_dict[item]["qty"] = desire_to_transfer item_dict[item]["qty"] = desire_to_transfer
@ -734,34 +732,43 @@ class StockEntry(StockController):
return item_dict return item_dict
def get_issued_qty(self): def get_pro_order_required_items(self):
issued_item_qty = {} item_dict = frappe._dict()
result = frappe.db.sql("""select t1.item_code, sum(t1.qty) pro_order = frappe.get_doc("Production Order", self.production_order)
from `tabStock Entry Detail` t1, `tabStock Entry` t2 if not frappe.db.get_value("Warehouse", pro_order.wip_warehouse, "is_group"):
where t1.parent = t2.name and t2.production_order = %s and t2.docstatus = 1 wip_warehouse = pro_order.wip_warehouse
and t2.purpose = 'Material Transfer for Manufacture' else:
group by t1.item_code""", self.production_order) wip_warehouse = None
for t in result:
issued_item_qty[t[0]] = flt(t[1])
return issued_item_qty 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): def add_to_stock_entry_detail(self, item_dict, bom_no=None):
expense_account, cost_center = frappe.db.get_values("Company", self.company, \ expense_account, cost_center = frappe.db.get_values("Company", self.company, \
["default_expense_account", "cost_center"])[0] ["default_expense_account", "cost_center"])[0]
for d in item_dict: 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 = self.append('items')
se_child.s_warehouse = item_dict[d].get("from_warehouse") se_child.s_warehouse = item_dict[d].get("from_warehouse")
se_child.t_warehouse = item_dict[d].get("to_warehouse") se_child.t_warehouse = item_dict[d].get("to_warehouse")
se_child.item_code = cstr(d) se_child.item_code = cstr(d)
se_child.item_name = item_dict[d]["item_name"] se_child.item_name = item_dict[d]["item_name"]
se_child.description = item_dict[d]["description"] se_child.description = item_dict[d]["description"]
se_child.uom = item_dict[d]["stock_uom"] se_child.uom = stock_uom
se_child.stock_uom = item_dict[d]["stock_uom"] se_child.stock_uom = stock_uom
se_child.qty = flt(item_dict[d]["qty"]) se_child.qty = flt(item_dict[d]["qty"])
se_child.expense_account = item_dict[d]["expense_account"] or expense_account se_child.expense_account = item_dict[d].get("expense_account") or expense_account
se_child.cost_center = item_dict[d]["cost_center"] or cost_center se_child.cost_center = item_dict[d].get("cost_center") or cost_center
if se_child.s_warehouse==None: if se_child.s_warehouse==None:
se_child.s_warehouse = self.from_warehouse se_child.s_warehouse = self.from_warehouse

View File

@ -132,7 +132,7 @@ def get_ordered_qty(item_code, warehouse):
def get_planned_qty(item_code, warehouse): def get_planned_qty(item_code, warehouse):
planned_qty = frappe.db.sql(""" planned_qty = frappe.db.sql("""
select sum(qty - produced_qty) from `tabProduction Order` 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)) and docstatus=1 and qty > produced_qty""", (item_code, warehouse))
return flt(planned_qty[0][0]) if planned_qty else 0 return flt(planned_qty[0][0]) if planned_qty else 0

View File

@ -41,7 +41,8 @@ def get_stock_value_on(warehouse=None, posting_date=None, item_code=None):
sle_map = {} sle_map = {}
for sle in stock_ledger_entries: 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()) return sum(sle_map.values())
@ -67,6 +68,28 @@ def get_stock_balance(item_code, warehouse, posting_date=None, posting_time=None
else: else:
return last_entry.qty_after_transaction if last_entry else 0.0 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(): def get_latest_stock_balance():
bin_map = {} bin_map = {}
for d in frappe.db.sql("""SELECT item_code, warehouse, stock_value as stock_value for d in frappe.db.sql("""SELECT item_code, warehouse, stock_value as stock_value