[enhance] Production Plan (#12160)

* [enhance] Production plan

* Test cases

* Removed production planning tool

* Documentation

* Added actual qty in material request production item

* Renamed field 'Use Multilevel BOM' to 'Include Exploded Items'
This commit is contained in:
rohitwaghchaure 2018-02-20 12:20:00 +05:30 committed by Nabin Hait
parent 244e235c48
commit e2558308f6
42 changed files with 2463 additions and 933 deletions

View File

@ -217,11 +217,6 @@ def get_data():
"label": _("Bill of Materials"),
"youtube_id": "hDV0c1OeWLo"
},
{
"type": "help",
"label": _("Production Planning Tool"),
"youtube_id": "CzatSl4zJ2Y"
},
{
"type": "help",
"label": _("Production Order"),

View File

@ -14,7 +14,7 @@ def get_data():
},
{
"type": "doctype",
"name": "Production Planning Tool",
"name": "Production Plan",
"description": _("Generate Material Requests (MRP) and Production Orders."),
},
{
@ -141,11 +141,6 @@ def get_data():
"label": _("Bill of Materials"),
"youtube_id": "hDV0c1OeWLo"
},
{
"type": "help",
"label": _("Production Planning Tool"),
"youtube_id": "CzatSl4zJ2Y"
},
{
"type": "help",
"label": _("Production Order"),

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 102 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

View File

@ -0,0 +1,53 @@
# Production Plan
Production plan helps user to plan production aginst the multiple sales orders or the material requests and also plan for the purchase of the raw materials which will be used in the production.
To use the Production Plan, go to:
> Manufacturing > Production > Production Plan
<img class="screenshot" alt="Production Plan" src="/docs/assets/img/manufacturing/production_plan.png">
## Planning for Production
#### Production Against Sales Orders
* Select option as Sales Order from the drop down list of get items from. System will show the filters, using that we can pull the sales orders for the production.
<img class="screenshot" alt="Sales Order Filters" src="/docs/assets/img/manufacturing/sales_order_filter.png">
* Click on Get Sales Orders to fetch sales orders based on above filters
<img class="screenshot" alt="Sales Orders" src="/docs/assets/img/manufacturing/sales_orders.png">
* Click on Get Items for Production Order to fetch the items from the above sales orders.
<img class="screenshot" alt="Sales Order Item" src="/docs/assets/img/manufacturing/sales_order_items.png">
* Include Exploded Items :- To include subassembly items of raw materials in the production.
#### Production Against Material Requests
* Select option as Material Request from the drop down list of get items from. System will show the filters, using that we can pull the material requests for the production.
<img class="screenshot" alt="Material Request Filters" src="/docs/assets/img/manufacturing/material_request_filter.png">
* Click on Get Material Request to fetch material requests based on above filters
<img class="screenshot" alt="Material Requests" src="/docs/assets/img/manufacturing/material_requests.png">
* Click on Get Items for Production Order to fetch the items from the above material requests.
<img class="screenshot" alt="Material Request Item" src="/docs/assets/img/manufacturing/material_request_items.png">
## Planning for Material Requests
* Click on get raw materials for production button to fetech raw materials required in the production.
<img class="screenshot" alt="Material Request Plan" src="/docs/assets/img/manufacturing/material_request_plan.png">
* Include Non Stock Items :- To add non stock items in the material request planning.
* Include Subcontracted Items :- To add subcontracted item's raw materials if include exploded items is disabled
* Ignore Existing Ordered Quantity :- If enabled then system will not check the projected quantity to make material request.
# Options To Make Production Order and Material Request
<img class="screenshot" alt="Make PO or MR" src="/docs/assets/img/manufacturing/make_po_mr.png">

View File

@ -0,0 +1,8 @@
// Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
frappe.ui.form.on('Material Request Plan Item', {
refresh: function() {
}
});

View File

@ -0,0 +1,344 @@
{
"allow_copy": 0,
"allow_guest_to_view": 0,
"allow_import": 0,
"allow_rename": 0,
"beta": 0,
"creation": "2017-12-01 12:12:55.048691",
"custom": 0,
"docstatus": 0,
"doctype": "DocType",
"document_type": "",
"editable_grid": 1,
"engine": "InnoDB",
"fields": [
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "item_code",
"fieldtype": "Link",
"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": "Item Code",
"length": 0,
"no_copy": 0,
"options": "Item",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "item_name",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Item Name",
"length": 0,
"no_copy": 0,
"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,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "warehouse",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 1,
"label": "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": 1,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "column_break_4",
"fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "quantity",
"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": "Quantity",
"length": 0,
"no_copy": 1,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "actual_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": "Actual Qty",
"length": 0,
"no_copy": 1,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "min_order_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": "Minimum Order Quantity",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 1,
"columns": 0,
"fieldname": "section_break_8",
"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": "Reference",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "sales_order",
"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": "Sales Order",
"length": 0,
"no_copy": 0,
"options": "Sales Order",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "requested_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_standard_filter": 0,
"label": "Requested 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,
"unique": 0
}
],
"has_web_view": 0,
"hide_heading": 0,
"hide_toolbar": 0,
"idx": 0,
"image_view": 0,
"in_create": 0,
"is_submittable": 0,
"issingle": 0,
"istable": 1,
"max_attachments": 0,
"modified": "2018-02-15 13:08:30.535963",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "Material Request Plan 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,
"track_seen": 0
}

View File

@ -0,0 +1,9 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
from __future__ import unicode_literals
from frappe.model.document import Document
class MaterialRequestPlanItem(Document):
pass

View File

@ -0,0 +1,23 @@
/* eslint-disable */
// rename this file from _test_[name] to test_[name] to activate
// and remove above this line
QUnit.test("test: Material Request Plan Item", function (assert) {
let done = assert.async();
// number of asserts
assert.expect(1);
frappe.run_serially([
// insert a new Material Request Plan Item
() => frappe.tests.make('Material Request Plan Item', [
// values to be set
{key: 'value'}
]),
() => {
assert.equal(cur_frm.doc.key, 'value');
},
() => done()
]);
});

View File

@ -0,0 +1,9 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
from __future__ import unicode_literals
import unittest
class TestMaterialRequestPlanItem(unittest.TestCase):
pass

View File

@ -1344,6 +1344,67 @@
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "production_plan",
"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": "Production Plan",
"length": 0,
"no_copy": 1,
"options": "Production Plan",
"permlevel": 0,
"precision": "",
"print_hide": 1,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "production_plan_item",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Production Plan Item",
"length": 0,
"no_copy": 1,
"permlevel": 0,
"precision": "",
"print_hide": 1,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
@ -1388,7 +1449,7 @@
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2017-11-03 05:31:56.636724",
"modified": "2017-12-20 05:31:56.636724",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "Production Order",

View File

@ -189,6 +189,13 @@ class ProductionOrder(Document):
self.db_set(fieldname, qty)
if self.production_plan:
self.update_production_plan_status()
def update_production_plan_status(self):
production_plan = frappe.get_doc('Production Plan', self.production_plan)
production_plan.run_method("update_produced_qty", self.produced_qty, self.production_plan_item)
def before_submit(self):
self.make_time_logs()
@ -201,6 +208,7 @@ class ProductionOrder(Document):
self.update_reserved_qty_for_production()
self.update_completed_qty_in_material_request()
self.update_planned_qty()
self.update_ordered_qty()
def on_cancel(self):
self.validate_cancel()
@ -209,6 +217,7 @@ class ProductionOrder(Document):
self.delete_timesheet()
self.update_completed_qty_in_material_request()
self.update_planned_qty()
self.update_ordered_qty()
self.update_reserved_qty_for_production()
def validate_cancel(self):
@ -230,6 +239,16 @@ class ProductionOrder(Document):
mr_obj = frappe.get_doc("Material Request", self.material_request)
mr_obj.update_requested_qty([self.material_request_item])
def update_ordered_qty(self):
if self.production_plan and self.production_plan_item:
qty = self.qty if self.docstatus == 1 else 0
frappe.db.set_value('Production Plan Item',
self.production_plan_item, 'ordered_qty', qty)
doc = frappe.get_doc('Production Plan', self.production_plan)
doc.set_status()
doc.db_set('status', doc.status)
def update_completed_qty_in_material_request(self):
if self.material_request:
frappe.get_doc("Material Request", self.material_request).update_completed_qty([self.material_request_item])

View File

