feat: provision to make subcontracted purchase order from the production plan
This commit is contained in:
parent
c1853ddf73
commit
61690775a8
@ -97,6 +97,9 @@
|
|||||||
"is_fixed_asset",
|
"is_fixed_asset",
|
||||||
"item_tax_rate",
|
"item_tax_rate",
|
||||||
"section_break_72",
|
"section_break_72",
|
||||||
|
"production_plan",
|
||||||
|
"production_plan_item",
|
||||||
|
"production_plan_sub_assembly_item",
|
||||||
"page_break"
|
"page_break"
|
||||||
],
|
],
|
||||||
"fields": [
|
"fields": [
|
||||||
@ -803,13 +806,37 @@
|
|||||||
"options": "Company:company:default_currency",
|
"options": "Company:company:default_currency",
|
||||||
"print_hide": 1,
|
"print_hide": 1,
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "production_plan",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Production Plan",
|
||||||
|
"options": "Production Plan",
|
||||||
|
"print_hide": 1,
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "production_plan_item",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"hidden": 1,
|
||||||
|
"label": "Production Plan Item",
|
||||||
|
"no_copy": 1,
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "production_plan_sub_assembly_item",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"hidden": 1,
|
||||||
|
"label": "Production Plan Sub Assembly Item",
|
||||||
|
"no_copy": 1,
|
||||||
|
"read_only": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"idx": 1,
|
"idx": 1,
|
||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2021-03-22 11:46:12.357435",
|
"modified": "2021-06-28 19:22:22.715365",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Buying",
|
"module": "Buying",
|
||||||
"name": "Purchase Order Item",
|
"name": "Purchase Order Item",
|
||||||
|
@ -36,6 +36,9 @@
|
|||||||
"materials_section",
|
"materials_section",
|
||||||
"inspection_required",
|
"inspection_required",
|
||||||
"quality_inspection_template",
|
"quality_inspection_template",
|
||||||
|
"column_break_31",
|
||||||
|
"bom_level",
|
||||||
|
"section_break_33",
|
||||||
"items",
|
"items",
|
||||||
"scrap_section",
|
"scrap_section",
|
||||||
"scrap_items",
|
"scrap_items",
|
||||||
@ -513,6 +516,22 @@
|
|||||||
"no_copy": 1,
|
"no_copy": 1,
|
||||||
"print_hide": 1,
|
"print_hide": 1,
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_31",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"fieldname": "bom_level",
|
||||||
|
"fieldtype": "Int",
|
||||||
|
"label": "BOM Level",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "section_break_33",
|
||||||
|
"fieldtype": "Section Break",
|
||||||
|
"hide_border": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"icon": "fa fa-sitemap",
|
"icon": "fa fa-sitemap",
|
||||||
@ -520,7 +539,7 @@
|
|||||||
"image_field": "image",
|
"image_field": "image",
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2021-03-16 12:25:09.081968",
|
"modified": "2021-05-16 12:25:09.081968",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Manufacturing",
|
"module": "Manufacturing",
|
||||||
"name": "BOM",
|
"name": "BOM",
|
||||||
|
@ -154,6 +154,7 @@ class BOM(WebsiteGenerator):
|
|||||||
self.calculate_cost()
|
self.calculate_cost()
|
||||||
self.update_stock_qty()
|
self.update_stock_qty()
|
||||||
self.update_cost(update_parent=False, from_child_bom=True, update_hour_rate = False, save=False)
|
self.update_cost(update_parent=False, from_child_bom=True, update_hour_rate = False, save=False)
|
||||||
|
self.set_bom_level()
|
||||||
|
|
||||||
def get_context(self, context):
|
def get_context(self, context):
|
||||||
context.parents = [{'name': 'boms', 'title': _('All BOMs') }]
|
context.parents = [{'name': 'boms', 'title': _('All BOMs') }]
|
||||||
@ -676,6 +677,19 @@ class BOM(WebsiteGenerator):
|
|||||||
"""Get a complete tree representation preserving order of child items."""
|
"""Get a complete tree representation preserving order of child items."""
|
||||||
return BOMTree(self.name)
|
return BOMTree(self.name)
|
||||||
|
|
||||||
|
def set_bom_level(self, update=False):
|
||||||
|
levels = []
|
||||||
|
|
||||||
|
self.bom_level = 0
|
||||||
|
for row in self.items:
|
||||||
|
if row.bom_no:
|
||||||
|
levels.append(frappe.get_cached_value("BOM", row.bom_no, "bom_level") or 0)
|
||||||
|
|
||||||
|
if levels:
|
||||||
|
self.bom_level = max(levels) + 1
|
||||||
|
|
||||||
|
if update:
|
||||||
|
self.db_set("bom_level", self.bom_level)
|
||||||
|
|
||||||
def get_bom_item_rate(args, bom_doc):
|
def get_bom_item_rate(args, bom_doc):
|
||||||
if bom_doc.rm_cost_as_per == 'Valuation Rate':
|
if bom_doc.rm_cost_as_per == 'Valuation Rate':
|
||||||
@ -860,7 +874,7 @@ def get_children(doctype, parent=None, is_root=False, **filters):
|
|||||||
frappe.form_dict.parent = parent
|
frappe.form_dict.parent = parent
|
||||||
|
|
||||||
if frappe.form_dict.parent:
|
if frappe.form_dict.parent:
|
||||||
bom_doc = frappe.get_doc("BOM", frappe.form_dict.parent)
|
bom_doc = frappe.get_cached_doc("BOM", frappe.form_dict.parent)
|
||||||
frappe.has_permission("BOM", doc=bom_doc, throw=True)
|
frappe.has_permission("BOM", doc=bom_doc, throw=True)
|
||||||
|
|
||||||
bom_items = frappe.get_all('BOM Item',
|
bom_items = frappe.get_all('BOM Item',
|
||||||
@ -871,7 +885,7 @@ def get_children(doctype, parent=None, is_root=False, **filters):
|
|||||||
item_names = tuple(d.get('item_code') for d in bom_items)
|
item_names = tuple(d.get('item_code') for d in bom_items)
|
||||||
|
|
||||||
items = frappe.get_list('Item',
|
items = frappe.get_list('Item',
|
||||||
fields=['image', 'description', 'name', 'stock_uom', 'item_name'],
|
fields=['image', 'description', 'name', 'stock_uom', 'item_name', 'is_sub_contracted_item'],
|
||||||
filters=[['name', 'in', item_names]]) # to get only required item dicts
|
filters=[['name', 'in', item_names]]) # to get only required item dicts
|
||||||
|
|
||||||
for bom_item in bom_items:
|
for bom_item in bom_items:
|
||||||
@ -884,6 +898,7 @@ def get_children(doctype, parent=None, is_root=False, **filters):
|
|||||||
|
|
||||||
bom_item.parent_bom_qty = bom_doc.quantity
|
bom_item.parent_bom_qty = bom_doc.quantity
|
||||||
bom_item.expandable = 0 if bom_item.value in ('', None) else 1
|
bom_item.expandable = 0 if bom_item.value in ('', None) else 1
|
||||||
|
bom_item.image = frappe.db.escape(bom_item.image)
|
||||||
|
|
||||||
return bom_items
|
return bom_items
|
||||||
|
|
||||||
|
@ -1,13 +1,31 @@
|
|||||||
<div style="padding: 15px;">
|
<div style="padding: 15px;">
|
||||||
{% if data.image %}
|
<div class="row mb-5">
|
||||||
<img class="responsive" src={{ data.image }}>
|
<div class="col-md-5" style="max-height: 500px">
|
||||||
<hr style="margin: 15px -15px;">
|
{% if data.image %}
|
||||||
{% endif %}
|
<div class="border image-field " style="overflow: hidden;border-color:#e6e6e6">
|
||||||
<h4>
|
<img class="responsive" src={{ data.image }}>
|
||||||
{{ __("Description") }}
|
</div>
|
||||||
</h4>
|
{% endif %}
|
||||||
<div style="padding-top: 10px;">
|
</div>
|
||||||
{{ data.description }}
|
<div class="col-md-7 h-500">
|
||||||
|
<h4>
|
||||||
|
{{ __("Description") }}
|
||||||
|
</h4>
|
||||||
|
<div style="padding-top: 10px;">
|
||||||
|
{{ data.description }}
|
||||||
|
</div>
|
||||||
|
<hr style="margin: 15px -15px;">
|
||||||
|
<p>
|
||||||
|
{% if data.value %}
|
||||||
|
<a style="margin-right: 7px; margin-bottom: 7px" class="btn btn-default btn-xs" href="#Form/BOM/{{ data.value }}">
|
||||||
|
{{ __("Open BOM {0}", [data.value.bold()]) }}</a>
|
||||||
|
{% endif %}
|
||||||
|
{% if data.item_code %}
|
||||||
|
<a class="btn btn-default btn-xs" href="#Form/Item/{{ data.item_code }}">
|
||||||
|
{{ __("Open Item {0}", [data.item_code.bold()]) }}</a>
|
||||||
|
{% endif %}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<hr style="margin: 15px -15px;">
|
<hr style="margin: 15px -15px;">
|
||||||
<p>
|
<p>
|
||||||
|
@ -64,7 +64,7 @@ frappe.treeview_settings["BOM"] = {
|
|||||||
if(node.is_root && node.data.value!="BOM") {
|
if(node.is_root && node.data.value!="BOM") {
|
||||||
frappe.model.with_doc("BOM", node.data.value, function() {
|
frappe.model.with_doc("BOM", node.data.value, function() {
|
||||||
var bom = frappe.model.get_doc("BOM", node.data.value);
|
var bom = frappe.model.get_doc("BOM", node.data.value);
|
||||||
node.data.image = bom.image || "";
|
node.data.image = escape(bom.image) || "";
|
||||||
node.data.description = bom.description || "";
|
node.data.description = bom.description || "";
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
frappe.ui.form.on('Production Plan', {
|
frappe.ui.form.on('Production Plan', {
|
||||||
setup: function(frm) {
|
setup: function(frm) {
|
||||||
frm.custom_make_buttons = {
|
frm.custom_make_buttons = {
|
||||||
'Work Order': 'Work Order',
|
'Work Order': 'Work Order / Subcontract PO',
|
||||||
'Material Request': 'Material Request',
|
'Material Request': 'Material Request',
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -68,17 +68,13 @@ frappe.ui.form.on('Production Plan', {
|
|||||||
frm.trigger("show_progress");
|
frm.trigger("show_progress");
|
||||||
|
|
||||||
if (frm.doc.status !== "Completed") {
|
if (frm.doc.status !== "Completed") {
|
||||||
if (frm.doc.po_items && frm.doc.status !== "Closed") {
|
frm.add_custom_button(__("Work Order Tree"), ()=> {
|
||||||
frm.add_custom_button(__("Work Order"), ()=> {
|
frappe.set_route('Tree', 'Work Order', {production_plan: frm.doc.name});
|
||||||
frm.trigger("make_work_order");
|
}, __('View'));
|
||||||
}, __('Create'));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (frm.doc.mr_items && !in_list(['Material Requested', 'Closed'], frm.doc.status)) {
|
frm.add_custom_button(__("Production Plan Summary"), ()=> {
|
||||||
frm.add_custom_button(__("Material Request"), ()=> {
|
frappe.set_route('query-report', 'Production Plan Summary', {production_plan: frm.doc.name});
|
||||||
frm.trigger("make_material_request");
|
}, __('View'));
|
||||||
}, __('Create'));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (frm.doc.status === "Closed") {
|
if (frm.doc.status === "Closed") {
|
||||||
frm.add_custom_button(__("Re-open"), function() {
|
frm.add_custom_button(__("Re-open"), function() {
|
||||||
@ -89,6 +85,18 @@ frappe.ui.form.on('Production Plan', {
|
|||||||
frm.events.close_open_production_plan(frm, true);
|
frm.events.close_open_production_plan(frm, true);
|
||||||
}, __("Status"));
|
}, __("Status"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (frm.doc.po_items && frm.doc.status !== "Closed") {
|
||||||
|
frm.add_custom_button(__("Work Order / Subcontract PO"), ()=> {
|
||||||
|
frm.trigger("make_work_order");
|
||||||
|
}, __('Create'));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (frm.doc.mr_items && !in_list(['Material Requested', 'Closed'], frm.doc.status)) {
|
||||||
|
frm.add_custom_button(__("Material Request"), ()=> {
|
||||||
|
frm.trigger("make_material_request");
|
||||||
|
}, __('Create'));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -233,6 +241,17 @@ frappe.ui.form.on('Production Plan', {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
get_sub_assembly_items: function(frm) {
|
||||||
|
frappe.call({
|
||||||
|
method: "get_sub_assembly_items",
|
||||||
|
freeze: true,
|
||||||
|
doc: frm.doc,
|
||||||
|
callback: function() {
|
||||||
|
refresh_field("sub_assembly_items");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
get_items_for_mr: function(frm) {
|
get_items_for_mr: function(frm) {
|
||||||
if (!frm.doc.for_warehouse) {
|
if (!frm.doc.for_warehouse) {
|
||||||
frappe.throw(__("Select warehouse for material requests"));
|
frappe.throw(__("Select warehouse for material requests"));
|
||||||
|
@ -32,6 +32,9 @@
|
|||||||
"po_items",
|
"po_items",
|
||||||
"section_break_25",
|
"section_break_25",
|
||||||
"prod_plan_references",
|
"prod_plan_references",
|
||||||
|
"section_break_24",
|
||||||
|
"get_sub_assembly_items",
|
||||||
|
"sub_assembly_items",
|
||||||
"material_request_planning",
|
"material_request_planning",
|
||||||
"include_non_stock_items",
|
"include_non_stock_items",
|
||||||
"include_subcontracted_items",
|
"include_subcontracted_items",
|
||||||
@ -187,7 +190,7 @@
|
|||||||
"depends_on": "get_items_from",
|
"depends_on": "get_items_from",
|
||||||
"fieldname": "get_items",
|
"fieldname": "get_items",
|
||||||
"fieldtype": "Button",
|
"fieldtype": "Button",
|
||||||
"label": "Get Items For Work Order"
|
"label": "Get Finished Goods for Manufacture"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "po_items",
|
"fieldname": "po_items",
|
||||||
@ -199,7 +202,7 @@
|
|||||||
{
|
{
|
||||||
"fieldname": "material_request_planning",
|
"fieldname": "material_request_planning",
|
||||||
"fieldtype": "Section Break",
|
"fieldtype": "Section Break",
|
||||||
"label": "Material Request Planning"
|
"label": "Material Requirement Planning"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"default": "1",
|
"default": "1",
|
||||||
@ -237,12 +240,13 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "section_break_27",
|
"fieldname": "section_break_27",
|
||||||
"fieldtype": "Section Break"
|
"fieldtype": "Section Break",
|
||||||
|
"hide_border": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "mr_items",
|
"fieldname": "mr_items",
|
||||||
"fieldtype": "Table",
|
"fieldtype": "Table",
|
||||||
"label": "Material Request Plan Item",
|
"label": "Raw Materials",
|
||||||
"no_copy": 1,
|
"no_copy": 1,
|
||||||
"options": "Material Request Plan Item"
|
"options": "Material Request Plan Item"
|
||||||
},
|
},
|
||||||
@ -337,13 +341,30 @@
|
|||||||
"hidden": 1,
|
"hidden": 1,
|
||||||
"label": "Production Plan Item Reference",
|
"label": "Production Plan Item Reference",
|
||||||
"options": "Production Plan Item Reference"
|
"options": "Production Plan Item Reference"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "section_break_24",
|
||||||
|
"fieldtype": "Section Break",
|
||||||
|
"hide_border": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "sub_assembly_items",
|
||||||
|
"fieldtype": "Table",
|
||||||
|
"label": "Sub Assembly Items",
|
||||||
|
"no_copy": 1,
|
||||||
|
"options": "Production Plan Sub Assembly Item"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "get_sub_assembly_items",
|
||||||
|
"fieldtype": "Button",
|
||||||
|
"label": "Get Sub Assembly Items"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"icon": "fa fa-calendar",
|
"icon": "fa fa-calendar",
|
||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2021-05-24 16:59:03.643211",
|
"modified": "2021-06-28 20:00:33.905114",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Manufacturing",
|
"module": "Manufacturing",
|
||||||
"name": "Production Plan",
|
"name": "Production Plan",
|
||||||
|
@ -5,10 +5,11 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
import frappe, json, copy
|
import frappe, json, copy
|
||||||
from frappe import msgprint, _
|
from frappe import msgprint, _
|
||||||
from six import string_types, iteritems
|
from six import iteritems
|
||||||
|
|
||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
from frappe.utils import cstr, flt, cint, nowdate, add_days, comma_and, now_datetime, ceil
|
from frappe.utils import (flt, cint, nowdate, add_days, comma_and, now_datetime,
|
||||||
|
ceil, get_link_to_form, getdate)
|
||||||
from frappe.utils.csvutils import build_csv_response
|
from frappe.utils.csvutils import build_csv_response
|
||||||
from erpnext.manufacturing.doctype.bom.bom import validate_bom_no, get_children
|
from erpnext.manufacturing.doctype.bom.bom import validate_bom_no, get_children
|
||||||
from erpnext.manufacturing.doctype.work_order.work_order import get_item_details
|
from erpnext.manufacturing.doctype.work_order.work_order import get_item_details
|
||||||
@ -349,49 +350,88 @@ class ProductionPlan(Document):
|
|||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def make_work_order(self):
|
def make_work_order(self):
|
||||||
wo_list = []
|
wo_list, po_list = [], []
|
||||||
|
subcontracted_po = {}
|
||||||
|
|
||||||
self.validate_data()
|
self.validate_data()
|
||||||
|
self.make_work_order_for_finished_goods(wo_list)
|
||||||
|
self.make_work_order_for_subassembly_items(wo_list, subcontracted_po)
|
||||||
|
self.make_subcontracted_purchase_order(subcontracted_po, po_list)
|
||||||
|
self.show_list_created_message('Work Order', wo_list)
|
||||||
|
self.show_list_created_message('Purchase Order', po_list)
|
||||||
|
|
||||||
|
def make_work_order_for_finished_goods(self, wo_list):
|
||||||
items_data = self.get_production_items()
|
items_data = self.get_production_items()
|
||||||
|
|
||||||
for key, item in items_data.items():
|
for key, item in items_data.items():
|
||||||
|
if self.sub_assembly_items:
|
||||||
|
item['use_multi_level_bom'] = 0
|
||||||
|
|
||||||
work_order = self.create_work_order(item)
|
work_order = self.create_work_order(item)
|
||||||
if work_order:
|
if work_order:
|
||||||
wo_list.append(work_order)
|
wo_list.append(work_order)
|
||||||
|
|
||||||
if item.get("make_work_order_for_sub_assembly_items"):
|
def make_work_order_for_subassembly_items(self, wo_list, subcontracted_po):
|
||||||
work_orders = self.make_work_order_for_sub_assembly_items(item)
|
for row in self.sub_assembly_items:
|
||||||
wo_list.extend(work_orders)
|
if row.type_of_manufacturing == 'Subcontract':
|
||||||
|
subcontracted_po.setdefault(row.supplier, []).append(row)
|
||||||
|
continue
|
||||||
|
|
||||||
|
args = {}
|
||||||
|
self.prepare_args_for_sub_assembly_items(row, args)
|
||||||
|
work_order = self.create_work_order(args)
|
||||||
|
if work_order:
|
||||||
|
wo_list.append(work_order)
|
||||||
|
|
||||||
|
def make_subcontracted_purchase_order(self, subcontracted_po, purchase_orders):
|
||||||
|
if not subcontracted_po:
|
||||||
|
return
|
||||||
|
|
||||||
|
for supplier, po_list in subcontracted_po.items():
|
||||||
|
po = frappe.new_doc('Purchase Order')
|
||||||
|
po.supplier = supplier
|
||||||
|
po.schedule_date = getdate(po_list[0].schedule_date) if po_list[0].schedule_date else nowdate()
|
||||||
|
po.is_subcontracted_item = 'Yes'
|
||||||
|
for row in po_list:
|
||||||
|
args = {
|
||||||
|
'item_code': row.production_item,
|
||||||
|
'warehouse': row.fg_warehouse,
|
||||||
|
'production_plan_sub_assembly_item': row.name,
|
||||||
|
'bom': row.bom_no,
|
||||||
|
'production_plan': self.name
|
||||||
|
}
|
||||||
|
|
||||||
|
for field in ['schedule_date', 'qty', 'uom', 'stock_uom', 'item_name',
|
||||||
|
'description', 'production_plan_item']:
|
||||||
|
args[field] = row.get(field)
|
||||||
|
|
||||||
|
po.append('items', args)
|
||||||
|
|
||||||
|
po.set_missing_values()
|
||||||
|
po.flags.ignore_mandatory = True
|
||||||
|
po.flags.ignore_validate = True
|
||||||
|
po.insert()
|
||||||
|
purchase_orders.append(po.name)
|
||||||
|
|
||||||
|
def show_list_created_message(self, doctype, doc_list=None):
|
||||||
|
if not doc_list:
|
||||||
|
return
|
||||||
|
|
||||||
frappe.flags.mute_messages = False
|
frappe.flags.mute_messages = False
|
||||||
|
if doc_list:
|
||||||
|
doc_list = [get_link_to_form(doctype, p) for p in doc_list]
|
||||||
|
msgprint(_("{0} created").format(comma_and(doc_list)))
|
||||||
|
|
||||||
if wo_list:
|
def prepare_args_for_sub_assembly_items(self, row, args):
|
||||||
wo_list = ["""<a href="/app/Form/Work Order/%s" target="_blank">%s</a>""" % \
|
for field in ["production_item", "item_name", "qty", "fg_warehouse",
|
||||||
(p, p) for p in wo_list]
|
"description", "bom_no", "stock_uom", "bom_level", "production_plan_item"]:
|
||||||
msgprint(_("{0} created").format(comma_and(wo_list)))
|
args[field] = row.get(field)
|
||||||
else :
|
|
||||||
msgprint(_("No Work Orders created"))
|
|
||||||
|
|
||||||
def make_work_order_for_sub_assembly_items(self, item):
|
args.update({
|
||||||
work_orders = []
|
"use_multi_level_bom": 0,
|
||||||
bom_data = {}
|
"production_plan": self.name,
|
||||||
|
"production_plan_sub_assembly_item": row.name
|
||||||
get_sub_assembly_items(item.get("bom_no"), bom_data, item.get("qty"))
|
})
|
||||||
|
|
||||||
for key, data in bom_data.items():
|
|
||||||
data.update({
|
|
||||||
'qty': data.get("stock_qty"),
|
|
||||||
'production_plan': self.name,
|
|
||||||
'use_multi_level_bom': item.get("use_multi_level_bom"),
|
|
||||||
'company': self.company,
|
|
||||||
'fg_warehouse': item.get("fg_warehouse"),
|
|
||||||
'update_consumed_material_cost_in_project': 0
|
|
||||||
})
|
|
||||||
|
|
||||||
work_order = self.create_work_order(data)
|
|
||||||
if work_order:
|
|
||||||
work_orders.append(work_order)
|
|
||||||
|
|
||||||
return work_orders
|
|
||||||
|
|
||||||
def create_work_order(self, item):
|
def create_work_order(self, item):
|
||||||
from erpnext.manufacturing.doctype.work_order.work_order import OverProductionError, get_default_warehouse
|
from erpnext.manufacturing.doctype.work_order.work_order import OverProductionError, get_default_warehouse
|
||||||
@ -476,9 +516,32 @@ class ProductionPlan(Document):
|
|||||||
else :
|
else :
|
||||||
msgprint(_("No material request created"))
|
msgprint(_("No material request created"))
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def get_sub_assembly_items(self, manufacturing_type=None):
|
||||||
|
self.sub_assembly_items = []
|
||||||
|
for row in self.po_items:
|
||||||
|
bom_data = []
|
||||||
|
get_sub_assembly_items(row.bom_no, bom_data, row.planned_qty)
|
||||||
|
self.set_sub_assembly_items_based_on_level(row, bom_data, manufacturing_type)
|
||||||
|
|
||||||
|
self.save()
|
||||||
|
|
||||||
|
def set_sub_assembly_items_based_on_level(self, row, bom_data, manufacturing_type=None):
|
||||||
|
bom_data = sorted(bom_data, key = lambda i: i.bom_level)
|
||||||
|
|
||||||
|
for data in bom_data:
|
||||||
|
data.qty = data.stock_qty
|
||||||
|
data.production_plan_item = row.name
|
||||||
|
data.fg_warehouse = row.warehouse
|
||||||
|
data.schedule_date = row.planned_start_date
|
||||||
|
data.type_of_manufacturing = manufacturing_type or ("Subcontract" if data.is_sub_contracted_item
|
||||||
|
else "In House")
|
||||||
|
|
||||||
|
self.append("sub_assembly_items", data)
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def download_raw_materials(doc, warehouses=None):
|
def download_raw_materials(doc, warehouses=None):
|
||||||
if isinstance(doc, string_types):
|
if isinstance(doc, str):
|
||||||
doc = frappe._dict(json.loads(doc))
|
doc = frappe._dict(json.loads(doc))
|
||||||
|
|
||||||
item_list = [['Item Code', 'Description', 'Stock UOM', 'Warehouse', 'Required Qty as per BOM',
|
item_list = [['Item Code', 'Description', 'Stock UOM', 'Warehouse', 'Required Qty as per BOM',
|
||||||
@ -660,7 +723,7 @@ def get_sales_orders(self):
|
|||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def get_bin_details(row, company, for_warehouse=None, all_warehouse=False):
|
def get_bin_details(row, company, for_warehouse=None, all_warehouse=False):
|
||||||
if isinstance(row, string_types):
|
if isinstance(row, str):
|
||||||
row = frappe._dict(json.loads(row))
|
row = frappe._dict(json.loads(row))
|
||||||
|
|
||||||
company = frappe.db.escape(company)
|
company = frappe.db.escape(company)
|
||||||
@ -684,8 +747,11 @@ def get_bin_details(row, company, for_warehouse=None, all_warehouse=False):
|
|||||||
group by item_code, warehouse
|
group by item_code, warehouse
|
||||||
""".format(conditions=conditions), { "item_code": row['item_code'] }, as_dict=1)
|
""".format(conditions=conditions), { "item_code": row['item_code'] }, as_dict=1)
|
||||||
|
|
||||||
def get_warehouse_list(warehouses, warehouse_list=[]):
|
def get_warehouse_list(warehouses, warehouse_list=None):
|
||||||
if isinstance(warehouses, string_types):
|
if not warehouse_list:
|
||||||
|
warehouse_list = []
|
||||||
|
|
||||||
|
if isinstance(warehouses, str):
|
||||||
warehouses = json.loads(warehouses)
|
warehouses = json.loads(warehouses)
|
||||||
|
|
||||||
for row in warehouses:
|
for row in warehouses:
|
||||||
@ -697,7 +763,7 @@ def get_warehouse_list(warehouses, warehouse_list=[]):
|
|||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def get_items_for_material_requests(doc, warehouses=None, get_parent_warehouse_data=None):
|
def get_items_for_material_requests(doc, warehouses=None, get_parent_warehouse_data=None):
|
||||||
if isinstance(doc, string_types):
|
if isinstance(doc, str):
|
||||||
doc = frappe._dict(json.loads(doc))
|
doc = frappe._dict(json.loads(doc))
|
||||||
|
|
||||||
warehouse_list = []
|
warehouse_list = []
|
||||||
@ -726,6 +792,9 @@ def get_items_for_material_requests(doc, warehouses=None, get_parent_warehouse_d
|
|||||||
|
|
||||||
so_item_details = frappe._dict()
|
so_item_details = frappe._dict()
|
||||||
for data in po_items:
|
for data in po_items:
|
||||||
|
if not data.get("include_exploded_items") and doc.get("sub_assembly_items"):
|
||||||
|
data["include_exploded_items"] = 1
|
||||||
|
|
||||||
planned_qty = data.get('required_qty') or data.get('planned_qty')
|
planned_qty = data.get('required_qty') or data.get('planned_qty')
|
||||||
ignore_existing_ordered_qty = data.get('ignore_existing_ordered_qty') or ignore_existing_ordered_qty
|
ignore_existing_ordered_qty = data.get('ignore_existing_ordered_qty') or ignore_existing_ordered_qty
|
||||||
warehouse = doc.get('for_warehouse')
|
warehouse = doc.get('for_warehouse')
|
||||||
@ -857,23 +926,28 @@ def get_item_data(item_code):
|
|||||||
# "description": item_details.get("description")
|
# "description": item_details.get("description")
|
||||||
}
|
}
|
||||||
|
|
||||||
def get_sub_assembly_items(bom_no, bom_data, to_produce_qty):
|
def get_sub_assembly_items(bom_no, bom_data, to_produce_qty, indent=0):
|
||||||
data = get_children('BOM', parent = bom_no)
|
data = get_children('BOM', parent = bom_no)
|
||||||
for d in data:
|
for d in data:
|
||||||
if d.expandable:
|
if d.expandable:
|
||||||
key = (d.name, d.value)
|
parent_item_code = frappe.get_cached_value("BOM", bom_no, "item")
|
||||||
if key not in bom_data:
|
bom_level = (frappe.get_cached_value("BOM", d.value, "bom_level")
|
||||||
bom_data.setdefault(key, {
|
if d.value else 0)
|
||||||
'stock_qty': 0,
|
|
||||||
'description': d.description,
|
|
||||||
'production_item': d.item_code,
|
|
||||||
'item_name': d.item_name,
|
|
||||||
'stock_uom': d.stock_uom,
|
|
||||||
'uom': d.stock_uom,
|
|
||||||
'bom_no': d.value
|
|
||||||
})
|
|
||||||
|
|
||||||
bom_item = bom_data.get(key)
|
stock_qty = (d.stock_qty / d.parent_bom_qty) * flt(to_produce_qty)
|
||||||
bom_item["stock_qty"] += (d.stock_qty / d.parent_bom_qty) * flt(to_produce_qty)
|
bom_data.append(frappe._dict({
|
||||||
|
'parent_item_code': parent_item_code,
|
||||||
|
'description': d.description,
|
||||||
|
'production_item': d.item_code,
|
||||||
|
'item_name': d.item_name,
|
||||||
|
'stock_uom': d.stock_uom,
|
||||||
|
'uom': d.stock_uom,
|
||||||
|
'bom_no': d.value,
|
||||||
|
'is_sub_contracted_item': d.is_sub_contracted_item,
|
||||||
|
'bom_level': bom_level,
|
||||||
|
'indent': indent,
|
||||||
|
'stock_qty': stock_qty
|
||||||
|
}))
|
||||||
|
|
||||||
get_sub_assembly_items(bom_item.get("bom_no"), bom_data, bom_item["stock_qty"])
|
if d.value:
|
||||||
|
get_sub_assembly_items(d.value, bom_data, stock_qty, indent=indent+1)
|
||||||
|
@ -9,5 +9,9 @@ def get_data():
|
|||||||
'label': _('Transactions'),
|
'label': _('Transactions'),
|
||||||
'items': ['Work Order', 'Material Request']
|
'items': ['Work Order', 'Material Request']
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
'label': _('Subcontract'),
|
||||||
|
'items': ['Purchase Order']
|
||||||
|
},
|
||||||
]
|
]
|
||||||
}
|
}
|
@ -169,7 +169,7 @@ class TestProductionPlan(unittest.TestCase):
|
|||||||
pln.get_items()
|
pln.get_items()
|
||||||
pln.submit()
|
pln.submit()
|
||||||
|
|
||||||
self.assertTrue(pln.po_items[0].planned_qty, 3)
|
self.assertTrue(pln.po_items[0].planned_qty, 3)
|
||||||
|
|
||||||
pln.make_work_order()
|
pln.make_work_order()
|
||||||
work_order = frappe.db.get_value('Work Order', {
|
work_order = frappe.db.get_value('Work Order', {
|
||||||
@ -193,10 +193,10 @@ class TestProductionPlan(unittest.TestCase):
|
|||||||
for so_item in so_items:
|
for so_item in so_items:
|
||||||
so_wo_qty = frappe.db.get_value('Sales Order Item', so_item, 'work_order_qty')
|
so_wo_qty = frappe.db.get_value('Sales Order Item', so_item, 'work_order_qty')
|
||||||
self.assertEqual(so_wo_qty, 0.0)
|
self.assertEqual(so_wo_qty, 0.0)
|
||||||
|
|
||||||
latest_plan = frappe.get_doc('Production Plan', pln.name)
|
latest_plan = frappe.get_doc('Production Plan', pln.name)
|
||||||
latest_plan.cancel()
|
latest_plan.cancel()
|
||||||
|
|
||||||
def test_pp_to_mr_customer_provided(self):
|
def test_pp_to_mr_customer_provided(self):
|
||||||
#Material Request from Production Plan for Customer Provided
|
#Material Request from Production Plan for Customer Provided
|
||||||
create_item('CUST-0987', is_customer_provided_item = 1, customer = '_Test Customer', is_purchase_item = 0)
|
create_item('CUST-0987', is_customer_provided_item = 1, customer = '_Test Customer', is_purchase_item = 0)
|
||||||
@ -236,10 +236,10 @@ class TestProductionPlan(unittest.TestCase):
|
|||||||
pln.append("po_items", {
|
pln.append("po_items", {
|
||||||
"item_code": item_code,
|
"item_code": item_code,
|
||||||
"bom_no": frappe.db.get_value('BOM', {'item': "Test BOM 1"}),
|
"bom_no": frappe.db.get_value('BOM', {'item': "Test BOM 1"}),
|
||||||
"planned_qty": 3,
|
"planned_qty": 3
|
||||||
"make_work_order_for_sub_assembly_items": 1
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
pln.get_sub_assembly_items('In House')
|
||||||
pln.submit()
|
pln.submit()
|
||||||
pln.make_work_order()
|
pln.make_work_order()
|
||||||
|
|
||||||
|
@ -9,18 +9,17 @@
|
|||||||
"include_exploded_items",
|
"include_exploded_items",
|
||||||
"item_code",
|
"item_code",
|
||||||
"bom_no",
|
"bom_no",
|
||||||
"planned_qty",
|
|
||||||
"column_break_6",
|
"column_break_6",
|
||||||
"make_work_order_for_sub_assembly_items",
|
"planned_qty",
|
||||||
"warehouse",
|
"warehouse",
|
||||||
"planned_start_date",
|
"planned_start_date",
|
||||||
"section_break_9",
|
"section_break_9",
|
||||||
"pending_qty",
|
"pending_qty",
|
||||||
"ordered_qty",
|
"ordered_qty",
|
||||||
"produced_qty",
|
|
||||||
"column_break_17",
|
"column_break_17",
|
||||||
"description",
|
"description",
|
||||||
"stock_uom",
|
"stock_uom",
|
||||||
|
"produced_qty",
|
||||||
"reference_section",
|
"reference_section",
|
||||||
"sales_order",
|
"sales_order",
|
||||||
"sales_order_item",
|
"sales_order_item",
|
||||||
@ -32,11 +31,10 @@
|
|||||||
],
|
],
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
"columns": 2,
|
"columns": 1,
|
||||||
"default": "0",
|
"default": "1",
|
||||||
"fieldname": "include_exploded_items",
|
"fieldname": "include_exploded_items",
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
"in_list_view": 1,
|
|
||||||
"label": "Include Exploded Items"
|
"label": "Include Exploded Items"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -80,13 +78,6 @@
|
|||||||
"fieldname": "column_break_6",
|
"fieldname": "column_break_6",
|
||||||
"fieldtype": "Column Break"
|
"fieldtype": "Column Break"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"default": "0",
|
|
||||||
"description": "If enabled, system will create the work order for the exploded items against which BOM is available.",
|
|
||||||
"fieldname": "make_work_order_for_sub_assembly_items",
|
|
||||||
"fieldtype": "Check",
|
|
||||||
"label": "Make Work Order for Sub Assembly Items"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"fieldname": "warehouse",
|
"fieldname": "warehouse",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
@ -218,7 +209,7 @@
|
|||||||
"idx": 1,
|
"idx": 1,
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2021-04-28 19:14:57.772123",
|
"modified": "2021-06-28 18:31:06.822168",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Manufacturing",
|
"module": "Manufacturing",
|
||||||
"name": "Production Plan Item",
|
"name": "Production Plan Item",
|
||||||
|
@ -0,0 +1,202 @@
|
|||||||
|
{
|
||||||
|
"actions": [],
|
||||||
|
"creation": "2020-12-27 16:08:36.127199",
|
||||||
|
"doctype": "DocType",
|
||||||
|
"editable_grid": 1,
|
||||||
|
"engine": "InnoDB",
|
||||||
|
"field_order": [
|
||||||
|
"production_item",
|
||||||
|
"item_name",
|
||||||
|
"fg_warehouse",
|
||||||
|
"parent_item_code",
|
||||||
|
"schedule_date",
|
||||||
|
"column_break_3",
|
||||||
|
"qty",
|
||||||
|
"bom_no",
|
||||||
|
"bom_level",
|
||||||
|
"type_of_manufacturing",
|
||||||
|
"supplier",
|
||||||
|
"work_order_details_section",
|
||||||
|
"work_order",
|
||||||
|
"purchase_order",
|
||||||
|
"production_plan_item",
|
||||||
|
"column_break_7",
|
||||||
|
"produced_qty",
|
||||||
|
"received_qty",
|
||||||
|
"indent",
|
||||||
|
"section_break_19",
|
||||||
|
"uom",
|
||||||
|
"stock_uom",
|
||||||
|
"column_break_22",
|
||||||
|
"description"
|
||||||
|
],
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fetch_from": "sub_assembly_item_code.item_name",
|
||||||
|
"fieldname": "item_name",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"label": "Item Name",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_3",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"depends_on": "eval:doc.type_of_manufacturing == \"In House\"",
|
||||||
|
"fieldname": "work_order_details_section",
|
||||||
|
"fieldtype": "Section Break",
|
||||||
|
"label": "Reference"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "work_order",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Work Order",
|
||||||
|
"options": "Work Order",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_7",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"columns": 1,
|
||||||
|
"fieldname": "qty",
|
||||||
|
"fieldtype": "Float",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Required Qty",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "purchase_order",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Purchase Order",
|
||||||
|
"options": "Purchase Order",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "received_qty",
|
||||||
|
"fieldtype": "Float",
|
||||||
|
"label": "Received Qty"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "bom_no",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Bom No",
|
||||||
|
"options": "BOM"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "production_plan_item",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"hidden": 1,
|
||||||
|
"label": "Production Plan Item",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "parent_item_code",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Finished Good",
|
||||||
|
"options": "Item",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"columns": 1,
|
||||||
|
"fetch_from": "bom_no.bom_level",
|
||||||
|
"fieldname": "bom_level",
|
||||||
|
"fieldtype": "Int",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Level (BOM)",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"collapsible": 1,
|
||||||
|
"fieldname": "section_break_19",
|
||||||
|
"fieldtype": "Section Break",
|
||||||
|
"label": "Item Details"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "uom",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "UOM",
|
||||||
|
"options": "UOM",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "stock_uom",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Stock UOM",
|
||||||
|
"options": "UOM",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_22",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "description",
|
||||||
|
"fieldtype": "Small Text",
|
||||||
|
"label": "description",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "production_item",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Sub Assembly Item Code",
|
||||||
|
"options": "Item",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "indent",
|
||||||
|
"fieldtype": "Int",
|
||||||
|
"label": "Indent"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "fg_warehouse",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Target Warehouse",
|
||||||
|
"options": "Warehouse"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "produced_qty",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"label": "Produced Quantity",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "In House",
|
||||||
|
"fieldname": "type_of_manufacturing",
|
||||||
|
"fieldtype": "Select",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Manufacturing Type",
|
||||||
|
"options": "In House\nSubcontract"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "supplier",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Supplier",
|
||||||
|
"mandatory_depends_on": "eval:doc.type_of_manufacturing == 'Subcontract'",
|
||||||
|
"options": "Supplier"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "schedule_date",
|
||||||
|
"fieldtype": "Datetime",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Schedule Date"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"index_web_pages_for_search": 1,
|
||||||
|
"istable": 1,
|
||||||
|
"links": [],
|
||||||
|
"modified": "2021-06-28 20:10:56.296410",
|
||||||
|
"modified_by": "Administrator",
|
||||||
|
"module": "Manufacturing",
|
||||||
|
"name": "Production Plan Sub Assembly Item",
|
||||||
|
"owner": "Administrator",
|
||||||
|
"permissions": [],
|
||||||
|
"quick_entry": 1,
|
||||||
|
"sort_field": "modified",
|
||||||
|
"sort_order": "DESC",
|
||||||
|
"track_changes": 1
|
||||||
|
}
|
@ -0,0 +1,10 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
|
# For license information, please see license.txt
|
||||||
|
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
# import frappe
|
||||||
|
from frappe.model.document import Document
|
||||||
|
|
||||||
|
class ProductionPlanSubAssemblyItem(Document):
|
||||||
|
pass
|
@ -64,11 +64,16 @@
|
|||||||
"description",
|
"description",
|
||||||
"stock_uom",
|
"stock_uom",
|
||||||
"column_break2",
|
"column_break2",
|
||||||
|
"references_section",
|
||||||
"material_request",
|
"material_request",
|
||||||
"material_request_item",
|
"material_request_item",
|
||||||
"sales_order_item",
|
"sales_order_item",
|
||||||
|
"column_break_61",
|
||||||
"production_plan",
|
"production_plan",
|
||||||
"production_plan_item",
|
"production_plan_item",
|
||||||
|
"production_plan_sub_assembly_item",
|
||||||
|
"parent_work_order",
|
||||||
|
"bom_level",
|
||||||
"product_bundle_item",
|
"product_bundle_item",
|
||||||
"amended_from"
|
"amended_from"
|
||||||
],
|
],
|
||||||
@ -546,17 +551,26 @@
|
|||||||
"no_copy": 1,
|
"no_copy": 1,
|
||||||
"print_hide": 1,
|
"print_hide": 1,
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
}
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "production_plan_sub_assembly_item",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"label": "Production Plan Sub-assembly Item",
|
||||||
|
"no_copy": 1,
|
||||||
|
"print_hide": 1,
|
||||||
|
"read_only": 1
|
||||||
|
}
|
||||||
],
|
],
|
||||||
"icon": "fa fa-cogs",
|
"icon": "fa fa-cogs",
|
||||||
"idx": 1,
|
"idx": 1,
|
||||||
"image_field": "image",
|
"image_field": "image",
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2021-06-20 15:19:14.902699",
|
"modified": "2021-06-28 16:19:14.902699",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Manufacturing",
|
"module": "Manufacturing",
|
||||||
"name": "Work Order",
|
"name": "Work Order",
|
||||||
|
"nsm_parent_field": "parent_work_order",
|
||||||
"owner": "Administrator",
|
"owner": "Administrator",
|
||||||
"permissions": [
|
"permissions": [
|
||||||
{
|
{
|
||||||
|
@ -483,7 +483,7 @@ class WorkOrder(Document):
|
|||||||
|
|
||||||
|
|
||||||
self.set('operations', [])
|
self.set('operations', [])
|
||||||
if not self.bom_no:
|
if not self.bom_no or not frappe.get_cached_value('BOM', self.bom_no, 'with_operations'):
|
||||||
return
|
return
|
||||||
|
|
||||||
operations = []
|
operations = []
|
||||||
@ -590,6 +590,7 @@ class WorkOrder(Document):
|
|||||||
def validate_operation_time(self):
|
def validate_operation_time(self):
|
||||||
for d in self.operations:
|
for d in self.operations:
|
||||||
if not d.time_in_mins > 0:
|
if not d.time_in_mins > 0:
|
||||||
|
print(self.bom_no, self.production_item)
|
||||||
frappe.throw(_("Operation Time must be greater than 0 for Operation {0}").format(d.operation))
|
frappe.throw(_("Operation Time must be greater than 0 for Operation {0}").format(d.operation))
|
||||||
|
|
||||||
def update_required_items(self):
|
def update_required_items(self):
|
||||||
|
@ -0,0 +1,33 @@
|
|||||||
|
<div style="padding: 15px;">
|
||||||
|
<div class="row mb-5">
|
||||||
|
<div class="col-md-5" style="max-height: 500px">
|
||||||
|
{% if data.image %}
|
||||||
|
<div class="border image-field " style="overflow: hidden;border-color:#e6e6e6">
|
||||||
|
<img class="responsive" src={{ data.image }}>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
<div class="col-md-7 h-500">
|
||||||
|
<div style="padding-top: 10px;">
|
||||||
|
<b> Status </b> {{ data.status }}
|
||||||
|
</div>
|
||||||
|
<div style="padding-top: 10px;">
|
||||||
|
<b> Qty to Produce </b> {{ data.qty }}
|
||||||
|
</div>
|
||||||
|
<div style="padding-top: 10px;">
|
||||||
|
<b> Produced Qty </b> {{ data.produced_qty }}
|
||||||
|
</div>
|
||||||
|
<hr style="margin: 15px -15px;">
|
||||||
|
<p>
|
||||||
|
{% if data.value %}
|
||||||
|
<a style="margin-right: 7px; margin-bottom: 7px" class="btn btn-default btn-xs" href="#Form/Work Order/{{ data.value }}">
|
||||||
|
{{ __("Open Work Order {0}", [data.value.bold()]) }}</a>
|
||||||
|
{% endif %}
|
||||||
|
{% if data.item_code %}
|
||||||
|
<a class="btn btn-default btn-xs" href="#Form/Item/{{ data.item_code }}">
|
||||||
|
{{ __("Open Item {0}", [data.item_code.bold()]) }}</a>
|
||||||
|
{% endif %}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
@ -20,17 +20,20 @@ def get_exploded_items(bom, data, indent=0, qty=1):
|
|||||||
fields= ['qty','bom_no','qty','scrap','item_code','item_name','description','uom'])
|
fields= ['qty','bom_no','qty','scrap','item_code','item_name','description','uom'])
|
||||||
|
|
||||||
for item in exploded_items:
|
for item in exploded_items:
|
||||||
|
print(item.bom_no, indent)
|
||||||
item["indent"] = indent
|
item["indent"] = indent
|
||||||
data.append({
|
data.append({
|
||||||
'item_code': item.item_code,
|
'item_code': item.item_code,
|
||||||
'item_name': item.item_name,
|
'item_name': item.item_name,
|
||||||
'indent': indent,
|
'indent': indent,
|
||||||
|
'bom_level': (frappe.get_cached_value("BOM", item.bom_no, "bom_level")
|
||||||
|
if item.bom_no else ""),
|
||||||
'bom': item.bom_no,
|
'bom': item.bom_no,
|
||||||
'qty': item.qty * qty,
|
'qty': item.qty * qty,
|
||||||
'uom': item.uom,
|
'uom': item.uom,
|
||||||
'description': item.description,
|
'description': item.description,
|
||||||
'scrap': item.scrap
|
'scrap': item.scrap
|
||||||
})
|
})
|
||||||
if item.bom_no:
|
if item.bom_no:
|
||||||
get_exploded_items(item.bom_no, data, indent=indent+1, qty=item.qty)
|
get_exploded_items(item.bom_no, data, indent=indent+1, qty=item.qty)
|
||||||
|
|
||||||
@ -68,6 +71,12 @@ def get_columns():
|
|||||||
"fieldname": "uom",
|
"fieldname": "uom",
|
||||||
"width": 100
|
"width": 100
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"label": "BOM Level",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"fieldname": "bom_level",
|
||||||
|
"width": 100
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"label": "Standard Description",
|
"label": "Standard Description",
|
||||||
"fieldtype": "data",
|
"fieldtype": "data",
|
||||||
|
@ -68,6 +68,18 @@ frappe.query_reports["Job Card Summary"] = {
|
|||||||
get_data: function(txt) {
|
get_data: function(txt) {
|
||||||
return frappe.db.get_link_options('Item', txt);
|
return frappe.db.get_link_options('Item', txt);
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: __("Workstation"),
|
||||||
|
fieldname: "workstation",
|
||||||
|
fieldtype: "Link",
|
||||||
|
options: "Workstation"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: __("Operation"),
|
||||||
|
fieldname: "operation",
|
||||||
|
fieldtype: "Link",
|
||||||
|
options: "Operation"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
@ -1,14 +1,16 @@
|
|||||||
{
|
{
|
||||||
"add_total_row": 0,
|
"add_total_row": 1,
|
||||||
|
"columns": [],
|
||||||
"creation": "2020-04-20 12:00:21.436619",
|
"creation": "2020-04-20 12:00:21.436619",
|
||||||
"disable_prepared_report": 0,
|
"disable_prepared_report": 0,
|
||||||
"disabled": 0,
|
"disabled": 0,
|
||||||
"docstatus": 0,
|
"docstatus": 0,
|
||||||
"doctype": "Report",
|
"doctype": "Report",
|
||||||
|
"filters": [],
|
||||||
"idx": 0,
|
"idx": 0,
|
||||||
"is_standard": "Yes",
|
"is_standard": "Yes",
|
||||||
"letter_head": "Gadgets International",
|
"letter_head": "",
|
||||||
"modified": "2020-04-20 12:00:21.436619",
|
"modified": "2020-12-30 11:49:21.713561",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Manufacturing",
|
"module": "Manufacturing",
|
||||||
"name": "Job Card Summary",
|
"name": "Job Card Summary",
|
||||||
|
@ -0,0 +1,32 @@
|
|||||||
|
// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
|
// For license information, please see license.txt
|
||||||
|
/* eslint-disable */
|
||||||
|
|
||||||
|
frappe.query_reports["Production Plan Summary"] = {
|
||||||
|
"filters": [
|
||||||
|
{
|
||||||
|
fieldname: "production_plan",
|
||||||
|
label: __("Production Plan"),
|
||||||
|
fieldtype: "Link",
|
||||||
|
options: "Production Plan",
|
||||||
|
reqd: 1,
|
||||||
|
get_query: function() {
|
||||||
|
return {
|
||||||
|
filters: {
|
||||||
|
"docstatus": 1
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"formatter": function(value, row, column, data, default_formatter) {
|
||||||
|
value = default_formatter(value, row, column, data);
|
||||||
|
|
||||||
|
if (column.fieldname == "document_name") {
|
||||||
|
var color = data.pending_qty > 0 ? 'red': 'green';
|
||||||
|
value = `<a style='color:${color}' href="#Form/${data['document_type']}/${data['document_name']}" data-doctype="${data['document_type']}">${data['document_name']}</a>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return value;
|
||||||
|
},
|
||||||
|
};
|
@ -0,0 +1,26 @@
|
|||||||
|
{
|
||||||
|
"add_total_row": 0,
|
||||||
|
"columns": [],
|
||||||
|
"creation": "2020-12-27 11:43:39.781793",
|
||||||
|
"disable_prepared_report": 0,
|
||||||
|
"disabled": 0,
|
||||||
|
"docstatus": 0,
|
||||||
|
"doctype": "Report",
|
||||||
|
"filters": [],
|
||||||
|
"idx": 0,
|
||||||
|
"is_standard": "Yes",
|
||||||
|
"modified": "2020-12-27 11:43:42.677584",
|
||||||
|
"modified_by": "Administrator",
|
||||||
|
"module": "Manufacturing",
|
||||||
|
"name": "Production Plan Summary",
|
||||||
|
"owner": "Administrator",
|
||||||
|
"prepared_report": 0,
|
||||||
|
"ref_doctype": "Production Plan",
|
||||||
|
"report_name": "Production Plan Summary",
|
||||||
|
"report_type": "Script Report",
|
||||||
|
"roles": [
|
||||||
|
{
|
||||||
|
"role": "Manufacturing User"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
@ -0,0 +1,136 @@
|
|||||||
|
# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
|
# For license information, please see license.txt
|
||||||
|
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
import frappe
|
||||||
|
from frappe.utils import flt
|
||||||
|
|
||||||
|
def execute(filters=None):
|
||||||
|
columns, data = [], []
|
||||||
|
data = get_data(filters)
|
||||||
|
columns = get_column(filters)
|
||||||
|
|
||||||
|
return columns, data
|
||||||
|
|
||||||
|
def get_data(filters):
|
||||||
|
data = []
|
||||||
|
|
||||||
|
order_details = {}
|
||||||
|
get_work_order_details(filters, order_details)
|
||||||
|
get_purchase_order_details(filters, order_details)
|
||||||
|
get_production_plan_item_details(filters, data, order_details)
|
||||||
|
|
||||||
|
return data
|
||||||
|
|
||||||
|
def get_production_plan_item_details(filters, data, order_details):
|
||||||
|
itemwise_indent = {}
|
||||||
|
|
||||||
|
production_plan_doc = frappe.get_cached_doc("Production Plan", filters.get("production_plan"))
|
||||||
|
for row in production_plan_doc.po_items:
|
||||||
|
work_order = frappe.get_cached_value("Work Order", {"production_plan_item": row.name,
|
||||||
|
"bom_no": row.bom_no, "production_item": row.item_code}, "name")
|
||||||
|
|
||||||
|
if row.item_code not in itemwise_indent:
|
||||||
|
itemwise_indent.setdefault(row.item_code, {})
|
||||||
|
|
||||||
|
data.append({
|
||||||
|
"indent": 0,
|
||||||
|
"item_code": row.item_code,
|
||||||
|
"item_name": frappe.get_cached_value("Item", row.item_code, "item_name"),
|
||||||
|
"qty": row.planned_qty,
|
||||||
|
"document_type": "Work Order",
|
||||||
|
"document_name": work_order,
|
||||||
|
"bom_level": frappe.get_cached_value("BOM", row.bom_no, "bom_level"),
|
||||||
|
"produced_qty": order_details.get((work_order, row.item_code)).get("produced_qty"),
|
||||||
|
"pending_qty": flt(row.planned_qty) - flt(order_details.get((work_order, row.item_code)).get("produced_qty"))
|
||||||
|
})
|
||||||
|
|
||||||
|
get_production_plan_sub_assembly_item_details(filters, row, production_plan_doc, data, order_details)
|
||||||
|
|
||||||
|
def get_production_plan_sub_assembly_item_details(filters, row, production_plan_doc, data, order_details):
|
||||||
|
for item in production_plan_doc.sub_assembly_items:
|
||||||
|
if row.name == item.production_plan_item:
|
||||||
|
subcontracted_item = (item.type_of_manufacturing == 'Subcontract')
|
||||||
|
|
||||||
|
if subcontracted_item:
|
||||||
|
docname = frappe.get_cached_value("Purchase Order Item",
|
||||||
|
{"production_plan_sub_assembly_item": item.name, "docstatus": ("<", 2)}, "parent")
|
||||||
|
else:
|
||||||
|
docname = frappe.get_cached_value("Work Order",
|
||||||
|
{"production_plan_sub_assembly_item": item.name, "docstatus": ("<", 2)}, "name")
|
||||||
|
|
||||||
|
data.append({
|
||||||
|
"indent": 1,
|
||||||
|
"item_code": item.production_item,
|
||||||
|
"item_name": item.item_name,
|
||||||
|
"qty": item.qty,
|
||||||
|
"document_type": "Work Order" if not subcontracted_item else "Purchase Order",
|
||||||
|
"document_name": docname,
|
||||||
|
"bom_level": item.bom_level,
|
||||||
|
"produced_qty": order_details.get((docname, item.production_item)).get("produced_qty"),
|
||||||
|
"pending_qty": flt(item.qty) - flt(order_details.get((docname, item.production_item)).get("produced_qty"))
|
||||||
|
})
|
||||||
|
|
||||||
|
def get_work_order_details(filters, order_details):
|
||||||
|
for row in frappe.get_all("Work Order", filters = {"production_plan": filters.get("production_plan")},
|
||||||
|
fields=["name", "produced_qty", "production_plan", "production_item"]):
|
||||||
|
order_details.setdefault((row.name, row.production_item), row)
|
||||||
|
|
||||||
|
def get_purchase_order_details(filters, order_details):
|
||||||
|
for row in frappe.get_all("Purchase Order Item", filters = {"production_plan": filters.get("production_plan")},
|
||||||
|
fields=["parent", "received_qty as produced_qty", "item_code"]):
|
||||||
|
order_details.setdefault((row.parent, row.item_code), row)
|
||||||
|
|
||||||
|
def get_column(filters):
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
"label": "Finished Good",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"fieldname": "item_code",
|
||||||
|
"width": 300,
|
||||||
|
"options": "Item"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Item Name",
|
||||||
|
"fieldtype": "data",
|
||||||
|
"fieldname": "item_name",
|
||||||
|
"width": 100
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Document Type",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"fieldname": "document_type",
|
||||||
|
"width": 150,
|
||||||
|
"options": "DocType"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Document Name",
|
||||||
|
"fieldtype": "Dynamic Link",
|
||||||
|
"fieldname": "document_name",
|
||||||
|
"width": 150
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "BOM Level",
|
||||||
|
"fieldtype": "Int",
|
||||||
|
"fieldname": "bom_level",
|
||||||
|
"width": 100
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Order Qty",
|
||||||
|
"fieldtype": "Float",
|
||||||
|
"fieldname": "qty",
|
||||||
|
"width": 120
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Received Qty",
|
||||||
|
"fieldtype": "Float",
|
||||||
|
"fieldname": "produced_qty",
|
||||||
|
"width": 160
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Pending Qty",
|
||||||
|
"fieldtype": "Float",
|
||||||
|
"fieldname": "pending_qty",
|
||||||
|
"width": 110
|
||||||
|
}
|
||||||
|
]
|
@ -19,7 +19,7 @@ def execute(filters=None):
|
|||||||
return columns, data, None, chart_data
|
return columns, data, None, chart_data
|
||||||
|
|
||||||
def get_data(filters):
|
def get_data(filters):
|
||||||
query_filters = {"docstatus": 1}
|
query_filters = {"docstatus": ("<", 2)}
|
||||||
|
|
||||||
fields = ["name", "status", "sales_order", "production_item", "qty", "produced_qty",
|
fields = ["name", "status", "sales_order", "production_item", "qty", "produced_qty",
|
||||||
"planned_start_date", "planned_end_date", "actual_start_date", "actual_end_date", "lead_time"]
|
"planned_start_date", "planned_end_date", "actual_start_date", "actual_end_date", "lead_time"]
|
||||||
@ -62,7 +62,8 @@ def get_chart_based_on_status(data):
|
|||||||
"Not Started": 0,
|
"Not Started": 0,
|
||||||
"In Process": 0,
|
"In Process": 0,
|
||||||
"Stopped": 0,
|
"Stopped": 0,
|
||||||
"Completed": 0
|
"Completed": 0,
|
||||||
|
"Draft": 0
|
||||||
}
|
}
|
||||||
|
|
||||||
for d in data:
|
for d in data:
|
||||||
|
@ -290,3 +290,4 @@ erpnext.patches.v13_0.set_training_event_attendance
|
|||||||
erpnext.patches.v13_0.rename_issue_status_hold_to_on_hold
|
erpnext.patches.v13_0.rename_issue_status_hold_to_on_hold
|
||||||
erpnext.patches.v13_0.bill_for_rejected_quantity_in_purchase_invoice
|
erpnext.patches.v13_0.bill_for_rejected_quantity_in_purchase_invoice
|
||||||
erpnext.patches.v13_0.update_job_card_details
|
erpnext.patches.v13_0.update_job_card_details
|
||||||
|
erpnext.patches.v13_0.update_level_in_bom #1234sswef
|
||||||
|
30
erpnext/patches/v13_0/update_level_in_bom.py
Normal file
30
erpnext/patches/v13_0/update_level_in_bom.py
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
# Copyright (c) 2020, Frappe and Contributors
|
||||||
|
# License: GNU General Public License v3. See license.txt
|
||||||
|
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
import frappe
|
||||||
|
|
||||||
|
def execute():
|
||||||
|
for document in ["bom", "bom_item", "bom_explosion_item"]:
|
||||||
|
frappe.reload_doc('manufacturing', 'doctype', document)
|
||||||
|
|
||||||
|
frappe.db.sql(" update `tabBOM` set bom_level = 0 where docstatus = 1")
|
||||||
|
|
||||||
|
bom_list = frappe.db.sql_list("""select name from `tabBOM` bom
|
||||||
|
where docstatus=1 and is_active=1 and not exists(select bom_no from `tabBOM Item`
|
||||||
|
where parent=bom.name and ifnull(bom_no, '')!='')""")
|
||||||
|
|
||||||
|
count = 0
|
||||||
|
while(count < len(bom_list)):
|
||||||
|
for parent_bom in get_parent_boms(bom_list[count]):
|
||||||
|
bom_doc = frappe.get_cached_doc("BOM", parent_bom)
|
||||||
|
bom_doc.set_bom_level(update=True)
|
||||||
|
bom_list.append(parent_bom)
|
||||||
|
count += 1
|
||||||
|
|
||||||
|
def get_parent_boms(bom_no):
|
||||||
|
return frappe.db.sql_list("""
|
||||||
|
select distinct bom_item.parent from `tabBOM Item` bom_item
|
||||||
|
where bom_item.bom_no = %s and bom_item.docstatus=1 and bom_item.parenttype='BOM'
|
||||||
|
and exists(select bom.name from `tabBOM` bom where bom.name=bom_item.parent and bom.is_active=1)
|
||||||
|
""", bom_no)
|
@ -72,7 +72,7 @@ class StockEntry(StockController):
|
|||||||
self.validate_with_material_request()
|
self.validate_with_material_request()
|
||||||
self.validate_batch()
|
self.validate_batch()
|
||||||
self.validate_inspection()
|
self.validate_inspection()
|
||||||
self.validate_fg_completed_qty()
|
# self.validate_fg_completed_qty()
|
||||||
self.validate_difference_account()
|
self.validate_difference_account()
|
||||||
self.set_job_card_data()
|
self.set_job_card_data()
|
||||||
self.set_purpose_for_stock_entry()
|
self.set_purpose_for_stock_entry()
|
||||||
|
Loading…
x
Reference in New Issue
Block a user