[Enhancement] Work Order Material Consumption (#13384)
* Work Order Material Consumption * Test cases and other minor fixes * Test cases fixes * Travis Fixes * Work Order Material Consumption Request Changes * Update work_order.js
This commit is contained in:
parent
e9638be032
commit
ad76f9ad70
Binary file not shown.
After Width: | Height: | Size: 112 KiB |
Binary file not shown.
After Width: | Height: | Size: 296 KiB |
BIN
erpnext/docs/assets/img/manufacturing/consumed-qty.png
Normal file
BIN
erpnext/docs/assets/img/manufacturing/consumed-qty.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 145 KiB |
Binary file not shown.
After Width: | Height: | Size: 127 KiB |
Binary file not shown.
After Width: | Height: | Size: 209 KiB |
Binary file not shown.
After Width: | Height: | Size: 134 KiB |
@ -0,0 +1,34 @@
|
||||
#Material consumption
|
||||
|
||||
Material Consumption functionality allows you to have multiple consumption `Stock Entry` against a Work Order. To enable this, go to Manufacturing > Manufacturing Settings.
|
||||
|
||||
<img class="screenshot" alt="Item Alternative" src="{{docs_base_url}}/assets/img/manufacturing/allow-material-consumption.png">
|
||||
|
||||
Once enabled, a `Material Consumption` button will be available in Work Order once started.
|
||||
|
||||
<img class="screenshot" alt="Item Alternative" src="{{docs_base_url}}/assets/img/manufacturing/material-consumption-button.png">
|
||||
|
||||
When button is clicked, it will do the following:
|
||||
|
||||
1. It will create Stock Entry with purpose `Material Consumption for Manufacture`.
|
||||
|
||||
<img class="screenshot" alt="Item Alternative" src="{{docs_base_url}}/assets/img/manufacturing/material-consumption-for-manufacture.png">
|
||||
|
||||
2. If the "Backflush Raw Materials Based On" in the Manufacturing Settings is set to `BOM`, if will propose to consume all required qty for manufacture.
|
||||
3. If the "Backflush Raw Materials Based On" in the Manufacturing Settings is set to `Material Transferred for Manufacture`, if will propose to consume all transferred qty for manufacture.
|
||||
4. Once submitted, it will update `Consumed Qty` column in the Work Order.
|
||||
|
||||
<img class="screenshot" alt="Item Alternative" src="{{docs_base_url}}/assets/img/manufacturing/consumed-qty.png">
|
||||
|
||||
5. In succeeding Material Consumption, it will suggest unconsumed qty.
|
||||
6. Once "Finish" button is clicked in Work Order, it will take into account consumed qty.
|
||||
|
||||
### Validations
|
||||
|
||||
* If "Allow Multiple Material Consumption" is not set in Manufacturing Settings but "Material Consumption for Manufacture" is use in Stock Entry.
|
||||
|
||||
<img class="screenshot" alt="Item Alternative" src="{{docs_base_url}}/assets/img/manufacturing/material-consumption-stock-entry.gif">
|
||||
|
||||
* Cannot cancel "Material Consumption for Manufacture" for completed Work Order.
|
||||
|
||||
<img class="screenshot" alt="Item Alternative" src="{{docs_base_url}}/assets/img/manufacturing/cancel-material-consumption-stock-entry.gif">
|
@ -0,0 +1,5 @@
|
||||
// Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.ui.form.on('Manufacturing Settings', {
|
||||
});
|
@ -40,6 +40,7 @@
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
@ -71,6 +72,7 @@
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
@ -102,6 +104,7 @@
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
@ -134,6 +137,7 @@
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
@ -163,6 +167,7 @@
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
@ -195,6 +200,7 @@
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
@ -226,6 +232,7 @@
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
@ -255,6 +262,7 @@
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
@ -285,6 +293,7 @@
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
@ -317,6 +326,39 @@
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"description": "Allow multiple Material Consumption against a Work Order",
|
||||
"fieldname": "material_consumption",
|
||||
"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": "Allow Multiple Material Consumption",
|
||||
"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,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
@ -348,6 +390,7 @@
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
@ -377,6 +420,7 @@
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
@ -408,6 +452,7 @@
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
@ -439,6 +484,7 @@
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
}
|
||||
],
|
||||
@ -454,7 +500,7 @@
|
||||
"istable": 0,
|
||||
"max_attachments": 0,
|
||||
"menu_index": 0,
|
||||
"modified": "2018-02-16 13:18:17.964103",
|
||||
"modified": "2018-03-28 13:56:31.187520",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Manufacturing",
|
||||
"name": "Manufacturing Settings",
|
||||
|
@ -0,0 +1,9 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# See license.txt
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import unittest
|
||||
|
||||
class TestManufacturingSettings(unittest.TestCase):
|
||||
pass
|
@ -60,6 +60,7 @@ class TestWorkOrder(unittest.TestCase):
|
||||
|
||||
def test_over_production(self):
|
||||
from erpnext.manufacturing.doctype.work_order.work_order import StockOverProductionError
|
||||
|
||||
wo_doc = self.check_planned_qty()
|
||||
|
||||
test_stock_entry.make_stock_entry(item_code="_Test Item",
|
||||
@ -300,6 +301,7 @@ def make_wo_order_test_record(**args):
|
||||
wo_order.company = args.company or "_Test Company"
|
||||
wo_order.stock_uom = args.stock_uom or "_Test UOM"
|
||||
wo_order.use_multi_level_bom=0
|
||||
wo_order.skip_transfer=1
|
||||
wo_order.get_items_and_operations_from_bom()
|
||||
|
||||
if args.source_warehouse:
|
||||
|
@ -310,21 +310,42 @@ erpnext.work_order = {
|
||||
}
|
||||
|
||||
if(!frm.doc.skip_transfer){
|
||||
if ((flt(doc.produced_qty) < flt(doc.material_transferred_for_manufacturing))
|
||||
&& frm.doc.status != 'Stopped') {
|
||||
frm.has_finish_btn = true;
|
||||
var finish_btn = frm.add_custom_button(__('Finish'), function() {
|
||||
erpnext.work_order.make_se(frm, 'Manufacture');
|
||||
});
|
||||
// If "Material Consumption is check in Manufacturing Settings, allow Material Consumption
|
||||
frappe.model.get_value('Manufacturing Settings', {'name': 'Manufacturing Settings'}, 'material_consumption', function(d) {
|
||||
if ((flt(doc.produced_qty) < flt(doc.material_transferred_for_manufacturing))
|
||||
&& frm.doc.status != 'Stopped') {
|
||||
frm.has_finish_btn = true;
|
||||
|
||||
if(doc.material_transferred_for_manufacturing==doc.qty) {
|
||||
// all materials transferred for manufacturing, make this primary
|
||||
finish_btn.addClass('btn-primary');
|
||||
if (d.material_consumption == 1) {
|
||||
// Only show "Material Consumption" when required_qty > consumed_qty
|
||||
var counter = 0;
|
||||
var tbl = frm.doc.required_items || [];
|
||||
var tbl_lenght = tbl.length;
|
||||
for (var i = 0, len = tbl_lenght; i < len; i++) {
|
||||
if (flt(frm.doc.required_items[i].required_qty) > flt(frm.doc.required_items[i].consumed_qty)) {
|
||||
counter += 1;
|
||||
}
|
||||
}
|
||||
if (counter > 0) {
|
||||
var consumption_btn = frm.add_custom_button(__('Material Consumption'), function() {
|
||||
erpnext.work_order.make_consumption_se(frm, d.backflush_raw_materials_based_on);
|
||||
});
|
||||
consumption_btn.addClass('btn-primary');
|
||||
}
|
||||
}
|
||||
|
||||
var finish_btn = frm.add_custom_button(__('Finish'), function() {
|
||||
erpnext.work_order.make_se(frm, 'Manufacture');
|
||||
});
|
||||
|
||||
if(doc.material_transferred_for_manufacturing==doc.qty) {
|
||||
// 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 finish_btn = frm.add_custom_button(__('Finish'), function() {
|
||||
erpnext.work_order.make_se(frm, 'Manufacture');
|
||||
});
|
||||
@ -400,6 +421,30 @@ erpnext.work_order = {
|
||||
}, __("Select Quantity"), __("Make"));
|
||||
},
|
||||
|
||||
make_consumption_se: function(frm, backflush_raw_materials_based_on) {
|
||||
if(!frm.doc.skip_transfer){
|
||||
var max = (backflush_raw_materials_based_on === "Material Transferred for Manufacture") ?
|
||||
flt(frm.doc.material_transferred_for_manufacturing) - flt(frm.doc.produced_qty) :
|
||||
flt(frm.doc.qty) - flt(frm.doc.produced_qty);
|
||||
// flt(frm.doc.qty) - flt(frm.doc.material_transferred_for_manufacturing);
|
||||
} else {
|
||||
var max = flt(frm.doc.qty) - flt(frm.doc.produced_qty);
|
||||
}
|
||||
|
||||
frappe.call({
|
||||
method:"erpnext.manufacturing.doctype.work_order.work_order.make_stock_entry",
|
||||
args: {
|
||||
"work_order_id": frm.doc.name,
|
||||
"purpose": "Material Consumption for Manufacture",
|
||||
"qty": max
|
||||
},
|
||||
callback: function(r) {
|
||||
var doclist = frappe.model.sync(r.message);
|
||||
frappe.set_route("Form", doclist[0].doctype, doclist[0].name);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
stop_work_order: function(frm, status) {
|
||||
frappe.call({
|
||||
method: "erpnext.manufacturing.doctype.work_order.work_order.stop_unstop",
|
||||
|
@ -184,7 +184,7 @@ class WorkOrder(Document):
|
||||
and purpose=%s""", (self.name, purpose))[0][0])
|
||||
|
||||
if qty > self.qty:
|
||||
frappe.throw(_("{0} ({1}) cannot be greater than planned quanitity ({2}) in Work Order {3}").format(\
|
||||
frappe.throw(_("{0} ({1}) cannot be greater than planned quantity ({2}) in Work Order {3}").format(\
|
||||
self.meta.get_label(fieldname), qty, self.qty, self.name), StockOverProductionError)
|
||||
|
||||
self.db_set(fieldname, qty)
|
||||
@ -449,6 +449,9 @@ class WorkOrder(Document):
|
||||
# update in bin
|
||||
self.update_reserved_qty_for_production()
|
||||
|
||||
# calculate consumed qty based on submitted stock entries
|
||||
self.update_consumed_qty_for_required_items()
|
||||
|
||||
def update_reserved_qty_for_production(self, items=None):
|
||||
'''update reserved_qty_for_production in bins'''
|
||||
for d in self.required_items:
|
||||
@ -515,6 +518,24 @@ class WorkOrder(Document):
|
||||
|
||||
d.db_set('transferred_qty', flt(transferred_qty), update_modified = False)
|
||||
|
||||
def update_consumed_qty_for_required_items(self):
|
||||
'''update consumed qty from submitted stock entries for that item against
|
||||
the work order'''
|
||||
|
||||
for d in self.required_items:
|
||||
consumed_qty = frappe.db.sql('''select sum(qty)
|
||||
from `tabStock Entry` entry, `tabStock Entry Detail` detail
|
||||
where
|
||||
entry.work_order = %s
|
||||
and (entry.purpose = "Material Consumption for Manufacture"
|
||||
or entry.purpose = "Manufacture")
|
||||
and entry.docstatus = 1
|
||||
and detail.parent = entry.name
|
||||
and detail.item_code = %s''', (self.name, d.item_code))[0][0]
|
||||
|
||||
d.db_set('consumed_qty', flt(consumed_qty), update_modified = False)
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_item_details(item, project = None):
|
||||
res = frappe.db.sql("""
|
||||
@ -600,9 +621,10 @@ def make_stock_entry(work_order_id, purpose, qty=None):
|
||||
else:
|
||||
stock_entry.from_warehouse = wip_warehouse
|
||||
stock_entry.to_warehouse = work_order.fg_warehouse
|
||||
additional_costs = get_additional_costs(work_order, fg_qty=stock_entry.fg_completed_qty)
|
||||
stock_entry.project = work_order.project
|
||||
stock_entry.set("additional_costs", additional_costs)
|
||||
if purpose=="Manufacture":
|
||||
additional_costs = get_additional_costs(work_order, fg_qty=stock_entry.fg_completed_qty)
|
||||
stock_entry.set("additional_costs", additional_costs)
|
||||
|
||||
stock_entry.get_items()
|
||||
return stock_entry.as_dict()
|
||||
|
@ -299,22 +299,24 @@
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "column_break_9",
|
||||
"fieldtype": "Column Break",
|
||||
"depends_on": "eval:!parent.skip_transfer",
|
||||
"fieldname": "consumed_qty",
|
||||
"fieldtype": "Float",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Consumed Qty",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"read_only": 1,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
|
@ -95,6 +95,7 @@ frappe.ui.form.on('Stock Entry', {
|
||||
|
||||
refresh: function(frm) {
|
||||
if(!frm.doc.docstatus) {
|
||||
frm.trigger('validate_purpose_consumption');
|
||||
frm.add_custom_button(__('Make Material Request'), function() {
|
||||
frappe.model.with_doctype('Material Request', function() {
|
||||
var mr = frappe.model.get_new_doc('Material Request');
|
||||
@ -168,10 +169,20 @@ frappe.ui.form.on('Stock Entry', {
|
||||
},
|
||||
|
||||
purpose: function(frm) {
|
||||
frm.trigger('validate_purpose_consumption');
|
||||
frm.fields_dict.items.grid.refresh();
|
||||
frm.cscript.toggle_related_fields(frm.doc);
|
||||
},
|
||||
|
||||
validate_purpose_consumption: function(frm) {
|
||||
frappe.model.get_value('Manufacturing Settings', {'name': 'Manufacturing Settings'}, 'material_consumption', function(d) {
|
||||
if (d.material_consumption==0 && frm.doc.purpose=="Material Consumption for Manufacture") {
|
||||
frm.set_value("purpose", 'Manufacture');
|
||||
frappe.throw(__('Material Consumption is not set in Manufacturing Settings.'));
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
company: function(frm) {
|
||||
if(frm.doc.company) {
|
||||
var company_doc = frappe.get_doc(":Company", frm.doc.company);
|
||||
@ -592,7 +603,8 @@ erpnext.stock.StockEntry = erpnext.stock.StockController.extend({
|
||||
clean_up: function() {
|
||||
// Clear Work Order record from locals, because it is updated via Stock Entry
|
||||
if(this.frm.doc.work_order &&
|
||||
in_list(["Manufacture", "Material Transfer for Manufacture"], this.frm.doc.purpose)) {
|
||||
in_list(["Manufacture", "Material Transfer for Manufacture", "Material Consumption for Manufacture"],
|
||||
this.frm.doc.purpose)) {
|
||||
frappe.model.remove_from_locals("Work Order",
|
||||
this.frm.doc.work_order);
|
||||
}
|
||||
@ -637,16 +649,17 @@ erpnext.stock.StockEntry = erpnext.stock.StockController.extend({
|
||||
me.frm.set_value("to_warehouse", r.message["wip_warehouse"]);
|
||||
|
||||
|
||||
if (me.frm.doc.purpose == "Manufacture") {
|
||||
if(r.message["additional_costs"].length) {
|
||||
$.each(r.message["additional_costs"], function(i, row) {
|
||||
me.frm.add_child("additional_costs", row);
|
||||
})
|
||||
refresh_field("additional_costs");
|
||||
if (me.frm.doc.purpose == "Manufacture" || me.frm.doc.purpose == "Material Consumption for Manufacture" ) {
|
||||
if (me.frm.doc.purpose == "Manufacture") {
|
||||
if (!me.frm.doc.to_warehouse) me.frm.set_value("to_warehouse", r.message["fg_warehouse"]);
|
||||
if (r.message["additional_costs"].length) {
|
||||
$.each(r.message["additional_costs"], function(i, row) {
|
||||
me.frm.add_child("additional_costs", row);
|
||||
})
|
||||
refresh_field("additional_costs");
|
||||
}
|
||||
}
|
||||
|
||||
if (!me.frm.doc.from_warehouse) me.frm.set_value("from_warehouse", r.message["wip_warehouse"]);
|
||||
if (!me.frm.doc.to_warehouse) me.frm.set_value("to_warehouse", r.message["fg_warehouse"]);
|
||||
}
|
||||
me.get_items()
|
||||
}
|
||||
|
@ -41,6 +41,7 @@
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
@ -72,6 +73,7 @@
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
@ -104,6 +106,7 @@
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 1,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
@ -127,7 +130,7 @@
|
||||
"no_copy": 0,
|
||||
"oldfieldname": "purpose",
|
||||
"oldfieldtype": "Select",
|
||||
"options": "Material Issue\nMaterial Receipt\nMaterial Transfer\nMaterial Transfer for Manufacture\nManufacture\nRepack\nSubcontract",
|
||||
"options": "Material Issue\nMaterial Receipt\nMaterial Transfer\nMaterial Transfer for Manufacture\nMaterial Consumption for Manufacture\nManufacture\nRepack\nSubcontract",
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
@ -137,6 +140,7 @@
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
@ -169,6 +173,7 @@
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
@ -177,7 +182,7 @@
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"depends_on": "eval:in_list([\"Material Transfer for Manufacture\", \"Manufacture\"], doc.purpose)",
|
||||
"depends_on": "eval:in_list([\"Material Transfer for Manufacture\", \"Manufacture\", \"Material Consumption for Manufacture\"], doc.purpose)",
|
||||
"fieldname": "work_order",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
@ -202,6 +207,7 @@
|
||||
"reqd": 0,
|
||||
"search_index": 1,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
@ -234,6 +240,7 @@
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
@ -267,6 +274,7 @@
|
||||
"reqd": 0,
|
||||
"search_index": 1,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
@ -298,6 +306,7 @@
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
@ -331,6 +340,7 @@
|
||||
"reqd": 0,
|
||||
"search_index": 1,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
@ -339,7 +349,7 @@
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"depends_on": "eval:in_list([\"Material Issue\", \"Material Transfer\", \"Manufacture\", \"Repack\", \t\t\t\t\t\"Subcontract\", \"Material Transfer for Manufacture\"], doc.purpose)",
|
||||
"depends_on": "eval:in_list([\"Material Issue\", \"Material Transfer\", \"Manufacture\", \"Repack\", \t\t\t\t\t\"Subcontract\", \"Material Transfer for Manufacture\", \"Material Consumption for Manufacture\"], doc.purpose)",
|
||||
"fieldname": "from_bom",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 0,
|
||||
@ -362,6 +372,7 @@
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
@ -393,6 +404,7 @@
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
@ -423,6 +435,7 @@
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0,
|
||||
"width": "50%"
|
||||
},
|
||||
@ -457,6 +470,7 @@
|
||||
"reqd": 0,
|
||||
"search_index": 1,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
@ -489,6 +503,7 @@
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
@ -520,6 +535,7 @@
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
@ -550,6 +566,7 @@
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
@ -581,6 +598,7 @@
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
@ -614,6 +632,7 @@
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
@ -642,6 +661,7 @@
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
@ -674,6 +694,7 @@
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
@ -705,6 +726,7 @@
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
@ -734,6 +756,7 @@
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
@ -766,6 +789,7 @@
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
@ -798,6 +822,7 @@
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
@ -828,6 +853,7 @@
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
@ -856,6 +882,7 @@
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
@ -888,6 +915,7 @@
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
@ -920,6 +948,7 @@
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
@ -950,6 +979,7 @@
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
@ -979,6 +1009,7 @@
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
@ -1011,6 +1042,7 @@
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
@ -1043,6 +1075,7 @@
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
@ -1072,6 +1105,7 @@
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
@ -1103,6 +1137,7 @@
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
@ -1132,6 +1167,7 @@
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
@ -1163,6 +1199,7 @@
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
@ -1194,6 +1231,7 @@
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
@ -1225,6 +1263,7 @@
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
@ -1256,6 +1295,7 @@
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
@ -1287,6 +1327,7 @@
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
@ -1317,6 +1358,7 @@
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
@ -1350,6 +1392,7 @@
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
@ -1382,6 +1425,7 @@
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
@ -1415,6 +1459,7 @@
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
@ -1445,6 +1490,7 @@
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
@ -1474,6 +1520,7 @@
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
@ -1507,6 +1554,7 @@
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
@ -1539,6 +1587,7 @@
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
@ -1571,6 +1620,7 @@
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
@ -1601,6 +1651,7 @@
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
@ -1633,6 +1684,7 @@
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
@ -1664,6 +1716,7 @@
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
@ -1694,6 +1747,7 @@
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
@ -1725,6 +1779,7 @@
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
@ -1756,6 +1811,7 @@
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
@ -1785,6 +1841,7 @@
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0,
|
||||
"width": "50%"
|
||||
},
|
||||
@ -1817,6 +1874,7 @@
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
@ -1849,6 +1907,7 @@
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
@ -1880,6 +1939,7 @@
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
}
|
||||
],
|
||||
@ -1894,7 +1954,7 @@
|
||||
"issingle": 0,
|
||||
"istable": 0,
|
||||
"max_attachments": 0,
|
||||
"modified": "2018-03-13 12:27:12.884611",
|
||||
"modified": "2018-03-23 10:59:55.065055",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Stock",
|
||||
"name": "Stock Entry",
|
||||
|
@ -43,6 +43,7 @@ class StockEntry(StockController):
|
||||
self.validate_posting_time()
|
||||
self.validate_purpose()
|
||||
self.validate_item()
|
||||
self.validate_qty()
|
||||
self.set_transfer_qty()
|
||||
self.validate_uom_is_integer("uom", "qty")
|
||||
self.validate_uom_is_integer("stock_uom", "transfer_qty")
|
||||
@ -80,15 +81,26 @@ class StockEntry(StockController):
|
||||
self.update_cost_in_project()
|
||||
|
||||
def on_cancel(self):
|
||||
self.update_stock_ledger()
|
||||
self.update_work_order()
|
||||
|
||||
if self.purchase_order and self.purpose == "Subcontract":
|
||||
self.update_purchase_order_supplied_items()
|
||||
|
||||
if self.work_order and self.purpose == "Material Consumption for Manufacture":
|
||||
self.validate_work_order_status()
|
||||
else:
|
||||
self.update_work_order()
|
||||
|
||||
self.update_stock_ledger()
|
||||
self.make_gl_entries_on_cancel()
|
||||
|
||||
def validate_work_order_status(self):
|
||||
pro_doc = frappe.get_doc("Work Order", self.work_order)
|
||||
if pro_doc.status == 'Completed':
|
||||
frappe.throw(_("Cannot cancel transaction for Completed Work Order."))
|
||||
|
||||
def validate_purpose(self):
|
||||
valid_purposes = ["Material Issue", "Material Receipt", "Material Transfer", "Material Transfer for Manufacture",
|
||||
"Manufacture", "Repack", "Subcontract"]
|
||||
"Manufacture", "Repack", "Subcontract", "Material Consumption for Manufacture"]
|
||||
if self.purpose not in valid_purposes:
|
||||
frappe.throw(_("Purpose must be one of {0}").format(comma_or(valid_purposes)))
|
||||
|
||||
@ -139,11 +151,39 @@ class StockEntry(StockController):
|
||||
frappe.throw(_("Row #{0}: Please specify Serial No for Item {1}").format(item.idx, item.item_code),
|
||||
frappe.MandatoryError)
|
||||
|
||||
def validate_qty(self):
|
||||
manufacture_purpose = ["Manufacture", "Material Consumption for Manufacture"]
|
||||
|
||||
if self.purpose in manufacture_purpose and self.work_order:
|
||||
if not frappe.get_value('Work Order', self.work_order, 'skip_transfer'):
|
||||
item_code = []
|
||||
for item in self.items:
|
||||
if cstr(item.t_warehouse) == '':
|
||||
req_items = frappe.get_all('Work Order Item',
|
||||
filters={'parent': self.work_order, 'item_code': item.item_code}, fields=["item_code"])
|
||||
|
||||
transferred_materials = frappe.db.sql("""
|
||||
select
|
||||
sum(qty) as qty
|
||||
from `tabStock Entry` se,`tabStock Entry Detail` sed
|
||||
where
|
||||
se.name = sed.parent and se.docstatus=1 and
|
||||
(se.purpose='Material Transfer for Manufacture' or se.purpose='Manufacture')
|
||||
and sed.item_code=%s and se.work_order= %s and ifnull(sed.t_warehouse, '') != ''
|
||||
""", (item.item_code, self.work_order), as_dict=1)
|
||||
|
||||
stock_qty = flt(item.qty)
|
||||
trans_qty = flt(transferred_materials[0].qty)
|
||||
if req_items:
|
||||
if stock_qty > trans_qty:
|
||||
item_code.append(item.item_code)
|
||||
|
||||
def validate_warehouse(self):
|
||||
"""perform various (sometimes conditional) validations on warehouse"""
|
||||
|
||||
source_mandatory = ["Material Issue", "Material Transfer", "Subcontract", "Material Transfer for Manufacture"]
|
||||
target_mandatory = ["Material Receipt", "Material Transfer", "Subcontract", "Material Transfer for Manufacture"]
|
||||
source_mandatory = ["Material Issue", "Material Transfer", "Subcontract", "Material Transfer for Manufacture",
|
||||
"Material Consumption for Manufacture"]
|
||||
target_mandatory = ["Material Receipt", "Material Transfer", "Subcontract", "Material Transfer for Manufacture",]
|
||||
|
||||
validate_for_manufacture_repack = any([d.bom_no for d in self.get("items")])
|
||||
|
||||
@ -196,10 +236,11 @@ class StockEntry(StockController):
|
||||
frappe.throw(_("Source and target warehouse cannot be same for row {0}").format(d.idx))
|
||||
|
||||
def validate_work_order(self):
|
||||
if self.purpose in ("Manufacture", "Material Transfer for Manufacture"):
|
||||
if self.purpose in ("Manufacture", "Material Transfer for Manufacture", "Material Consumption for Manufacture"):
|
||||
# check if work order is entered
|
||||
|
||||
if self.purpose=="Manufacture" and self.work_order:
|
||||
if (self.purpose=="Manufacture" or self.purpose=="Material Consumption for Manufacture") \
|
||||
and self.work_order:
|
||||
if not self.fg_completed_qty:
|
||||
frappe.throw(_("For Quantity (Manufactured Qty) is mandatory"))
|
||||
self.check_if_operations_completed()
|
||||
@ -234,8 +275,7 @@ class StockEntry(StockController):
|
||||
where parent in (%s)
|
||||
and item_code = %s
|
||||
and ifnull(s_warehouse,'')='' """ % (", ".join(["%s" * len(other_ste)]), "%s"), args)[0][0]
|
||||
|
||||
if fg_qty_already_entered >= qty:
|
||||
if fg_qty_already_entered and fg_qty_already_entered >= qty:
|
||||
frappe.throw(_("Stock Entries already created for Work Order ")
|
||||
+ self.work_order + ":" + ", ".join(other_ste), DuplicateEntryForWorkOrderError)
|
||||
|
||||
@ -590,8 +630,10 @@ class StockEntry(StockController):
|
||||
self.set_work_order_details()
|
||||
|
||||
if self.bom_no:
|
||||
|
||||
if self.purpose in ["Material Issue", "Material Transfer", "Manufacture", "Repack",
|
||||
"Subcontract", "Material Transfer for Manufacture"]:
|
||||
"Subcontract", "Material Transfer for Manufacture", "Material Consumption for Manufacture"]:
|
||||
|
||||
if self.work_order and self.purpose == "Material Transfer for Manufacture":
|
||||
item_dict = self.get_pending_raw_materials()
|
||||
if self.to_warehouse and self.pro_doc:
|
||||
@ -599,10 +641,15 @@ class StockEntry(StockController):
|
||||
item["to_warehouse"] = self.pro_doc.wip_warehouse
|
||||
self.add_to_stock_entry_detail(item_dict)
|
||||
|
||||
elif self.work_order and self.purpose == "Manufacture" and \
|
||||
elif self.work_order and (self.purpose == "Manufacture" or self.purpose == "Material Consumption for Manufacture") and \
|
||||
frappe.db.get_single_value("Manufacturing Settings", "backflush_raw_materials_based_on")== "Material Transferred for Manufacture":
|
||||
self.get_transfered_raw_materials()
|
||||
|
||||
elif self.work_order and (self.purpose == "Manufacture" or self.purpose == "Material Consumption for Manufacture") and \
|
||||
frappe.db.get_single_value("Manufacturing Settings", "backflush_raw_materials_based_on")== "BOM" and \
|
||||
frappe.db.get_single_value("Manufacturing Settings", "material_consumption")== 1:
|
||||
self.get_unconsumed_raw_materials()
|
||||
|
||||
else:
|
||||
if not self.fg_completed_qty:
|
||||
frappe.throw(_("Manufacturing Quantity is mandatory"))
|
||||
@ -726,6 +773,45 @@ class StockEntry(StockController):
|
||||
item.from_warehouse = ""
|
||||
return item_dict
|
||||
|
||||
def get_unconsumed_raw_materials(self):
|
||||
wo = frappe.get_doc("Work Order", self.work_order)
|
||||
wo_items = frappe.get_all('Work Order Item',
|
||||
filters={'parent': self.work_order},
|
||||
fields=["item_code", "required_qty", "consumed_qty"]
|
||||
)
|
||||
|
||||
for item in wo_items:
|
||||
qty = item.required_qty
|
||||
item_account_details = frappe.db.get_value("Item", item.item_code, ["item_name",
|
||||
"description", "stock_uom", "expense_account", "buying_cost_center", "name", "default_warehouse"], as_dict=1)
|
||||
# Take into account consumption if there are any.
|
||||
if self.purpose == 'Manufacture':
|
||||
req_qty_each = flt(item.required_qty / wo.qty)
|
||||
if (flt(item.consumed_qty) != 0):
|
||||
remaining_qty = flt(item.consumed_qty) - (flt(wo.produced_qty) * req_qty_each)
|
||||
exhaust_qty = req_qty_each * wo.produced_qty
|
||||
if remaining_qty > exhaust_qty :
|
||||
if (remaining_qty/(req_qty_each * flt(self.fg_completed_qty))) >= 1:
|
||||
qty =0
|
||||
else:
|
||||
qty = (req_qty_each * flt(self.fg_completed_qty)) - remaining_qty
|
||||
else:
|
||||
qty = req_qty_each * flt(self.fg_completed_qty)
|
||||
|
||||
if qty > 0:
|
||||
self.add_to_stock_entry_detail({
|
||||
item.item_code: {
|
||||
"from_warehouse": wo.wip_warehouse,
|
||||
"to_warehouse": "",
|
||||
"qty": qty,
|
||||
"item_name": item.item_name,
|
||||
"description": item.description,
|
||||
"stock_uom": item_account_details.stock_uom,
|
||||
"expense_account": item_account_details.expense_account,
|
||||
"cost_center": item_account_details.buying_cost_center,
|
||||
}
|
||||
})
|
||||
|
||||
def get_transfered_raw_materials(self):
|
||||
transferred_materials = frappe.db.sql("""
|
||||
select
|
||||
@ -744,7 +830,8 @@ class StockEntry(StockController):
|
||||
from
|
||||
`tabStock Entry` se, `tabStock Entry Detail` sed
|
||||
where
|
||||
se.name = sed.parent and se.docstatus=1 and se.purpose='Manufacture'
|
||||
se.name = sed.parent and se.docstatus=1
|
||||
and (se.purpose='Manufacture' or se.purpose='Material Consumption for Manufacture')
|
||||
and se.work_order= %s and ifnull(sed.s_warehouse, '') != ''
|
||||
group by sed.item_code, sed.s_warehouse
|
||||
""", self.work_order, as_dict=1)
|
||||
@ -755,20 +842,47 @@ class StockEntry(StockController):
|
||||
|
||||
po_qty = frappe.db.sql("""select qty, produced_qty, material_transferred_for_manufacturing from
|
||||
`tabWork Order` where name=%s""", self.work_order, as_dict=1)[0]
|
||||
|
||||
manufacturing_qty = flt(po_qty.qty)
|
||||
produced_qty = flt(po_qty.produced_qty)
|
||||
trans_qty = flt(po_qty.material_transferred_for_manufacturing)
|
||||
|
||||
for item in transferred_materials:
|
||||
qty= item.qty
|
||||
req_items = frappe.get_all('Work Order Item',
|
||||
filters={'parent': self.work_order, 'item_code': item.item_code},
|
||||
fields=["required_qty", "consumed_qty"]
|
||||
)
|
||||
req_qty = flt(req_items[0].required_qty)
|
||||
req_qty_each = flt(req_qty / manufacturing_qty)
|
||||
consumed_qty = flt(req_items[0].consumed_qty)
|
||||
|
||||
if trans_qty and manufacturing_qty >= (produced_qty + flt(self.fg_completed_qty)):
|
||||
if qty >= req_qty:
|
||||
qty = (req_qty/trans_qty) * flt(self.fg_completed_qty)
|
||||
else:
|
||||
qty = qty - consumed_qty
|
||||
|
||||
if self.purpose == 'Manufacture':
|
||||
# If Material Consumption is booked, must pull only remaining components to finish product
|
||||
if consumed_qty != 0:
|
||||
remaining_qty = consumed_qty - (produced_qty * req_qty_each)
|
||||
exhaust_qty = req_qty_each * produced_qty
|
||||
if remaining_qty > exhaust_qty :
|
||||
if (remaining_qty/(req_qty_each * flt(self.fg_completed_qty))) >= 1:
|
||||
qty =0
|
||||
else:
|
||||
qty = (req_qty_each * flt(self.fg_completed_qty)) - remaining_qty
|
||||
else:
|
||||
qty = req_qty_each * flt(self.fg_completed_qty)
|
||||
|
||||
if trans_qty and manufacturing_qty > (produced_qty + flt(self.fg_completed_qty)):
|
||||
qty = (qty/trans_qty) * flt(self.fg_completed_qty)
|
||||
|
||||
elif backflushed_materials.get(item.item_code):
|
||||
for d in backflushed_materials.get(item.item_code):
|
||||
if d.get(item.warehouse):
|
||||
qty-= d.get(item.warehouse)
|
||||
if (qty > req_qty):
|
||||
qty = req_qty
|
||||
qty-= d.get(item.warehouse)
|
||||
|
||||
if qty > 0:
|
||||
self.add_to_stock_entry_detail({
|
||||
|
@ -575,7 +575,8 @@ class TestStockEntry(unittest.TestCase):
|
||||
"bom_no": bom_no,
|
||||
"qty": 1.0,
|
||||
"stock_uom": "_Test UOM",
|
||||
"wip_warehouse": "_Test Warehouse - _TC"
|
||||
"wip_warehouse": "_Test Warehouse - _TC",
|
||||
"skip_transfer": 1
|
||||
})
|
||||
work_order.insert()
|
||||
work_order.submit()
|
||||
@ -680,6 +681,34 @@ class TestStockEntry(unittest.TestCase):
|
||||
repack.insert()
|
||||
self.assertRaises(frappe.ValidationError, repack.submit)
|
||||
|
||||
def test_material_consumption(self):
|
||||
from erpnext.manufacturing.doctype.work_order.work_order \
|
||||
import make_stock_entry as _make_stock_entry
|
||||
bom_no = frappe.db.get_value("BOM", {"item": "_Test FG Item 2",
|
||||
"is_default": 1, "docstatus": 1}, ["name", "operating_cost"])
|
||||
|
||||
work_order = frappe.new_doc("Work Order")
|
||||
work_order.update({
|
||||
"company": "_Test Company",
|
||||
"fg_warehouse": "_Test Warehouse 1 - _TC",
|
||||
"production_item": "_Test FG Item 2",
|
||||
"bom_no": bom_no,
|
||||
"qty": 4.0,
|
||||
"stock_uom": "_Test UOM",
|
||||
"wip_warehouse": "_Test Warehouse - _TC",
|
||||
"additional_operating_cost": 1000
|
||||
})
|
||||
work_order.insert()
|
||||
work_order.submit()
|
||||
|
||||
make_stock_entry(item_code="_Test Serialized Item With Series", target="_Test Warehouse - _TC", qty=50, basic_rate=100)
|
||||
make_stock_entry(item_code="_Test Item 2", target="_Test Warehouse - _TC", qty=50, basic_rate=20)
|
||||
|
||||
stock_entry = frappe.get_doc(_make_stock_entry(work_order.name, "Material Consumption for Manufacture", 2))
|
||||
self.assertEqual(stock_entry.get("items")[0].qty, 10)
|
||||
self.assertEqual(stock_entry.get("items")[1].qty, 6)
|
||||
|
||||
|
||||
def make_serialized_item(item_code=None, serial_no=None, target_warehouse=None):
|
||||
se = frappe.copy_doc(test_records[0])
|
||||
se.get("items")[0].item_code = item_code or "_Test Serialized Item With Series"
|
||||
|
Loading…
x
Reference in New Issue
Block a user