@ -0,0 +1,168 @@
// Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
frappe.ui.form.on('Production Plan', {
setup: function(frm) {
frm.fields_dict['po_items'].grid.get_field('warehouse').get_query = function(doc) {
return {
filters: {
company: doc.company
}
}
}
frm.fields_dict['po_items'].grid.get_field('bom_no').get_query = function(doc, cdt, cdn) {
var d = locals[cdt][cdn];
if (d.item_code) {
return {
query: "erpnext.controllers.queries.bom",
filters:{'item': cstr(d.item_code)}
}
} else frappe.msgprint(__("Please enter Item first"));
}
frm.fields_dict['mr_items'].grid.get_field('warehouse').get_query = function(doc) {
return {
filters: {
company: doc.company
}
}
}
},
refresh: function(frm) {
if (frm.doc.docstatus === 1) {
frm.trigger("show_progress");
}
if (frm.doc.docstatus === 1 && frm.doc.po_items
&& frm.doc.status != 'Completed') {
frm.add_custom_button(__("Production Order"), ()=> {
frm.trigger("make_production_order");
}, __("Make"));
}
if (frm.doc.docstatus === 1 && frm.doc.mr_items
&& !in_list(['Material Requested', 'Completed'], frm.doc.status)) {
frm.add_custom_button(__("Material Request"), ()=> {
frm.trigger("make_material_request");
}, __("Make"));
}
frm.trigger("material_requirement");
},
make_production_order: function(frm) {
frappe.call({
method: "make_production_order",
freeze: true,
doc: frm.doc,
callback: function() {
frm.reload_doc();
}
});
},
make_material_request: function(frm) {
frappe.call({
method: "make_material_request",
freeze: true,
doc: frm.doc,
callback: function(r) {
frm.reload_doc();
}
});
},
get_sales_orders: function(frm) {
frappe.call({
method: "get_open_sales_orders",
doc: frm.doc,
callback: function(r) {
refresh_field("sales_orders");
}
});
},
get_material_request: function(frm) {
frappe.call({
method: "get_pending_material_requests",
doc: frm.doc,
callback: function() {
refresh_field('material_requests');
}
});
},
get_items: function(frm) {
frappe.call({
method: "get_items",
freeze: true,
doc: frm.doc,
callback: function() {
refresh_field('po_items');
}
});
},
get_items_for_mr: function(frm) {
frappe.call({
method: "get_items_for_material_requests",
freeze: true,
doc: frm.doc,
callback: function() {
refresh_field('mr_items');
}
});
},
show_progress: function(frm) {
var bars = [];
var message = '';
var title = '';
// produced qty
let item_wise_qty = {};
frm.doc.po_items.forEach((data) => {
if(!item_wise_qty[data.item_code]) {
item_wise_qty[data.item_code] = data.produced_qty;
} else {
item_wise_qty[data.item_code] += data.produced_qty;
}
})
if (item_wise_qty) {
for (var key in item_wise_qty) {
title += __('Item {0}: {1} qty produced, ', [key, item_wise_qty[key]]);
}
}
bars.push({
'title': title,
'width': (frm.doc.total_produced_qty / frm.doc.total_planned_qty * 100) + '%',
'progress_class': 'progress-bar-success'
});
if (bars[0].width == '0%') {
bars[0].width = '0.5%';
}
message = title;
frm.dashboard.add_progress(__('Status'), bars, message);
},
});
frappe.ui.form.on("Material Request Plan Item", {
warehouse: function(frm, cdt, cdn) {
const row = locals[cdt][cdn];
if (row.warehouse && row.item_code) {
frappe.call({
method: "erpnext.manufacturing.doctype.production_plan.production_plan.get_bin_details",
args: {
row: row
},
callback: function(r) {
frappe.model.set_value(cdt, cdn, 'actual_qty', r.message[1])
}
})
}
}
})

View File

