[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:
Doridel Cahanap 2018-04-09 07:01:28 +02:00 committed by Nabin Hait
parent e9638be032
commit ad76f9ad70
18 changed files with 2834 additions and 2453 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 112 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 296 KiB

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

View File

@ -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">

View File

@ -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', {
});

View File

@ -1,493 +1,539 @@
{
"allow_copy": 0,
"allow_guest_to_view": 0,
"allow_import": 0,
"allow_rename": 0,
"beta": 0,
"creation": "2014-11-27 14:12:07.542534",
"custom": 0,
"docstatus": 0,
"doctype": "DocType",
"document_type": "Document",
"editable_grid": 0,
"engine": "InnoDB",
"allow_copy": 0,
"allow_guest_to_view": 0,
"allow_import": 0,
"allow_rename": 0,
"beta": 0,
"creation": "2014-11-27 14:12:07.542534",
"custom": 0,
"docstatus": 0,
"doctype": "DocType",
"document_type": "Document",
"editable_grid": 0,
"engine": "InnoDB",
"fields": [
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "capacity_planning",
"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": "Capacity Planning",
"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,
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "capacity_planning",
"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": "Capacity Planning",
"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
},
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"description": "Disables creation of time logs against Work Orders. Operations shall not be tracked against Work Order",
"fieldname": "disable_capacity_planning",
"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": "Disable Capacity Planning and Time Tracking",
"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,
"fieldname": "disable_capacity_planning",
"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": "Disable Capacity Planning and Time Tracking",
"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
},
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"description": "Plan time logs outside Workstation Working Hours.",
"fieldname": "allow_overtime",
"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 Overtime",
"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,
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"description": "Plan time logs outside Workstation Working Hours.",
"fieldname": "allow_overtime",
"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 Overtime",
"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
},
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "",
"fieldname": "allow_production_on_holidays",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Allow Production on Holidays",
"length": 0,
"no_copy": 0,
"options": "",
"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,
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "",
"fieldname": "allow_production_on_holidays",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Allow Production on Holidays",
"length": 0,
"no_copy": 0,
"options": "",
"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
},
},
{
"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,
"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,
"translatable": 0,
"unique": 0
},
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "30",
"description": "Try planning operations for X days in advance.",
"fieldname": "capacity_planning_for_days",
"fieldtype": "Int",
"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": "Capacity Planning For (Days)",
"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,
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "30",
"description": "Try planning operations for X days in advance.",
"fieldname": "capacity_planning_for_days",
"fieldtype": "Int",
"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": "Capacity Planning For (Days)",
"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
},
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"description": "Default 10 mins",
"fieldname": "mins_between_operations",
"fieldtype": "Int",
"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 Between Operations (in mins)",
"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,
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"description": "Default 10 mins",
"fieldname": "mins_between_operations",
"fieldtype": "Int",
"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 Between Operations (in mins)",
"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
},
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "section_break_6",
"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,
"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,
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "section_break_6",
"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,
"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
},
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "over_production_allowance_percentage",
"fieldtype": "Percent",
"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": "Over Production Allowance Percentage",
"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,
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "over_production_allowance_percentage",
"fieldtype": "Percent",
"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": "Over Production Allowance Percentage",
"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
},
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "BOM",
"fieldname": "backflush_raw_materials_based_on",
"fieldtype": "Select",
"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": "Backflush Raw Materials Based On",
"length": 0,
"no_copy": 0,
"options": "BOM\nMaterial Transferred for Manufacture",
"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,
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "BOM",
"fieldname": "backflush_raw_materials_based_on",
"fieldtype": "Select",
"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": "Backflush Raw Materials Based On",
"length": 0,
"no_copy": 0,
"options": "BOM\nMaterial Transferred for Manufacture",
"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
},
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"description": "Update BOM cost automatically via Scheduler, based on latest valuation rate / price list rate / last purchase rate of raw materials.",
"fieldname": "update_bom_costs_automatically",
"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": "Update BOM Cost Automatically",
"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,
"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
},
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "column_break_11",
"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,
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"description": "Update BOM cost automatically via Scheduler, based on latest valuation rate / price list rate / last purchase rate of raw materials.",
"fieldname": "update_bom_costs_automatically",
"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": "Update BOM Cost Automatically",
"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
},
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "default_wip_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": "Default Work In Progress 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,
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "column_break_11",
"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,
"translatable": 0,
"unique": 0
},
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "default_fg_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": "Default Finished Goods 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,
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "default_wip_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": "Default Work In Progress 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,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "default_fg_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": "Default Finished Goods 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,
"translatable": 0,
"unique": 0
}
],
"has_web_view": 0,
"hide_heading": 0,
"hide_toolbar": 0,
"icon": "icon-wrench",
"idx": 0,
"image_view": 0,
"in_create": 0,
"is_submittable": 0,
"issingle": 1,
"istable": 0,
"max_attachments": 0,
"menu_index": 0,
"modified": "2018-02-16 13:18:17.964103",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "Manufacturing Settings",
"name_case": "",
"owner": "Administrator",
],
"has_web_view": 0,
"hide_heading": 0,
"hide_toolbar": 0,
"icon": "icon-wrench",
"idx": 0,
"image_view": 0,
"in_create": 0,
"is_submittable": 0,
"issingle": 1,
"istable": 0,
"max_attachments": 0,
"menu_index": 0,
"modified": "2018-03-28 13:56:31.187520",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "Manufacturing Settings",
"name_case": "",
"owner": "Administrator",
"permissions": [
{
"amend": 0,
"apply_user_permissions": 0,
"cancel": 0,
"create": 1,
"delete": 0,
"email": 0,
"export": 0,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 0,
"read": 1,
"report": 0,
"role": "Manufacturing Manager",
"set_user_permissions": 0,
"share": 1,
"submit": 0,
"amend": 0,
"apply_user_permissions": 0,
"cancel": 0,
"create": 1,
"delete": 0,
"email": 0,
"export": 0,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 0,
"read": 1,
"report": 0,
"role": "Manufacturing Manager",
"set_user_permissions": 0,
"share": 1,
"submit": 0,
"write": 1
}
],
"quick_entry": 0,
"read_only": 0,
"read_only_onload": 0,
"show_name_in_global_search": 0,
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1,
],
"quick_entry": 0,
"read_only": 0,
"read_only_onload": 0,
"show_name_in_global_search": 0,
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1,
"track_seen": 0
}