@ -0,0 +1,499 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
from __future__ import unicode_literals
import frappe, json
from frappe import msgprint, _
from frappe.model.document import Document
from erpnext.manufacturing.doctype.bom.bom import validate_bom_no
from frappe.utils import cstr, flt, cint, nowdate, add_days, comma_and, now_datetime
from erpnext.manufacturing.doctype.production_order.production_order import get_item_details
class ProductionPlan(Document):
def validate(self):
self.calculate_total_planned_qty()
self.set_status()
def validate_data(self):
for d in self.get('po_items'):
if not d.bom_no:
frappe.throw(_("Please select BOM for Item in Row {0}".format(d.idx)))
else:
validate_bom_no(d.item_code, d.bom_no)
if not flt(d.planned_qty):
frappe.throw(_("Please enter Planned Qty for Item {0} at row {1}").format(d.item_code, d.idx))
def get_open_sales_orders(self):
""" Pull sales orders which are pending to deliver based on criteria selected"""
so_filter = item_filter = ""
if self.from_date:
so_filter += " and so.transaction_date >= %(from_date)s"
if self.to_date:
so_filter += " and so.transaction_date <= %(to_date)s"
if self.customer:
so_filter += " and so.customer = %(customer)s"
if self.project:
so_filter += " and so.project = %(project)s"
if self.item_code:
item_filter += " and so_item.item_code = %(item)s"
open_so = frappe.db.sql("""
select distinct so.name, so.transaction_date, so.customer, so.base_grand_total
from `tabSales Order` so, `tabSales Order Item` so_item
where so_item.parent = so.name
and so.docstatus = 1 and so.status not in ("Stopped", "Closed")
and so.company = %(company)s
and so_item.qty > so_item.delivered_qty {0} {1}
and (exists (select name from `tabBOM` bom where bom.item=so_item.item_code
and bom.is_active = 1)
or exists (select name from `tabPacked Item` pi
where pi.parent = so.name and pi.parent_item = so_item.item_code
and exists (select name from `tabBOM` bom where bom.item=pi.item_code
and bom.is_active = 1)))
""".format(so_filter, item_filter), {
"from_date": self.from_date,
"to_date": self.to_date,
"customer": self.customer,
"project": self.project,
"item": self.item_code,
"company": self.company
}, as_dict=1)
self.add_so_in_table(open_so)
def add_so_in_table(self, open_so):
""" Add sales orders in the table"""
self.set('sales_orders', [])
for data in open_so:
self.append('sales_orders', {
'sales_order': data.name,
'sales_order_date': data.transaction_date,
'customer': data.customer,
'grand_total': data.grand_total
})
def get_pending_material_requests(self):
""" Pull Material Requests that are pending based on criteria selected"""
mr_filter = item_filter = ""
if self.from_date:
mr_filter += " and mr.transaction_date >= %(from_date)s"
if self.to_date:
mr_filter += " and mr.transaction_date <= %(to_date)s"
if self.warehouse:
mr_filter += " and mr_item.warehouse = %(warehouse)s"
if self.item_code:
item_filter += " and mr_item.item_code = %(item)s"
pending_mr = frappe.db.sql("""
select distinct mr.name, mr.transaction_date
from `tabMaterial Request` mr, `tabMaterial Request Item` mr_item
where mr_item.parent = mr.name
and mr.material_request_type = "Manufacture"
and mr.docstatus = 1 and mr.company = %(company)s
and mr_item.qty > ifnull(mr_item.ordered_qty,0) {0} {1}
and (exists (select name from `tabBOM` bom where bom.item=mr_item.item_code
and bom.is_active = 1))
""".format(mr_filter, item_filter), {
"from_date": self.from_date,
"to_date": self.to_date,
"warehouse": self.warehouse,
"item": self.item_code,
"company": self.company
}, as_dict=1)
self.add_mr_in_table(pending_mr)
def add_mr_in_table(self, pending_mr):
""" Add Material Requests in the table"""
self.set('material_requests', [])
for data in pending_mr:
self.append('material_requests', {
'material_request': data.name,
'material_request_date': data.transaction_date
})
def get_items(self):
if self.get_items_from == "Sales Order":
self.get_so_items()
elif self.get_items_from == "Material Request":
self.get_mr_items()
def get_so_items(self):
so_list = [d.sales_order for d in self.sales_orders if d.sales_order]
if not so_list:
msgprint(_("Please enter Sales Orders in the above table"))
return []
item_condition = ""
if self.item_code:
item_condition = ' and so_item.item_code = "{0}"'.format(frappe.db.escape(self.item_code))
items = frappe.db.sql("""select distinct parent, item_code, warehouse,
(qty - delivered_qty)*conversion_factor as pending_qty
from `tabSales Order Item` so_item
where parent in (%s) and docstatus = 1 and qty > delivered_qty
and exists (select name from `tabBOM` bom where bom.item=so_item.item_code
and bom.is_active = 1) %s""" % \
(", ".join(["%s"] * len(so_list)), item_condition), tuple(so_list), as_dict=1)
if self.item_code:
item_condition = ' and pi.item_code = "{0}"'.format(frappe.db.escape(self.item_code))
packed_items = frappe.db.sql("""select distinct pi.parent, pi.item_code, pi.warehouse as warehouse,
(((so_item.qty - so_item.delivered_qty) * pi.qty) / so_item.qty)
as pending_qty
from `tabSales Order Item` so_item, `tabPacked Item` pi
where so_item.parent = pi.parent and so_item.docstatus = 1
and pi.parent_item = so_item.item_code
and so_item.parent in (%s) and so_item.qty > so_item.delivered_qty
and exists (select name from `tabBOM` bom where bom.item=pi.item_code
and bom.is_active = 1) %s""" % \
(", ".join(["%s"] * len(so_list)), item_condition), tuple(so_list), as_dict=1)
self.add_items(items + packed_items)
self.calculate_total_planned_qty()
def get_mr_items(self):
mr_list = [d.material_request for d in self.material_requests if d.material_request]
if not mr_list:
msgprint(_("Please enter Material Requests in the above table"))
return []
item_condition = ""
if self.item_code:
item_condition = " and mr_item.item_code ='{0}'".format(frappe.db.escape(self.item_code))
items = frappe.db.sql("""select distinct parent, name, item_code, warehouse,
(qty - ordered_qty) as pending_qty
from `tabMaterial Request Item` mr_item
where parent in (%s) and docstatus = 1 and qty > ordered_qty
and exists (select name from `tabBOM` bom where bom.item=mr_item.item_code
and bom.is_active = 1) %s""" % \
(", ".join(["%s"] * len(mr_list)), item_condition), tuple(mr_list), as_dict=1)
self.add_items(items)
self.calculate_total_planned_qty()
def add_items(self, items):
self.set('po_items', [])
for data in items:
item_details = get_item_details(data.item_code)
pi = self.append('po_items', {
'include_exploded_items': 1,
'warehouse': data.warehouse,
'item_code': data.item_code,
'description': item_details and item_details.description or '',
'stock_uom': item_details and item_details.stock_uom or '',
'bom_no': item_details and item_details.bom_no or '',
'planned_qty': data.pending_qty,
'pending_qty': data.pending_qty,
'planned_start_date': now_datetime()
})
if self.get_items_from == "Sales Order":
pi.sales_order = data.parent
pi.sales_order_item = data.name
elif self.get_items_from == "Material Request":
pi.material_request = data.parent
pi.material_request_item = data.name
def calculate_total_planned_qty(self):
self.total_planned_qty = 0
for d in self.po_items:
self.total_planned_qty += flt(d.planned_qty)
def calculate_total_produced_qty(self):
self.total_produced_qty = 0
for d in self.po_items:
self.total_produced_qty += flt(d.produced_qty)
self.db_set("total_produced_qty", self.total_produced_qty, update_modified=False)
def update_produced_qty(self, produced_qty, production_plan_item):
for data in self.po_items:
if data.name == production_plan_item:
data.produced_qty = produced_qty
data.db_update()
self.calculate_total_produced_qty()
self.set_status()
self.db_set('status', self.status)
def on_cancel(self):
self.db_set('status', 'Cancelled')
self.delete_draft_production_order()
def delete_draft_production_order(self):
for d in frappe.get_all('Production Order', fields = ["name"],
filters = {'docstatus': 0, 'production_plan': ("=", self.name)}):
frappe.delete_doc('Production Order', d.name)
def set_status(self):
self.status = {
'0': 'Draft',
'1': 'Submitted'
}[cstr(self.docstatus or 0)]
if self.total_produced_qty > 0:
self.status = "In Process"
if self.total_produced_qty == self.total_planned_qty:
self.status = "Completed"
if self.status != 'Completed':
self.update_ordered_status()
self.update_requested_status()
def update_ordered_status(self):
update_status = False
for d in self.po_items:
if d.planned_qty == d.ordered_qty:
update_status = True
if update_status and self.status != 'Completed':
self.status = 'In Process'
def update_requested_status(self):
update_status = True
for d in self.mr_items:
if d.quantity != d.requested_qty:
update_status = False
if update_status:
self.status = 'Material Requested'
def get_production_items(self):
item_dict = {}
for d in self.po_items:
item_details= {
"production_item" : d.item_code,
"use_multi_level_bom" : d.include_exploded_items,
"sales_order" : d.sales_order,
"material_request" : d.material_request,
"material_request_item" : d.material_request_item,
"bom_no" : d.bom_no,
"description" : d.description,
"stock_uom" : d.stock_uom,
"company" : self.company,
"fg_warehouse" : d.warehouse,
"production_plan" : self.name,
"production_plan_item" : d.name
}
item_details.update({
"project": self.project or frappe.db.get_value("Sales Order", d.sales_order, "project")
})
if self.get_items_from == "Material Request":
item_details.update({
"qty": d.planned_qty
})
item_dict[(d.item_code, d.material_request_item, d.warehouse)] = item_details
else:
item_details.update({
"qty":flt(item_dict.get((d.item_code, d.sales_order, d.warehouse),{})
.get("qty")) + flt(d.planned_qty)
})
item_dict[(d.item_code, d.sales_order, d.warehouse)] = item_details
return item_dict
def get_items_for_material_requests(self):
self.mr_items = []
for data in self.po_items:
bom_wise_item_details = {}
if not data.planned_qty:
frappe.throw(_("For row {0}: Enter planned qty").format(data.idx))
if data.include_exploded_items and data.bom_no and self.include_subcontracted_items:
for d in frappe.db.sql("""select bei.item_code, item.default_bom as bom,
ifnull(sum(bei.stock_qty/ifnull(bom.quantity, 1)), 0) as qty, item.item_name,
bei.description, bei.stock_uom, item.min_order_qty, bei.source_warehouse,
item.default_material_request_type, item.min_order_qty, item.default_warehouse
from
`tabBOM Explosion Item` bei, `tabBOM` bom, `tabItem` item
where
bom.name = bei.parent and item.name = bei.item_code
and bei.docstatus < 2 and bom.name=%s and item.is_stock_item in (1, {0})
group by bei.item_code, bei.stock_uom""".format(self.include_non_stock_items),
data.bom_no, as_dict=1):
bom_wise_item_details.setdefault(d.item_code, d)
else:
bom_wise_item_details = self.get_subitems(data, bom_wise_item_details, data.bom_no, 1)
for item, item_details in bom_wise_item_details.items():
if item_details.qty > 0:
self.add_item_in_material_request_items(item, item_details, data)
def get_subitems(self, data, bom_wise_item_details, bom_no, parent_qty):
items = frappe.db.sql("""
SELECT
bom_item.item_code, default_material_request_type, item.item_name,
ifnull(%(parent_qty)s * sum(bom_item.stock_qty/ifnull(bom.quantity, 1)), 0) as qty,
item.is_sub_contracted_item as is_sub_contracted, bom_item.source_warehouse,
item.default_bom as default_bom, bom_item.description as description,
bom_item.stock_uom as stock_uom, item.min_order_qty as min_order_qty,
item.default_warehouse
FROM
`tabBOM Item` bom_item, `tabBOM` bom, tabItem item
where
bom.name = bom_item.parent and bom.name = %(bom)s
and bom_item.docstatus < 2 and bom_item.item_code = item.name
and item.is_stock_item in (1, {0})
group by bom_item.item_code""".format(self.include_non_stock_items),{
'bom': bom_no,
'parent_qty': parent_qty
}, as_dict=1)
for d in items:
if not data.include_exploded_items or not d.default_bom:
if d.item_code in bom_wise_item_details:
bom_wise_item_details[d.item_code].qty = bom_wise_item_details[d.item_code].qty + d.qty
else:
bom_wise_item_details[d.item_code] = d
if data.include_exploded_items and d.default_bom:
if ((d.default_material_request_type in ["Manufacture", "Purchase"] and
not d.is_sub_contracted) or (d.is_sub_contracted and self.include_subcontracted_items)):
if d.qty > 0:
self.get_subitems(data, bom_wise_item_details, d.default_bom, d.qty)
return bom_wise_item_details
def add_item_in_material_request_items(self, item, row, data):
total_qty = row.qty * data.planned_qty
projected_qty, actual_qty = get_bin_details(row)
requested_qty = 0
if self.ignore_existing_ordered_qty:
requested_qty = total_qty
elif total_qty > projected_qty:
requested_qty = total_qty - projected_qty
if requested_qty and requested_qty < row.min_order_qty:
requested_qty = row.min_order_qty
if requested_qty > 0:
self.append('mr_items', {
'item_code': item,
'item_name': row.item_name,
'quantity': requested_qty,
'warehouse': row.source_warehouse or row.default_warehouse,
'actual_qty': actual_qty,
'min_order_qty': row.min_order_qty,
'sales_order': data.sales_order
})
def make_production_order(self):
pro_list = []
self.validate_data()
items_data = self.get_production_items()
for key, item in items_data.items():
production_order = self.create_production_order(item)
if production_order:
pro_list.append(production_order)
frappe.flags.mute_messages = False
if pro_list:
pro_list = ["""<a href="#Form/Production Order/%s" target="_blank">%s</a>""" % \
(p, p) for p in pro_list]
msgprint(_("{0} created").format(comma_and(pro_list)))
else :
msgprint(_("No Production Orders created"))
def create_production_order(self, item):
from erpnext.manufacturing.doctype.production_order.production_order import OverProductionError, get_default_warehouse
warehouse = get_default_warehouse()
pro = frappe.new_doc("Production Order")
pro.update(item)
pro.set_production_order_operations()
if not pro.fg_warehouse:
pro.fg_warehouse = warehouse.get('fg_warehouse')
try:
pro.insert()
return pro.name
except OverProductionError:
pass
def make_material_request(self):
material_request_list = []
item_details = self.get_itemwise_qty()
for item_code, rows in item_details.items():
item_doc = frappe.get_doc("Item", item_code)
schedule_date = add_days(nowdate(), cint(item_doc.lead_time_days))
material_request = frappe.new_doc("Material Request")
material_request.update({
"transaction_date": nowdate(),
"status": "Draft",
"company": self.company,
"requested_by": frappe.session.user,
"schedule_date": schedule_date,
'material_request_type': item_doc.default_material_request_type
})
for idx in rows:
child = self.mr_items[cint(idx)-1]
material_request.append("items", {
"item_code": item_code,
"qty": child.quantity,
"schedule_date": schedule_date,
"warehouse": child.warehouse,
"sales_order": child.sales_order,
'production_plan': self.name,
'material_request_plan_item': child.name,
"project": frappe.db.get_value("Sales Order", child.sales_order, "project") \
if child.sales_order else None
})
material_request.flags.ignore_permissions = 1
material_request.run_method("set_missing_values")
material_request.submit()
material_request_list.append(material_request.name)
frappe.flags.mute_messages = False
if material_request_list:
material_request_list = ["""<a href="#Form/Material Request/%s" target="_blank">%s</a>""" % \
(p, p) for p in material_request_list]
msgprint(_("{0} created").format(comma_and(material_request_list)))
else :
msgprint(_("No material request created"))
def get_itemwise_qty(self):
item_details = {}
for data in self.get('mr_items'):
if data.item_code in item_details:
item_details[data.item_code].append(data.idx)
else:
item_details.setdefault(data.item_code, [data.idx])
return item_details
@frappe.whitelist()
def get_bin_details(row):
if isinstance(row, basestring):
row = frappe._dict(json.loads(row))
conditions = ""
warehouse = row.source_warehouse or row.default_warehouse or row.warehouse
if warehouse:
conditions = " and warehouse='{0}'".format(frappe.db.escape(warehouse))
item_projected_qty = frappe.db.sql(""" select ifnull(sum(projected_qty),0) as projected_qty,
ifnull(sum(actual_qty),0) as actual_qty from `tabBin`
where item_code = %(item_code)s {conditions}
""".format(conditions=conditions), { "item_code": row.item_code }, as_list=1)
return item_projected_qty and item_projected_qty[0] or (0,0)

View File

@ -0,0 +1,12 @@
from frappe import _
def get_data():
return {
'fieldname': 'production_plan',
'transactions': [
{
'label': _('Related'),
'items': ['Production Order', 'Material Request']
},
]
}

View File

@ -0,0 +1,17 @@
frappe.listview_settings['Production Plan'] = {
add_fields: ["status"],
filters: [["status", "!=", "Stopped"]],
get_indicator: function(doc) {
if(doc.status==="Submitted") {
return [__("Not Started"), "orange", "status,=,Submitted"];
} else {
return [__(doc.status), {
"Draft": "red",
"In Process": "orange",
"Completed": "green",
"Material Requested": "darkgrey",
"Cancelled": "darkgrey"
}[doc.status], "status,=," + doc.status];
}
}
};

View File

@ -0,0 +1,23 @@
/* eslint-disable */
// rename this file from _test_[name] to test_[name] to activate
// and remove above this line
QUnit.test("test: Production Plan", function (assert) {
let done = assert.async();
// number of asserts
assert.expect(1);
frappe.run_serially([
// insert a new Production Plan
() => frappe.tests.make('Production Plan', [
// values to be set
{key: 'value'}
]),
() => {
assert.equal(cur_frm.doc.key, 'value');
},
() => done()
]);
});

View File

@ -0,0 +1,148 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
from __future__ import unicode_literals
import frappe
import unittest
from frappe.utils import nowdate, now_datetime, flt
from erpnext.stock.doctype.item.test_item import create_item
from erpnext.stock.doctype.stock_reconciliation.test_stock_reconciliation import create_stock_reconciliation
class TestProductionPlan(unittest.TestCase):
def setUp(self):
for item in ['Test Production Item 1', 'Subassembly Item 1',
'Raw Material Item 1', 'Raw Material Item 2']:
create_item(item, valuation_rate=100)
sr = frappe.db.get_value('Stock Reconciliation Item',
{'item_code': item, 'docstatus': 1}, 'parent')
if sr:
sr_doc = frappe.get_doc('Stock Reconciliation', sr)
sr_doc.cancel()
create_item('Test Non Stock Raw Material', is_stock_item=0)
for item, raw_materials in {'Subassembly Item 1': ['Raw Material Item 1', 'Raw Material Item 2'],
'Test Production Item 1': ['Raw Material Item 1', 'Subassembly Item 1',
'Test Non Stock Raw Material']}.items():
if not frappe.db.get_value('BOM', {'item': item}):
print(item, raw_materials)
make_bom(item = item, raw_materials = raw_materials)
def test_production_plan(self):
pln = create_production_plan(item_code='Test Production Item 1')
self.assertTrue(len(pln.mr_items), 2)
pln.make_material_request()
pln = frappe.get_doc('Production Plan', pln.name)
self.assertTrue(pln.status, 'Material Requested')
material_requests = frappe.get_all('Material Request Item', fields = ['distinct parent'],
filters = {'production_plan': pln.name}, as_list=1)
self.assertTrue(len(material_requests), 2)
pln.make_production_order()
production_orders = frappe.get_all('Production Order', fields = ['name'],
filters = {'production_plan': pln.name}, as_list=1)
self.assertTrue(len(production_orders), len(pln.po_items))
for name in material_requests:
mr = frappe.get_doc('Material Request', name[0])
mr.cancel()
for name in production_orders:
mr = frappe.delete_doc('Production Order', name[0])
pln = frappe.get_doc('Production Plan', pln.name)
pln.cancel()
def test_production_plan_for_existing_ordered_qty(self):
sr1 = create_stock_reconciliation(item_code="Raw Material Item 1",
target="_Test Warehouse - _TC", qty=1, rate=100)
sr2 = create_stock_reconciliation(item_code="Raw Material Item 2",
target="_Test Warehouse - _TC", qty=1, rate=100)
pln = create_production_plan(item_code='Test Production Item 1', ignore_existing_ordered_qty=0)
self.assertTrue(len(pln.mr_items), 1)
self.assertTrue(flt(pln.mr_items[0].quantity), 1.0)
sr1.cancel()
sr2.cancel()
pln.cancel()
def test_production_plan_with_non_stock_item(self):
pln = create_production_plan(item_code='Test Production Item 1', include_non_stock_items=0)
self.assertTrue(len(pln.mr_items), 3)
pln.cancel()
def test_production_plan_without_multi_level(self):
pln = create_production_plan(item_code='Test Production Item 1', use_multi_level_bom=0)
self.assertTrue(len(pln.mr_items), 2)
pln.cancel()
def test_production_plan_without_multi_level_for_existing_ordered_qty(self):
sr1 = create_stock_reconciliation(item_code="Raw Material Item 1",
target="_Test Warehouse - _TC", qty=1, rate=100)
sr2 = create_stock_reconciliation(item_code="Subassembly Item 1",
target="_Test Warehouse - _TC", qty=1, rate=100)
pln = create_production_plan(item_code='Test Production Item 1',
use_multi_level_bom=0, ignore_existing_ordered_qty=0)
self.assertTrue(len(pln.mr_items), 0)
sr1.cancel()
sr2.cancel()
pln.cancel()
def create_production_plan(**args):
args = frappe._dict(args)
pln = frappe.get_doc({
'doctype': 'Production Plan',
'company': args.company or '_Test Company',
'posting_date': nowdate(),
'include_non_stock_items': args.include_non_stock_items or 1,
'include_subcontracted_items': args.include_subcontracted_items or 1,
'ignore_existing_ordered_qty': args.ignore_existing_ordered_qty or 1,
'po_items': [{
'use_multi_level_bom': args.use_multi_level_bom or 1,
'item_code': args.item_code,
'bom_no': frappe.db.get_value('Item', args.item_code, 'default_bom'),
'planned_qty': args.planned_qty or 1,
'planned_start_date': args.planned_start_date or now_datetime()
}]
})
pln.get_items_for_material_requests()
if not args.do_not_save:
pln.insert()
if not args.do_not_submit:
pln.submit()
return pln
def make_bom(**args):
args = frappe._dict(args)
bom = frappe.get_doc({
'doctype': "BOM",
'is_default': 1,
'item': args.item,
'quantity': args.quantity or 1,
'company': args.company or '_Test Company'
})
for item in args.raw_materials:
item_doc = frappe.get_doc('Item', item)
bom.append('items', {
'item_code': item,
'qty': 1,
'uom': item_doc.stock_uom,
'stock_uom': item_doc.stock_uom,
'rate': item_doc.valuation_rate
})
bom.insert(ignore_permissions=True)
bom.submit()