View File

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

View File

@ -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:

View File

@ -7,7 +7,7 @@ frappe.ui.form.on("Work Order", {
'Timesheet': 'Make Timesheet',
'Stock Entry': 'Make Stock Entry',
}
// Set query for warehouses
frm.set_query("wip_warehouse", function(doc) {
return {
@ -16,7 +16,7 @@ frappe.ui.form.on("Work Order", {
}
}
});
frm.set_query("source_warehouse", function() {
return {
filters: {
@ -24,7 +24,7 @@ frappe.ui.form.on("Work Order", {
}
}
});
frm.set_query("source_warehouse", "required_items", function() {
return {
filters: {
@ -32,7 +32,7 @@ frappe.ui.form.on("Work Order", {
}
}
});
frm.set_query("fg_warehouse", function() {
return {
filters: {
@ -41,7 +41,7 @@ frappe.ui.form.on("Work Order", {
}
}
});
frm.set_query("scrap_warehouse", function() {
return {
filters: {
@ -50,7 +50,7 @@ frappe.ui.form.on("Work Order", {
}
}
});
// Set query for BOM
frm.set_query("bom_no", function() {
if (frm.doc.production_item) {
@ -60,7 +60,7 @@ frappe.ui.form.on("Work Order", {
}
} else msgprint(__("Please enter Production Item first"));
});
// Set query for FG Item
frm.set_query("production_item", function() {
return {
@ -84,7 +84,7 @@ frappe.ui.form.on("Work Order", {
frm.set_indicator_formatter('operation',
function(doc) { return (frm.doc.qty==doc.completed_qty) ? "green" : "orange" });
},
onload: function(frm) {
if (!frm.doc.status)
frm.doc.status = 'Draft';
@ -122,7 +122,7 @@ frappe.ui.form.on("Work Order", {
})
}
},
show_progress: function(frm) {
var bars = [];
var message = '';
@ -156,7 +156,7 @@ frappe.ui.form.on("Work Order", {
}
frm.dashboard.add_progress(__('Status'), bars, message);
},
production_item: function(frm) {
if (frm.doc.production_item) {
frappe.call({
@ -184,13 +184,13 @@ frappe.ui.form.on("Work Order", {
});
}
},
project: function(frm) {
if(!erpnext.in_production_item_onchange) {
frm.trigger("production_item");
}
},
bom_no: function(frm) {
return frm.call({
doc: frm.doc,
@ -203,7 +203,7 @@ frappe.ui.form.on("Work Order", {
}
});
},
use_multi_level_bom: function(frm) {
if(frm.doc.bom_no) {
frm.trigger("bom_no");
@ -213,7 +213,7 @@ frappe.ui.form.on("Work Order", {
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);
@ -251,7 +251,7 @@ frappe.ui.form.on("Work Order Item", {
item_code: row.item_code,
warehouse: row.source_warehouse
},
callback: function (r) {
callback: function (r) {
frappe.model.set_value(row.doctype, row.name,
"available_qty_at_source_warehouse", r.message);
}
@ -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');
});
@ -367,7 +388,7 @@ erpnext.work_order = {
});
}
},
make_se: function(frm, purpose) {
if(!frm.doc.skip_transfer){
var max = (purpose === "Manufacture") ?
@ -399,7 +420,31 @@ 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",

View File

@ -64,7 +64,7 @@ class WorkOrder(Document):
so.name, so_item.delivery_date, so.project
from
`tabSales Order` so, `tabSales Order Item` so_item, `tabPacked Item` packed_item
where so.name=%s
where so.name=%s
and so.name=so_item.parent
and so.name=packed_item.parent
and so_item.item_code = packed_item.parent_item
@ -88,7 +88,7 @@ class WorkOrder(Document):
self.wip_warehouse = frappe.db.get_single_value("Manufacturing Settings", "default_wip_warehouse")
if not self.fg_warehouse:
self.fg_warehouse = frappe.db.get_single_value("Manufacturing Settings", "default_fg_warehouse")
def validate_warehouse_belongs_to_company(self):
warehouses = [self.fg_warehouse, self.wip_warehouse]
for d in self.get("required_items"):
@ -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()
@ -668,5 +690,5 @@ def query_sales_order(production_item):
select distinct so.name from `tabSales Order` so, `tabPacked Item` pi_item
where pi_item.parent=so.name and pi_item.item_code=%s and so.docstatus=1
""", (production_item, production_item))
return out

View File

@ -1,16 +1,16 @@
{
"allow_copy": 0,
"allow_guest_to_view": 0,
"allow_import": 0,
"allow_rename": 0,
"beta": 0,
"creation": "2016-04-18 07:38:26.314642",
"custom": 0,
"docstatus": 0,
"doctype": "DocType",
"document_type": "",
"editable_grid": 1,
"engine": "InnoDB",
"allow_copy": 0,
"allow_guest_to_view": 0,
"allow_import": 0,
"allow_rename": 0,
"beta": 0,
"creation": "2016-04-18 07:38:26.314642",
"custom": 0,
"docstatus": 0,
"doctype": "DocType",
"document_type": "",
"editable_grid": 1,
"engine": "InnoDB",
"fields": [
{
"allow_bulk_edit": 0,
@ -41,9 +41,9 @@
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"translatable": 0,
"unique": 0
},
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
@ -73,9 +73,9 @@
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"translatable": 0,
"unique": 0
},
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
@ -103,9 +103,9 @@
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"translatable": 0,
"unique": 0
},
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
@ -134,9 +134,9 @@
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"translatable": 0,
"unique": 0
},
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
@ -165,9 +165,9 @@
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"translatable": 0,
"unique": 0
},
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
@ -196,9 +196,9 @@
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"translatable": 0,
"unique": 0
},
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
@ -227,9 +227,9 @@
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"translatable": 0,
"unique": 0
},
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
@ -290,39 +290,41 @@
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"translatable": 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,
"translatable": 0,
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"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": 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": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
@ -351,14 +353,14 @@
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"translatable": 0,
"unique": 0
},
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "available_qty_at_wip_warehouse",
"fieldtype": "Float",
@ -398,17 +400,17 @@
"max_attachments": 0,
"modified": "2018-03-05 13:07:07.530725",
"modified_by": "Administrator",
"module": "Manufacturing",
"module": "Manufacturing",
"name": "Work Order Item",
"name_case": "",
"owner": "Administrator",
"permissions": [],
"quick_entry": 1,
"read_only": 0,
"read_only_onload": 0,
"show_name_in_global_search": 0,
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1,
"name_case": "",
"owner": "Administrator",
"permissions": [],
"quick_entry": 1,
"read_only": 0,
"read_only_onload": 0,
"show_name_in_global_search": 0,
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1,
"track_seen": 0
}

View File

@ -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);
@ -519,11 +530,11 @@ erpnext.stock.StockEntry = erpnext.stock.StockController.extend({
}
this.frm.set_indicator_formatter('item_code',
function(doc) {
function(doc) {
if (!doc.s_warehouse) {
return 'blue';
} else {
return (doc.qty<=doc.actual_qty) ? "green" : "orange"
return (doc.qty<=doc.actual_qty) ? "green" : "orange"
}
})
@ -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()
}

File diff suppressed because it is too large Load Diff

View File

@ -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"))
@ -613,7 +660,7 @@ class StockEntry(StockController):
if self.purchase_order and self.purpose == "Subcontract":
#Get PO Supplied Items Details
item_wh = frappe._dict(frappe.db.sql("""
select rm_item_code, reserve_warehouse
select rm_item_code, reserve_warehouse
from `tabPurchase Order` po, `tabPurchase Order Item Supplied` poitemsup
where po.name = poitemsup.parent
and po.name = %s""",self.purchase_order))
@ -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({
@ -841,7 +955,7 @@ class StockEntry(StockController):
for d in item_dict:
stock_uom = item_dict[d].get("stock_uom") or frappe.db.get_value("Item", d, "stock_uom")
se_child = self.append('items')
se_child.s_warehouse = item_dict[d].get("from_warehouse")
se_child.t_warehouse = item_dict[d].get("to_warehouse")
@ -894,7 +1008,7 @@ class StockEntry(StockController):
def update_purchase_order_supplied_items(self):
#Get PO Supplied Items Details
item_wh = frappe._dict(frappe.db.sql("""
select rm_item_code, reserve_warehouse
select rm_item_code, reserve_warehouse
from `tabPurchase Order` po, `tabPurchase Order Item Supplied` poitemsup
where po.name = poitemsup.parent
and po.name = %s""", self.purchase_order))
@ -905,7 +1019,7 @@ class StockEntry(StockController):
reserve_warehouse = item_wh.get(item_code)
stock_bin = get_bin(item_code, reserve_warehouse)
stock_bin.update_reserved_qty_for_sub_contracting()
@frappe.whitelist()
def move_sample_to_retention_warehouse(company, items):
if isinstance(items, string_types):
@ -1075,4 +1189,4 @@ def validate_sample_quantity(item_code, sample_quantity, qty, batch_no = None):
frappe.msgprint(_("Maximum Samples - {0} can be retained for Batch {1} and Item {2}.").
format(max_retain_qty, batch_no, item_code), alert=True)
sample_quantity = qty_diff
return sample_quantity
return sample_quantity

View File

@ -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"