View File

@ -1,5 +1,6 @@
{
"allow_copy": 0,
"allow_guest_to_view": 0,
"allow_import": 0,
"allow_rename": 0,
"autoname": "hash",
@ -11,16 +12,50 @@
"editable_grid": 1,
"fields": [
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 2,
"fieldname": "include_exploded_items",
"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": "Include Exploded Items",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 2,
"fieldname": "item_code",
"fieldtype": "Link",
"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": "Item Code",
"length": 0,
"no_copy": 0,
@ -32,6 +67,7 @@
"print_hide_if_no_value": 0,
"print_width": "150px",
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
@ -40,16 +76,20 @@
"width": "150px"
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 2,
"fieldname": "bom_no",
"fieldtype": "Link",
"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": "BOM No",
"length": 0,
"no_copy": 0,
@ -61,6 +101,7 @@
"print_hide_if_no_value": 0,
"print_width": "100px",
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
@ -69,16 +110,20 @@
"width": "100px"
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "planned_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": "Planned Qty",
"length": 0,
"no_copy": 0,
@ -89,6 +134,7 @@
"print_hide_if_no_value": 0,
"print_width": "100px",
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
@ -97,17 +143,21 @@
"width": "100px"
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"default": "",
"columns": 0,
"default": "Today",
"fieldname": "planned_start_date",
"fieldtype": "Datetime",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Planned Start Date",
"length": 0,
"no_copy": 0,
@ -116,6 +166,7 @@
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
@ -123,16 +174,20 @@
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "column_break_6",
"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,
@ -140,6 +195,7 @@
"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,
@ -147,9 +203,11 @@
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"description": "Reserved Warehouse in Sales Order / Finished Goods Warehouse",
"fieldname": "warehouse",
"fieldtype": "Link",
@ -157,7 +215,9 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Warehouse",
"length": 0,
"no_copy": 0,
@ -166,6 +226,7 @@
"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,
@ -173,16 +234,50 @@
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "produced_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_standard_filter": 0,
"label": "Produced Qty",
"length": 0,
"no_copy": 1,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "sales_order",
"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": "Sales Order",
"length": 0,
"no_copy": 0,
@ -193,6 +288,7 @@
"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,
@ -200,16 +296,50 @@
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "sales_order_item",
"fieldtype": "Data",
"hidden": 1,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Sales Order Item",
"length": 0,
"no_copy": 1,
"permlevel": 0,
"precision": "",
"print_hide": 1,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "material_request",
"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": "Material Request",
"length": 0,
"no_copy": 0,
@ -219,6 +349,7 @@
"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,
@ -226,16 +357,20 @@
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "pending_qty",
"fieldtype": "Float",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 1,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Pending Qty",
"length": 0,
"no_copy": 0,
@ -246,6 +381,7 @@
"print_hide_if_no_value": 0,
"print_width": "100px",
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
@ -254,16 +390,20 @@
"width": "100px"
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "stock_uom",
"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": "UOM",
"length": 0,
"no_copy": 0,
@ -275,6 +415,7 @@
"print_hide_if_no_value": 0,
"print_width": "80px",
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
@ -283,16 +424,20 @@
"width": "80px"
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "description",
"fieldtype": "Text Editor",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 1,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Description",
"length": 0,
"no_copy": 0,
@ -303,6 +448,7 @@
"print_hide_if_no_value": 0,
"print_width": "200px",
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
@ -311,16 +457,20 @@
"width": "200px"
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "material_request_item",
"fieldtype": "Data",
"hidden": 1,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "material_request_item",
"length": 0,
"no_copy": 0,
@ -329,6 +479,37 @@
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "ordered_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_standard_filter": 0,
"label": "Ordered Qty",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 1,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
@ -336,17 +517,17 @@
"unique": 0
}
],
"has_web_view": 0,
"hide_heading": 0,
"hide_toolbar": 0,
"idx": 1,
"image_view": 0,
"in_create": 0,
"in_dialog": 0,
"is_submittable": 0,
"issingle": 0,
"istable": 1,
"max_attachments": 0,
"modified": "2016-07-11 03:28:04.410743",
"modified": "2018-02-16 12:13:41.513586",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "Production Plan Item",
@ -355,6 +536,8 @@
"quick_entry": 0,
"read_only": 0,
"read_only_onload": 0,
"show_name_in_global_search": 0,
"sort_order": "ASC",
"track_changes": 0,
"track_seen": 0
}

View File

@ -1,5 +1,6 @@
{
"allow_copy": 0,
"allow_guest_to_view": 0,
"allow_import": 0,
"allow_rename": 0,
"autoname": "hash",
@ -12,16 +13,20 @@
"editable_grid": 1,
"fields": [
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "material_request",
"fieldtype": "Link",
"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": "Material Request",
"length": 0,
"no_copy": 0,
@ -34,24 +39,29 @@
"print_hide_if_no_value": 0,
"print_width": "150px",
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"unique": 0,
"width": "150px"
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "col_break1",
"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,
@ -59,6 +69,7 @@
"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,
@ -66,16 +77,20 @@
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "material_request_date",
"fieldtype": "Date",
"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": "Material Request Date",
"length": 0,
"no_copy": 0,
@ -88,6 +103,7 @@
"print_hide_if_no_value": 0,
"print_width": "120px",
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
@ -96,17 +112,17 @@
"width": "120px"
}
],
"has_web_view": 0,
"hide_heading": 0,
"hide_toolbar": 0,
"idx": 0,
"image_view": 0,
"in_create": 0,
"in_dialog": 0,
"is_submittable": 0,
"issingle": 0,
"istable": 1,
"max_attachments": 0,
"modified": "2016-07-11 03:28:04.519523",
"modified": "2017-10-29 12:31:57.986869",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "Production Plan Material Request",
@ -116,7 +132,9 @@
"quick_entry": 0,
"read_only": 0,
"read_only_onload": 0,
"show_name_in_global_search": 0,
"sort_field": "modified",
"sort_order": "ASC",
"track_changes": 0,
"track_seen": 0
}

View File

@ -1,5 +1,6 @@
{
"allow_copy": 0,
"allow_guest_to_view": 0,
"allow_import": 0,
"allow_rename": 0,
"autoname": "hash",
@ -11,16 +12,20 @@
"editable_grid": 1,
"fields": [
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "sales_order",
"fieldtype": "Link",
"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": "Sales Order",
"length": 0,
"no_copy": 0,
@ -32,24 +37,29 @@
"print_hide_if_no_value": 0,
"print_width": "150px",
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"unique": 0,
"width": "150px"
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "sales_order_date",
"fieldtype": "Date",
"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": "Sales Order Date",
"length": 0,
"no_copy": 0,
@ -61,6 +71,7 @@
"print_hide_if_no_value": 0,
"print_width": "120px",
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
@ -69,16 +80,20 @@
"width": "120px"
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "col_break1",
"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,
@ -86,6 +101,7 @@
"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,
@ -93,16 +109,20 @@
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "customer",
"fieldtype": "Link",
"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": "Customer",
"length": 0,
"no_copy": 0,
@ -112,6 +132,7 @@
"print_hide_if_no_value": 0,
"print_width": "150px",
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
@ -120,9 +141,11 @@
"width": "150px"
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "",
"fieldname": "grand_total",
"fieldtype": "Currency",
@ -130,7 +153,9 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Grand Total",
"length": 0,
"no_copy": 0,
@ -140,6 +165,7 @@
"print_hide_if_no_value": 0,
"print_width": "120px",
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
@ -148,17 +174,17 @@
"width": "120px"
}
],
"has_web_view": 0,
"hide_heading": 0,
"hide_toolbar": 0,
"idx": 1,
"image_view": 0,
"in_create": 0,
"in_dialog": 0,
"is_submittable": 0,
"issingle": 0,
"istable": 1,
"max_attachments": 0,
"modified": "2016-07-11 03:28:04.587544",
"modified": "2017-10-29 12:29:36.726079",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "Production Plan Sales Order",
@ -167,6 +193,8 @@
"quick_entry": 0,
"read_only": 0,
"read_only_onload": 0,
"show_name_in_global_search": 0,
"sort_order": "ASC",
"track_changes": 0,
"track_seen": 0
}

View File

@ -1 +0,0 @@
Tool to create Production Orders from a bunch of Sales Orders and generate Material Requests (MRP) as required.

View File

@ -1 +0,0 @@
from __future__ import unicode_literals

View File

@ -1,128 +0,0 @@
// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
// License: GNU General Public License v3. See license.txt
cur_frm.cscript.onload = function(doc) {
cur_frm.set_value("company", frappe.defaults.get_user_default("Company"))
}
cur_frm.cscript.refresh = function(doc) {
cur_frm.disable_save();
}
cur_frm.add_fetch("material_request", "transaction_date", "material_request_date");
cur_frm.add_fetch("sales_order", "transaction_date", "sales_order_date");
cur_frm.add_fetch("sales_order", "customer", "customer");
cur_frm.add_fetch("sales_order", "base_grand_total", "grand_total");
frappe.ui.form.on("Production Planning Tool", {
onload_post_render: function(frm) {
frm.get_field("items").grid.set_multiple_add("item_code", "planned_qty");
},
get_sales_orders: function(frm) {
frappe.call({
doc: frm.doc,
method: "get_open_sales_orders",
callback: function(r) {
refresh_field("sales_orders");
}
});
},
get_material_request: function(frm) {
frappe.call({
doc: frm.doc,
method: "get_pending_material_requests",
callback: function(r) {
refresh_field("material_requests");
}
});
},
get_items: function(frm) {
frappe.call({
doc: frm.doc,
method: "get_items",
callback: function(r) {
refresh_field("items");
}
});
},
create_production_order: function(frm) {
frappe.call({
doc: frm.doc,
method: "raise_production_orders"
});
},
create_material_requests: function(frm) {
frappe.call({
doc: frm.doc,
method: "raise_material_requests"
});
}
});
cur_frm.cscript.item_code = function(doc,cdt,cdn) {
var d = locals[cdt][cdn];
if (d.item_code) {
frappe.call({
method: "erpnext.manufacturing.doctype.production_order.production_order.get_item_details",
args: {
"item" : d.item_code
},
callback: function(r) {
$.extend(d, r.message);
refresh_field("items");
}
});
}
}
cur_frm.cscript.download_materials_required = function(doc, cdt, cdn) {
return $c_obj(doc, 'validate_data', '', function(r, rt) {
if (!r['exc'])
$c_obj_csv(doc, 'download_raw_materials', '', '');
});
}
cur_frm.fields_dict['sales_orders'].grid.get_field('sales_order').get_query = function(doc) {
var args = { "docstatus": 1 };
if(doc.customer) {
args["customer"] = doc.customer;
}
return { filters: args }
}
cur_frm.fields_dict['items'].grid.get_field('item_code').get_query = function(doc) {
return erpnext.queries.item({
'is_stock_item': 1
});
}
cur_frm.fields_dict['items'].grid.get_field('bom_no').get_query = function(doc, cdt, cdn) {
var d = locals[cdt][cdn];
if (d.item_code) {
return {
query: "erpnext.controllers.queries.bom",
filters:{'item': cstr(d.item_code)}
}
} else frappe.msgprint(__("Please enter Item first"));
}
cur_frm.fields_dict.customer.get_query = function(doc,cdt,cdn) {
return{
query: "erpnext.controllers.queries.customer_query"
}
}
cur_frm.fields_dict.sales_orders.grid.get_field("customer").get_query =
cur_frm.fields_dict.customer.get_query;
cur_frm.cscript.planned_start_date = function(doc, cdt, cdn) {
erpnext.utils.copy_value_in_all_row(doc, cdt, cdn, "items", "planned_start_date");
}

View File

@ -1,388 +0,0 @@
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# MIT License. See license.txt
import frappe
import frappe.defaults
import unittest
from frappe.test_runner import make_test_records
from erpnext.manufacturing.doctype.production_planning_tool.production_planning_tool import ProductionPlanningTool
# load test records and dependencies
test_records = frappe.get_test_records('Production Planning Tool')
test_dependencies = ["Item","BOM"]
class TestEvent(unittest.TestCase):
def test_materials_requests_all_raw_multi_level(self):
items = ["_Test PPT Item Raw A","_Test PPT Item Raw B","_Test PPT Item Raw C","_Test PPT Item Raw D",
"_Test PPT Item Sub A","_Test PPT Item Sub B","_Test PPT Item Sub C","_Test PPT Item SC A",
"_Test PPT Item SC B","_Test PPT Item Master"]
quantities = [14,9,36,1,0,0,0,0,0,0]
types = ["Purchase","Purchase","Purchase","Purchase","Manufacture","Manufacture","Manufacture","Purchase",
"Purchase","Manufacture"]
self.runtest_materials_requests(items, quantities, types, use_multi_level_bom=1, only_raw_materials=1, \
include_subcontracted=1)
def test_materials_requests_multi_no_subcontracted(self):
items = ["_Test PPT Item Raw A","_Test PPT Item Raw B","_Test PPT Item Raw C","_Test PPT Item Raw D",
"_Test PPT Item Sub A","_Test PPT Item Sub B","_Test PPT Item Sub C","_Test PPT Item SC A",
"_Test PPT Item SC B","_Test PPT Item Master"]
quantities = [14,5,20,0,0,0,0,0,0,0]
types = ["Purchase","Purchase","Purchase","Purchase","Manufacture","Manufacture","Manufacture","Purchase",
"Purchase","Manufacture"]
# This one should fail for now
self.runtest_materials_requests(items, quantities, types, use_multi_level_bom=1, only_raw_materials=1, \
include_subcontracted=0)
def test_materials_requests_manufacture_and_sub_multi_level(self):
items = ["_Test PPT Item Raw A","_Test PPT Item Raw B","_Test PPT Item Raw C","_Test PPT Item Raw D",
"_Test PPT Item Sub A","_Test PPT Item Sub B","_Test PPT Item Sub C","_Test PPT Item SC A",
"_Test PPT Item SC B","_Test PPT Item Master"]
quantities = [14,9,36,1,2,5,2,1,4,0]
types = ["Purchase","Purchase","Purchase","Purchase","Manufacture","Manufacture","Manufacture","Purchase",
"Purchase","Manufacture"]
self.runtest_materials_requests(items, quantities, types, use_multi_level_bom=1, only_raw_materials=0, \
include_subcontracted=1)
def test_materials_requests_manufacture_multi_level(self):
items = ["_Test PPT Item Raw A","_Test PPT Item Raw B","_Test PPT Item Raw C","_Test PPT Item Raw D",
"_Test PPT Item Sub A","_Test PPT Item Sub B","_Test PPT Item Sub C","_Test PPT Item SC A",
"_Test PPT Item SC B","_Test PPT Item Master"]
quantities = [14,5,20,0,2,5,2,1,4,0]
types = ["Purchase","Purchase","Purchase","Purchase","Manufacture","Manufacture","Manufacture","Purchase",
"Purchase","Manufacture"]
self.runtest_materials_requests(items, quantities, types, use_multi_level_bom=1, only_raw_materials=0, \
include_subcontracted=0)
def test_materials_requests_single_level_purch_only(self):
items = ["_Test PPT Item Raw A","_Test PPT Item Raw B","_Test PPT Item Raw C","_Test PPT Item Raw D",
"_Test PPT Item Sub A","_Test PPT Item Sub B","_Test PPT Item Sub C","_Test PPT Item SC A",
"_Test PPT Item SC B","_Test PPT Item Master"]
quantities = [2,0,0,0,0,0,0,1,0,0]
types = ["Purchase","Purchase","Purchase","Purchase","Manufacture","Manufacture","Manufacture","Purchase",
"Purchase","Manufacture"]
self.runtest_materials_requests(items, quantities, types, use_multi_level_bom=0, only_raw_materials=1, \
include_subcontracted=0)
def test_materials_requests_single_level(self):
items = ["_Test PPT Item Raw A","_Test PPT Item Raw B","_Test PPT Item Raw C","_Test PPT Item Raw D",
"_Test PPT Item Sub A","_Test PPT Item Sub B","_Test PPT Item Sub C","_Test PPT Item SC A",
"_Test PPT Item SC B","_Test PPT Item Master"]
quantities = [2,0,0,0,2,1,0,1,0,0]
types = ["Purchase","Purchase","Purchase","Purchase","Manufacture","Manufacture","Manufacture","Purchase",
"Purchase","Manufacture"]
self.runtest_materials_requests(items, quantities, types, use_multi_level_bom=0, only_raw_materials=0, \
include_subcontracted=0)
def runtest_materials_requests(self, items, quantities, types,use_multi_level_bom, only_raw_materials, \
include_subcontracted):
clear_material_requests()
create_test_records()
ppt = run_production_planning_tool(use_multi_level_bom=use_multi_level_bom,
only_raw_materials=only_raw_materials, include_subcontracted=include_subcontracted,
item_code = "_Test PPT Item Master", bom_no = "BOM-_Test PPT Item Master-001",
planned_qty = 1, planned_start_date = "5/5/2029",
warehouse = "_Test Warehouse - _TC", company = "_Test Company")
create_material_requests(ppt)
for item, qty, type in zip(items, quantities, types):
self.assertEqual(qty, get_requested_qty(item))
for mat_req_type in get_requested_types(item):
self.assertEqual(type, mat_req_type)
def create_test_records():
from erpnext.stock.doctype.item.test_item import make_item
subA = make_item("_Test PPT Item Sub A",{
"item_code": "_Test PPT Item Sub A",
"item_name": "_Test PPT Item Sub A",
"description": "A manufactured _Test PPT Item Sub Assembly",
"default_material_request_type": "Manufacture",
"is_sub_contracted_item": 0,
"is_stock_item": 1,
"stock_uom": "_Test UOM",
"item_group": "_Test Item Group",
"default_warehouse": "_Test Warehouse - _TC"})
subB = make_item("_Test PPT Item Sub B",{
"item_code": "_Test PPT Item Sub B",
"item_name": "_Test PPT Item Sub B",
"description": "A manufactured _Test PPT Item Sub Assembly",
"default_material_request_type": "Manufacture",
"is_sub_contracted_item": 0,
"is_stock_item": 1,
"stock_uom": "_Test UOM",
"item_group": "_Test Item Group",
"default_warehouse": "_Test Warehouse - _TC"})
subC = make_item("_Test PPT Item Sub C",{
"item_code": "_Test PPT Item Sub C",
"item_name": "_Test PPT Item Sub C",
"description": "A manufactured _Test PPT Item Sub Assembly",
"default_material_request_type": "Manufacture",
"is_sub_contracted_item": 0,
"is_stock_item": 1,
"stock_uom": "_Test UOM",
"item_group": "_Test Item Group",
"default_warehouse": "_Test Warehouse - _TC"})
sCA = make_item("_Test PPT Item SC A",{
"item_code": "_Test PPT Item SC A",
"item_name": "_Test PPT Item SC A",
"description": "A subcontracted part with raw materials",
"default_material_request_type": "Purchase",
"is_sub_contracted_item": 1,
"is_stock_item": 1,
"stock_uom": "_Test UOM",
"item_group": "_Test Item Group",
"default_warehouse": "_Test Warehouse - _TC"})
subA = make_item("_Test PPT Item Sub A",{
"item_code": "_Test PPT Item Sub A",
"item_name": "_Test PPT Item Sub A",
"description": "A manufactured _Test PPT Item Sub Assembly",
"default_material_request_type": "Manufacture",
"is_sub_contracted_item": 0,
"is_stock_item": 1,
"stock_uom": "_Test UOM",
"item_group": "_Test Item Group",
"default_warehouse": "_Test Warehouse - _TC"})
sCB = make_item("_Test PPT Item SC B",{
"item_code": "_Test PPT Item SC B",
"item_name": "_Test PPT Item SC B",
"description": "A subcontracted part with raw materials",
"default_material_request_type": "Purchase",
"is_sub_contracted_item": 1,
"is_stock_item": 1,
"stock_uom": "_Test UOM",
"item_group": "_Test Item Group",
"default_warehouse": "_Test Warehouse - _TC"})
rawA = make_item("_Test PPT Item Raw A",{
"item_code": "_Test PPT Item Raw A",
"item_name": "_Test PPT Item Raw A",
"description": "A raw material",
"default_material_request_type": "Purchase",
"is_sub_contracted_item": 0,
"is_stock_item": 1,
"stock_uom": "_Test UOM",
"item_group": "_Test Item Group",
"default_warehouse": "_Test Warehouse - _TC"})
rawB = make_item("_Test PPT Item Raw B",{
"item_code": "_Test PPT Item Raw B",
"item_name": "_Test PPT Item Raw B",
"description": "A raw material",
"default_material_request_type": "Purchase",
"is_sub_contracted_item": 0,
"is_stock_item": 1,
"stock_uom": "_Test UOM",
"item_group": "_Test Item Group",
"default_warehouse": "_Test Warehouse - _TC"})
rawC = make_item("_Test PPT Item Raw C",{
"item_code": "_Test PPT Item Raw C",
"item_name": "_Test PPT Item Raw C",
"description": "A raw material",
"default_material_request_type": "Purchase",
"is_sub_contracted_item": 0,
"is_stock_item": 1,
"stock_uom": "_Test UOM",
"item_group": "_Test Item Group",
"default_warehouse": "_Test Warehouse - _TC"})
rawD = make_item("_Test PPT Item Raw D",{
"item_code": "_Test PPT Item Raw D",
"item_name": "_Test PPT Item Raw D",
"description": "A raw material",
"default_material_request_type": "Purchase",
"is_sub_contracted_item": 0,
"is_stock_item": 1,
"stock_uom": "_Test UOM",
"item_group": "_Test Item Group",
"default_warehouse": "_Test Warehouse - _TC"})
master = make_item("_Test PPT Item Master",{
"item_code": "_Test PPT Item Master",
"item_name": "_Test PPT Item Master",
"description": "The final assembly",
"default_material_request_type": "Manufacture",
"is_sub_contracted_item": 0,
"is_stock_item": 1,
"stock_uom": "_Test UOM",
"item_group": "_Test Item Group",
"default_warehouse": "_Test Warehouse - _TC"})
bom_subB = make_bom("BOM-_Test PPT Item Sub B-001",{"quantity":1.0,
"item": "_Test PPT Item Sub B",
"is_active": 1,
"is_default": 1,
"docstatus": 1,
"with_operations": 0}, [{"item_code": "_Test PPT Item Raw B", "doctype":"BOM Item", "stock_qty":1,
"rate":100, "amount": 100, "stock_uom": "_Test UOM"},
{"item_code": "_Test PPT Item Raw C", "doctype":"BOM Item", "stock_qty":4, "rate":100,
"amount": 400,"stock_uom": "_Test UOM"}])
bom_subC = make_bom("BOM-_Test PPT Item Sub C-001",{"quantity":1,
"item": "_Test PPT Item Sub C",
"is_active": 1,
"is_default": 1,
"docstatus": 1,
"with_operations": 0}, [
{"item_code": "_Test PPT Item Raw A","item_name": "_Test PPT Item Raw A",
"doctype":"BOM Item", "stock_qty":6, "rate":100, "amount": 600},
{"item_code": "_Test PPT Item Sub B","item_name": "_Test PPT Item Sub B",
"bom_no":"BOM-_Test PPT Item Sub B-001", "doctype":"BOM Item", "stock_qty":2,
"rate":100, "amount": 200}])
bom_sCA = make_bom("BOM-_Test PPT Item SC A-001",{"quantity":1,
"item": "_Test PPT Item SC A",
"is_active": 1,
"is_default": 1,
"docstatus": 1,
"with_operations": 0}, [
{"item_code": "_Test PPT Item Raw D","item_name": "_Test PPT Item Raw D",
"doctype":"BOM Item", "stock_qty":1, "rate":100, "amount": 100}])
bom_sCB = make_bom("BOM-_Test PPT Item SC B-001",{"quantity":1,
"item": "_Test PPT Item SC B",
"is_active": 1,
"is_default": 1,
"docstatus": 1,
"with_operations": 0}, [
{"item_code": "_Test PPT Item Raw B","item_name": "_Test PPT Item Raw B",
"doctype":"BOM Item", "stock_qty":1, "rate":100, "amount": 100},
{"item_code": "_Test PPT Item Raw C","item_name": "_Test PPT Item Raw C",
"doctype":"BOM Item", "stock_qty":4, "rate":100, "amount": 400}])
bom_subA = make_bom("BOM-_Test PPT Item Sub A-001",{"quantity":1,
"item": "_Test PPT Item Sub A",
"is_active": 1,
"is_default": 1,
"docstatus": 1,
"with_operations": 0}, [
{"item_code": "_Test PPT Item Sub C","item_name": "_Test PPT Item Sub C",
"bom_no":"BOM-_Test PPT Item Sub C-001", "doctype":"BOM Item",
"stock_qty":1, "rate":100, "amount": 100},
{"item_code": "_Test PPT Item SC B","item_name": "_Test PPT Item SC B",
"bom_no":"BOM-_Test PPT Item SC B-001", "doctype":"BOM Item", "stock_qty":2,
"rate":100, "amount": 200}])
bom_master = make_bom("BOM-_Test PPT Item Master-001",{"quantity":1,
"item": "_Test PPT Item Master",
"is_active": 1,
"is_default": 1,
"docstatus": 1,
"with_operations": 0}, [
{"item_code": "_Test PPT Item Sub A","item_name": "_Test PPT Item Sub A",
"bom_no":"BOM-_Test PPT Item Sub A-001",
"doctype":"BOM Item", "stock_qty":2, "rate":100, "amount": 200},
{"item_code": "_Test PPT Item Sub B","item_name": "_Test PPT Item Sub B",
"bom_no":"BOM-_Test PPT Item Sub B-001",
"doctype":"BOM Item", "stock_qty":1, "rate":100, "amount": 100},
{"item_code": "_Test PPT Item Raw A","item_name": "_Test PPT Item Raw A",
"doctype":"BOM Item", "stock_qty":2, "rate":100,
"amount": 200},
{"item_code": "_Test PPT Item SC A","item_name": "_Test PPT Item SC A",
"bom_no":"BOM-_Test PPT Item SC A-001",
"doctype":"BOM Item", "stock_qty":1, "rate":100, "amount": 100}
])
def make_bom(name, properties=None, items=None):
if frappe.db.exists("BOM", name):
return frappe.get_doc("BOM", name)
bom = frappe.new_doc("BOM")
item = frappe.get_doc({
"doctype": "BOM",
"name": name,
"quantity": "1",
"with_operations": 0
})
if properties:
bom.update(properties)
if items:
for item in items:
bom.append("items", item)
bom.insert()
bom.submit()
return bom
def clear_material_requests():
frappe.db.sql("delete from `tabMaterial Request Item`")
frappe.db.sql("delete from `tabMaterial Request`")
def run_production_planning_tool(**args):
ppt = frappe.new_doc("Production Planning Tool")
args = frappe._dict(args)
if args.use_multi_level_bom:
ppt.use_multi_level_bom = args.use_multi_level_bom
else:
ppt.use_multi_level_bom = 0
if args.only_raw_materials:
ppt.only_raw_materials = args.only_raw_materials
else:
ppt.only_raw_materials = 0
if args.include_subcontracted:
ppt.include_subcontracted = args.include_subcontracted
else:
ppt.include_subcontracted = 0
if args.warehouse:
ppt.purchase_request_for_warehouse = args.warehouse
if args.company:
ppt.company = args.company
ppt.create_material_requests_for_all_required_qty = 1
ppt.append("items",{"item_code": args.item_code, "bom_no": args.bom_no, "planned_qty": args.planned_qty,
"planned_start_date": args.planned_start_date, "warehouse": args.warehouse})
return ppt
def create_material_requests(ppt):
ppt.raise_material_requests()
def get_requested_qty(item_code):
total_qty = 0
for d in frappe.db.sql("""select item.qty as qty
from `tabMaterial Request` mat_req, `tabMaterial Request Item` item
where item.item_code = %(item_code)s and item.parent = mat_req.name""", {"item_code":item_code}, as_dict=1):
total_qty += d.qty
return total_qty
def get_requested_types(item_code):
types = []
for d in frappe.db.sql("""select mat_req.material_request_type as type
from `tabMaterial Request` mat_req, `tabMaterial Request Item` item
where item.item_code = %(item_code)s and item.parent = mat_req.name""", {"item_code":item_code}, as_dict=1):
types.append(d.type)
return types

View File

@ -499,4 +499,5 @@ erpnext.patches.v10_0.set_currency_in_pricing_rule
erpnext.patches.v10_0.workflow_expense_claim
erpnext.patches.v10_0.set_b2c_limit
erpnext.patches.v10_0.update_translatable_fields
erpnext.patches.v10_0.rename_offer_letter_to_job_offer
erpnext.patches.v10_0.rename_offer_letter_to_job_offer
execute:frappe.delete_doc('DocType', 'Production Planning Tool', ignore_missing=True)

View File

@ -234,10 +234,6 @@ class Company(Document):
frappe.db.set(self, "round_off_cost_center", _("Main") + " - " + self.abbr)
frappe.db.set(self, "depreciation_cost_center", _("Main") + " - " + self.abbr)
def before_rename(self, olddn, newdn, merge=False):
if merge:
frappe.throw(_("Sorry, companies cannot be merged"))
def after_rename(self, olddn, newdn, merge=False):
frappe.db.set(self, "company_name", newdn)

View File

@ -296,7 +296,7 @@ def make_item_variant():
test_records = frappe.get_test_records('Item')
def create_item(item_code, is_stock_item=None):
def create_item(item_code, is_stock_item=None, valuation_rate=0, warehouse=None):
if not frappe.db.exists("Item", item_code):
item = frappe.new_doc("Item")
item.item_code = item_code
@ -304,4 +304,6 @@ def create_item(item_code, is_stock_item=None):
item.description = item_code
item.item_group = "All Item Groups"
item.is_stock_item = is_stock_item or 1
item.valuation_rate = valuation_rate or 0.0
item.default_warehouse = warehouse or '_Test Warehouse - _TC'
item.save()

View File

@ -702,6 +702,36 @@
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 1,
"columns": 0,
"fieldname": "reference",
"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": "Reference",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
}
],
"has_web_view": 0,
@ -716,7 +746,7 @@
"istable": 0,
"max_attachments": 0,
"menu_index": 0,
"modified": "2017-10-05 18:24:17.148782",
"modified": "2017-12-03 18:45:10.293916",
"modified_by": "Administrator",
"module": "Stock",
"name": "Material Request",

View File

@ -92,6 +92,7 @@ class MaterialRequest(BuyingController):
def on_submit(self):
# frappe.db.set(self, 'status', 'Submitted')
self.update_requested_qty()
self.update_requested_qty_in_production_plan()
def before_save(self):
self.set_status(update=True)
@ -144,6 +145,7 @@ class MaterialRequest(BuyingController):
def on_cancel(self):
self.update_requested_qty()
self.update_requested_qty_in_production_plan()
def update_completed_qty(self, mr_items=None, update_modified=True):
if self.material_request_type == "Purchase":
@ -195,6 +197,22 @@ class MaterialRequest(BuyingController):
"indented_qty": get_indented_qty(item_code, warehouse)
})
def update_requested_qty_in_production_plan(self):
production_plans = []
for d in self.get('items'):
if d.production_plan and d.material_request_plan_item:
qty = d.qty if self.docstatus == 1 else 0
frappe.db.set_value('Material Request Plan Item',
d.material_request_plan_item, 'requested_qty', qty)
if d.production_plan not in production_plans:
production_plans.append(d.production_plan)
for production_plan in production_plans:
doc = frappe.get_doc('Production Plan', production_plan)
doc.set_status()
doc.db_set('status', doc.status)
def update_completed_and_requested_qty(stock_entry, method):
if stock_entry.doctype == "Stock Entry":
material_request_map = {}

View File

@ -701,6 +701,67 @@
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "production_plan",
"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": "Production Plan",
"length": 0,
"no_copy": 1,
"options": "Production Plan",
"permlevel": 0,
"precision": "",
"print_hide": 1,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "material_request_plan_item",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Material Request Plan Item",
"length": 0,
"no_copy": 1,
"permlevel": 0,
"precision": "",
"print_hide": 1,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
@ -898,7 +959,7 @@
"issingle": 0,
"istable": 1,
"max_attachments": 0,
"modified": "2017-12-15 16:29:18.902085",
"modified": "2017-12-20 16:29:18.902085",
"modified_by": "nabinhait@gmail.com",
"module": "Stock",
"name": "Material Request Item",