feat: BOM template (#21262)
Co-authored-by: Marica <maricadsouza221197@gmail.com>
diff --git a/erpnext/controllers/queries.py b/erpnext/controllers/queries.py
index d946591..9bba71d 100644
--- a/erpnext/controllers/queries.py
+++ b/erpnext/controllers/queries.py
@@ -188,12 +188,6 @@
# scan description only if items are less than 50000
description_cond = 'or tabItem.description LIKE %(txt)s'
- extra_cond = " and tabItem.has_variants=0"
- if (filters and isinstance(filters, dict)
- and filters.get("doctype") == "BOM"):
- extra_cond = ""
- del filters["doctype"]
-
return frappe.db.sql("""select tabItem.name,
if(length(tabItem.item_name) > 40,
concat(substr(tabItem.item_name, 1, 40), "..."), item_name) as item_name,
@@ -204,10 +198,10 @@
from tabItem
where tabItem.docstatus < 2
and tabItem.disabled=0
+ and tabItem.has_variants=0
and (tabItem.end_of_life > %(today)s or ifnull(tabItem.end_of_life, '0000-00-00')='0000-00-00')
and ({scond} or tabItem.item_code IN (select parent from `tabItem Barcode` where barcode LIKE %(txt)s)
{description_cond})
- {extra_cond}
{fcond} {mcond}
order by
if(locate(%(_txt)s, name), locate(%(_txt)s, name), 99999),
@@ -218,7 +212,6 @@
key=searchfield,
columns=columns,
scond=searchfields,
- extra_cond=extra_cond,
fcond=get_filters_cond(doctype, filters, conditions).replace('%', '%%'),
mcond=get_match_cond(doctype).replace('%', '%%'),
description_cond = description_cond),
diff --git a/erpnext/manufacturing/doctype/bom/bom.js b/erpnext/manufacturing/doctype/bom/bom.js
index 898955e..47b4207 100644
--- a/erpnext/manufacturing/doctype/bom/bom.js
+++ b/erpnext/manufacturing/doctype/bom/bom.js
@@ -29,10 +29,7 @@
frm.set_query("item", function() {
return {
- query: "erpnext.controllers.queries.item_query",
- filters: {
- "doctype": "BOM"
- }
+ query: "erpnext.manufacturing.doctype.bom.bom.item_query"
};
});
@@ -44,9 +41,12 @@
};
});
- frm.set_query("item_code", "items", function() {
+ frm.set_query("item_code", "items", function(doc) {
return {
- query: "erpnext.controllers.queries.item_query"
+ query: "erpnext.manufacturing.doctype.bom.bom.item_query",
+ filters: {
+ "item_code": doc.item
+ }
};
});
@@ -96,6 +96,12 @@
frm.trigger("make_work_order");
}, __("Create"));
+ if (frm.doc.has_variants) {
+ frm.add_custom_button(__("Variant BOM"), function() {
+ frm.trigger("make_variant_bom");
+ }, __("Create"));
+ }
+
if (frm.doc.inspection_required) {
frm.add_custom_button(__("Quality Inspection"), function() {
frm.trigger("make_quality_inspection");
@@ -124,7 +130,7 @@
}
- if (frm.doc.__onload && frm.doc.__onload["has_variants"]) {
+ if (frm.doc.has_variants) {
frm.set_intro(__('This is a Template BOM and will be used to make the work order for {0} of the item {1}',
[
`<a class="variants-intro">variants</a>`,
@@ -138,9 +144,52 @@
},
make_work_order: function(frm) {
+ frm.events.setup_variant_prompt(frm, "Work Order", (frm, item, data, variant_items) => {
+ frappe.call({
+ method: "erpnext.manufacturing.doctype.work_order.work_order.make_work_order",
+ args: {
+ bom_no: frm.doc.name,
+ item: item,
+ qty: data.qty || 0.0,
+ project: frm.doc.project,
+ variant_items: variant_items
+ },
+ freeze: true,
+ callback: function(r) {
+ if(r.message) {
+ let doc = frappe.model.sync(r.message)[0];
+ frappe.set_route("Form", doc.doctype, doc.name);
+ }
+ }
+ });
+ });
+ },
+
+ make_variant_bom: function(frm) {
+ frm.events.setup_variant_prompt(frm, "Variant BOM", (frm, item, data, variant_items) => {
+ frappe.call({
+ method: "erpnext.manufacturing.doctype.bom.bom.make_variant_bom",
+ args: {
+ source_name: frm.doc.name,
+ bom_no: frm.doc.name,
+ item: item,
+ variant_items: variant_items
+ },
+ freeze: true,
+ callback: function(r) {
+ if(r.message) {
+ let doc = frappe.model.sync(r.message)[0];
+ frappe.set_route("Form", doc.doctype, doc.name);
+ }
+ }
+ });
+ }, true);
+ },
+
+ setup_variant_prompt: function(frm, title, callback, skip_qty_field) {
const fields = [];
- if (frm.doc.__onload && frm.doc.__onload["has_variants"]) {
+ if (frm.doc.has_variants) {
fields.push({
fieldtype: 'Link',
label: __('Variant Item'),
@@ -158,34 +207,106 @@
});
}
- fields.push({
- fieldtype: 'Float',
- label: __('Qty To Manufacture'),
- fieldname: 'qty',
- reqd: 1,
- default: 1
+ if (!skip_qty_field) {
+ fields.push({
+ fieldtype: 'Float',
+ label: __('Qty To Manufacture'),
+ fieldname: 'qty',
+ reqd: 1,
+ default: 1
+ });
+ }
+
+ var has_template_rm = frm.doc.items.filter(d => d.has_variants === 1) || [];
+ if (has_template_rm && has_template_rm.length > 0) {
+ fields.push({
+ fieldname: "items",
+ fieldtype: "Table",
+ label: __("Raw Materials"),
+ fields: [
+ {
+ fieldname: "item_code",
+ options: "Item",
+ label: __("Template Item"),
+ fieldtype: "Link",
+ in_list_view: 1,
+ reqd: 1,
+ },
+ {
+ fieldname: "varint_item_code",
+ options: "Item",
+ label: __("Variant Item"),
+ fieldtype: "Link",
+ in_list_view: 1,
+ reqd: 1,
+ get_query: function(data) {
+ if (!data.item_code) {
+ frappe.throw(__("Select template item"));
+ }
+
+ return {
+ query: "erpnext.controllers.queries.item_query",
+ filters: {
+ "variant_of": data.item_code
+ }
+ };
+ }
+ },
+ {
+ fieldname: "qty",
+ label: __("Quantity"),
+ fieldtype: "Float",
+ in_list_view: 1,
+ reqd: 1,
+ },
+ {
+ fieldname: "source_warehouse",
+ label: __("Source Warehouse"),
+ fieldtype: "Link",
+ options: "Warehouse"
+ },
+ {
+ fieldname: "operation",
+ label: __("Operation"),
+ fieldtype: "Data",
+ hidden: 1,
+ }
+ ],
+ in_place_edit: true,
+ data: [],
+ get_data: function () {
+ return [];
+ },
+ });
+ }
+
+ let dialog = frappe.prompt(fields, data => {
+ let item = data.item || frm.doc.item;
+ let variant_items = data.items || [];
+
+ variant_items.forEach(d => {
+ if (!d.varint_item_code) {
+ frappe.throw(__("Select variant item code for the template item {0}", [d.item_code]));
+ }
+ })
+
+ callback(frm, item, data, variant_items);
+
+ }, __(title), __("Create"));
+
+ has_template_rm.forEach(d => {
+ dialog.fields_dict.items.df.data.push({
+ "item_code": d.item_code,
+ "varint_item_code": "",
+ "qty": d.qty,
+ "source_warehouse": d.source_warehouse,
+ "operation": d.operation
+ });
});
- frappe.prompt(fields, data => {
- let item = data.item || frm.doc.item;
-
- frappe.call({
- method: "erpnext.manufacturing.doctype.work_order.work_order.make_work_order",
- args: {
- bom_no: frm.doc.name,
- item: item,
- qty: data.qty || 0.0,
- project: frm.doc.project
- },
- freeze: true,
- callback: function(r) {
- if(r.message) {
- var doc = frappe.model.sync(r.message)[0];
- frappe.set_route("Form", doc.doctype, doc.name);
- }
- }
- });
- }, __("Enter Value"), __("Create"));
+ if (has_template_rm) {
+ dialog.fields_dict.items.grid.refresh();
+ }
},
make_quality_inspection: function(frm) {
diff --git a/erpnext/manufacturing/doctype/bom/bom.json b/erpnext/manufacturing/doctype/bom/bom.json
index 4ce0ecf..f551b91 100644
--- a/erpnext/manufacturing/doctype/bom/bom.json
+++ b/erpnext/manufacturing/doctype/bom/bom.json
@@ -53,6 +53,7 @@
"section_break_25",
"description",
"column_break_27",
+ "has_variants",
"section_break0",
"exploded_items",
"website_section",
@@ -498,6 +499,17 @@
"options": "Currency",
"print_hide": 1,
"read_only": 1
+ },
+ {
+ "default": "0",
+ "fetch_from": "item.has_variants",
+ "fieldname": "has_variants",
+ "fieldtype": "Check",
+ "in_list_view": 1,
+ "label": "Has Variants",
+ "no_copy": 1,
+ "print_hide": 1,
+ "read_only": 1
}
],
"icon": "fa fa-sitemap",
@@ -505,7 +517,7 @@
"image_field": "image",
"is_submittable": 1,
"links": [],
- "modified": "2020-05-05 14:29:32.634952",
+ "modified": "2020-05-21 12:29:32.634952",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "BOM",
diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py
index 6ac653e..1bdac57 100644
--- a/erpnext/manufacturing/doctype/bom/bom.py
+++ b/erpnext/manufacturing/doctype/bom/bom.py
@@ -3,13 +3,16 @@
from __future__ import unicode_literals
import frappe, erpnext
-from frappe.utils import cint, cstr, flt
+from frappe.utils import cint, cstr, flt, today
from frappe import _
from erpnext.setup.utils import get_exchange_rate
from frappe.website.website_generator import WebsiteGenerator
from erpnext.stock.get_item_details import get_conversion_factor
from erpnext.stock.get_item_details import get_price_list_rate
from frappe.core.doctype.version.version import get_diff
+from erpnext.controllers.queries import get_match_cond
+from erpnext.stock.doctype.item.item import get_item_details
+from frappe.model.mapper import get_mapped_doc
import functools
@@ -59,11 +62,6 @@
self.name = name
- def onload(self):
- super(BOM, self).onload()
- if self.get("item") and cint(frappe.db.get_value("Item", self.item, "has_variants")):
- self.set_onload("has_variants", True)
-
def validate(self):
self.route = frappe.scrub(self.name).replace('_', '-')
self.clear_operations()
@@ -103,9 +101,7 @@
self.manage_default_bom()
def get_item_det(self, item_code):
- item = frappe.db.sql("""select name, item_name, docstatus, description, image,
- is_sub_contracted_item, stock_uom, default_bom, last_purchase_rate, include_item_in_manufacturing
- from `tabItem` where name=%s""", item_code, as_dict = 1)
+ item = get_item_details(item_code)
if not item:
frappe.throw(_("Item: {0} does not exist in the system").format(item_code))
@@ -150,10 +146,10 @@
item = self.get_item_det(args['item_code'])
- args['bom_no'] = args['bom_no'] or item and cstr(item[0]['default_bom']) or ''
+ args['bom_no'] = args['bom_no'] or item and cstr(item['default_bom']) or ''
args['transfer_for_manufacture'] = (cstr(args.get('include_item_in_manufacturing', '')) or
- item and item[0].include_item_in_manufacturing or 0)
- args.update(item[0])
+ item and item.include_item_in_manufacturing or 0)
+ args.update(item)
rate = self.get_rm_rate(args)
ret_item = {
@@ -185,40 +181,14 @@
self.rm_cost_as_per = "Valuation Rate"
if arg.get('scrap_items'):
- rate = self.get_valuation_rate(arg)
+ rate = get_valuation_rate(arg)
elif arg:
#Customer Provided parts will have zero rate
if not frappe.db.get_value('Item', arg["item_code"], 'is_customer_provided_item'):
if arg.get('bom_no') and self.set_rate_of_sub_assembly_item_based_on_bom:
rate = flt(self.get_bom_unitcost(arg['bom_no'])) * (arg.get("conversion_factor") or 1)
else:
- if self.rm_cost_as_per == 'Valuation Rate':
- rate = self.get_valuation_rate(arg) * (arg.get("conversion_factor") or 1)
- elif self.rm_cost_as_per == 'Last Purchase Rate':
- rate = flt(arg.get('last_purchase_rate') \
- or frappe.db.get_value("Item", arg['item_code'], "last_purchase_rate")) \
- * (arg.get("conversion_factor") or 1)
- elif self.rm_cost_as_per == "Price List":
- if not self.buying_price_list:
- frappe.throw(_("Please select Price List"))
- args = frappe._dict({
- "doctype": "BOM",
- "price_list": self.buying_price_list,
- "qty": arg.get("qty") or 1,
- "uom": arg.get("uom") or arg.get("stock_uom"),
- "stock_uom": arg.get("stock_uom"),
- "transaction_type": "buying",
- "company": self.company,
- "currency": self.currency,
- "conversion_rate": 1, # Passed conversion rate as 1 purposefully, as conversion rate is applied at the end of the function
- "conversion_factor": arg.get("conversion_factor") or 1,
- "plc_conversion_rate": 1,
- "ignore_party": True
- })
- item_doc = frappe.get_doc("Item", arg.get("item_code"))
- out = frappe._dict()
- get_price_list_rate(args, item_doc, out)
- rate = out.price_list_rate
+ rate = get_bom_item_rate(arg, self)
if not rate:
if self.rm_cost_as_per == "Price List":
@@ -286,31 +256,6 @@
where is_active = 1 and name = %s""", bom_no, as_dict=1)
return bom and bom[0]['unit_cost'] or 0
- def get_valuation_rate(self, args):
- """ Get weighted average of valuation rate from all warehouses """
-
- total_qty, total_value, valuation_rate = 0.0, 0.0, 0.0
- for d in frappe.db.sql("""select actual_qty, stock_value from `tabBin`
- where item_code=%s""", args['item_code'], as_dict=1):
- total_qty += flt(d.actual_qty)
- total_value += flt(d.stock_value)
-
- if total_qty:
- valuation_rate = total_value / total_qty
-
- if valuation_rate <= 0:
- last_valuation_rate = frappe.db.sql("""select valuation_rate
- from `tabStock Ledger Entry`
- where item_code = %s and valuation_rate > 0
- order by posting_date desc, posting_time desc, creation desc limit 1""", args['item_code'])
-
- valuation_rate = flt(last_valuation_rate[0][0]) if last_valuation_rate else 0
-
- if not valuation_rate:
- valuation_rate = frappe.db.get_value("Item", args['item_code'], "valuation_rate")
-
- return flt(valuation_rate)
-
def manage_default_bom(self):
""" Uncheck others if current one is selected as default or
check the current one as default if it the only bom for the selected item,
@@ -624,6 +569,62 @@
if not d.batch_size or d.batch_size <= 0:
d.batch_size = 1
+def get_bom_item_rate(args, bom_doc):
+ if bom_doc.rm_cost_as_per == 'Valuation Rate':
+ rate = get_valuation_rate(args) * (args.get("conversion_factor") or 1)
+ elif bom_doc.rm_cost_as_per == 'Last Purchase Rate':
+ rate = ( flt(args.get('last_purchase_rate')) \
+ or frappe.db.get_value("Item", args['item_code'], "last_purchase_rate")) \
+ * (args.get("conversion_factor") or 1)
+ elif bom_doc.rm_cost_as_per == "Price List":
+ if not bom_doc.buying_price_list:
+ frappe.throw(_("Please select Price List"))
+ bom_args = frappe._dict({
+ "doctype": "BOM",
+ "price_list": bom_doc.buying_price_list,
+ "qty": args.get("qty") or 1,
+ "uom": args.get("uom") or args.get("stock_uom"),
+ "stock_uom": args.get("stock_uom"),
+ "transaction_type": "buying",
+ "company": bom_doc.company,
+ "currency": bom_doc.currency,
+ "conversion_rate": 1, # Passed conversion rate as 1 purposefully, as conversion rate is applied at the end of the function
+ "conversion_factor": args.get("conversion_factor") or 1,
+ "plc_conversion_rate": 1,
+ "ignore_party": True
+ })
+ item_doc = frappe.get_cached_doc("Item", args.get("item_code"))
+ out = frappe._dict()
+ get_price_list_rate(bom_args, item_doc, out)
+ rate = out.price_list_rate
+
+ return rate
+
+def get_valuation_rate(args):
+ """ Get weighted average of valuation rate from all warehouses """
+
+ total_qty, total_value, valuation_rate = 0.0, 0.0, 0.0
+ for d in frappe.db.sql("""select actual_qty, stock_value from `tabBin`
+ where item_code=%s""", args['item_code'], as_dict=1):
+ total_qty += flt(d.actual_qty)
+ total_value += flt(d.stock_value)
+
+ if total_qty:
+ valuation_rate = total_value / total_qty
+
+ if valuation_rate <= 0:
+ last_valuation_rate = frappe.db.sql("""select valuation_rate
+ from `tabStock Ledger Entry`
+ where item_code = %s and valuation_rate > 0
+ order by posting_date desc, posting_time desc, creation desc limit 1""", args['item_code'])
+
+ valuation_rate = flt(last_valuation_rate[0][0]) if last_valuation_rate else 0
+
+ if not valuation_rate:
+ valuation_rate = frappe.db.get_value("Item", args['item_code'], "valuation_rate")
+
+ return flt(valuation_rate)
+
def get_list_context(context):
context.title = _("Bill of Materials")
# context.introduction = _('Boms')
@@ -639,6 +640,8 @@
sum(bom_item.{qty_field}/ifnull(bom.quantity, 1)) * %(qty)s as qty,
item.image,
bom.project,
+ bom_item.rate,
+ bom_item.amount,
item.stock_uom,
item.item_group,
item.allow_alternative_item,
@@ -655,6 +658,7 @@
where
bom_item.docstatus < 2
and bom.name = %(bom)s
+ and ifnull(item.has_variants, 0) = 0
and item.is_stock_item in (1, {is_stock_item})
{where_conditions}
group by item_code, stock_uom
@@ -897,3 +901,84 @@
out.removed.append([df.fieldname, d.as_dict()])
return out
+
+def item_query(doctype, txt, searchfield, start, page_len, filters):
+ meta = frappe.get_meta("Item", cached=True)
+ searchfields = meta.get_search_fields()
+
+ order_by = "idx desc, name, item_name"
+
+ fields = ["name", "item_group", "item_name", "description"]
+ fields.extend([field for field in searchfields
+ if not field in ["name", "item_group", "description"]])
+
+ searchfields = searchfields + [field for field in [searchfield or "name", "item_code", "item_group", "item_name"]
+ if not field in searchfields]
+
+ query_filters = {
+ "disabled": 0,
+ "ifnull(end_of_life, '5050-50-50')": (">", today())
+ }
+
+ or_cond_filters = {}
+ if txt:
+ for s_field in searchfields:
+ or_cond_filters[s_field] = ("like", "%{0}%".format(txt))
+
+ barcodes = frappe.get_all("Item Barcode",
+ fields=["distinct parent as item_code"],
+ filters = {"barcode": ("like", "%{0}%".format(txt))})
+
+ barcodes = [d.item_code for d in barcodes]
+ if barcodes:
+ or_cond_filters["name"] = ("in", barcodes)
+
+ for cond in get_match_cond(doctype, as_condition=False):
+ for key, value in cond.items():
+ if key == doctype:
+ key = "name"
+
+ query_filters[key] = ("in", value)
+
+ if filters and filters.get("item_code"):
+ has_variants = frappe.get_cached_value("Item", filters.get("item_code"), "has_variants")
+ if not has_variants:
+ query_filters["has_variants"] = 0
+
+ return frappe.get_all("Item",
+ fields = fields, filters=query_filters,
+ or_filters = or_cond_filters, order_by=order_by,
+ limit_start=start, limit_page_length=page_len, as_list=1)
+
+@frappe.whitelist()
+def make_variant_bom(source_name, bom_no, item, variant_items, target_doc=None):
+ from erpnext.manufacturing.doctype.work_order.work_order import add_variant_item
+
+ def postprocess(source, doc):
+ doc.item = item
+ doc.quantity = 1
+
+ item_data = get_item_details(item)
+ doc.update({
+ "item_name": item_data.item_name,
+ "description": item_data.description,
+ "uom": item_data.stock_uom,
+ "allow_alternative_item": item_data.allow_alternative_item
+ })
+
+ add_variant_item(variant_items, doc, source_name)
+
+ doc = get_mapped_doc('BOM', source_name, {
+ 'BOM': {
+ 'doctype': 'BOM',
+ 'validation': {
+ 'docstatus': ['=', 1]
+ }
+ },
+ 'BOM Item': {
+ 'doctype': 'BOM Item',
+ 'condition': lambda doc: doc.has_variants == 0
+ },
+ }, target_doc, postprocess)
+
+ return doc
\ No newline at end of file
diff --git a/erpnext/manufacturing/doctype/bom/bom_list.js b/erpnext/manufacturing/doctype/bom/bom_list.js
index 2b06ed7..94cb466 100644
--- a/erpnext/manufacturing/doctype/bom/bom_list.js
+++ b/erpnext/manufacturing/doctype/bom/bom_list.js
@@ -1,7 +1,9 @@
frappe.listview_settings['BOM'] = {
- add_fields: ["is_active", "is_default", "total_cost"],
+ add_fields: ["is_active", "is_default", "total_cost", "has_variants"],
get_indicator: function(doc) {
- if(doc.is_default) {
+ if(doc.is_active && doc.has_variants) {
+ return [__("Template"), "orange", "has_variants,=,Yes"];
+ } else if(doc.is_default) {
return [__("Default"), "green", "is_default,=,Yes"];
} else if(doc.is_active) {
return [__("Active"), "blue", "is_active,=,Yes"];
diff --git a/erpnext/manufacturing/doctype/bom_item/bom_item.json b/erpnext/manufacturing/doctype/bom_item/bom_item.json
index f094be4..e34be61 100644
--- a/erpnext/manufacturing/doctype/bom_item/bom_item.json
+++ b/erpnext/manufacturing/doctype/bom_item/bom_item.json
@@ -1,8 +1,10 @@
{
+ "actions": [],
"creation": "2013-02-22 01:27:49",
"doctype": "DocType",
"document_type": "Setup",
"editable_grid": 1,
+ "engine": "InnoDB",
"field_order": [
"item_code",
"item_name",
@@ -33,6 +35,7 @@
"scrap",
"qty_consumed_per_unit",
"section_break_27",
+ "has_variants",
"include_item_in_manufacturing",
"original_item"
],
@@ -57,6 +60,7 @@
"label": "Item Name"
},
{
+ "depends_on": "eval:parent.with_operations == 1",
"fieldname": "operation",
"fieldtype": "Link",
"label": "Item operation",
@@ -258,11 +262,22 @@
"label": "Original Item",
"options": "Item",
"read_only": 1
+ },
+ {
+ "default": "0",
+ "fetch_from": "item_code.has_variants",
+ "fieldname": "has_variants",
+ "fieldtype": "Check",
+ "label": "Has Variants",
+ "no_copy": 1,
+ "print_hide": 1,
+ "read_only": 1
}
],
"idx": 1,
"istable": 1,
- "modified": "2019-11-22 11:38:52.087303",
+ "links": [],
+ "modified": "2020-04-09 14:30:26.535546",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "BOM Item",
diff --git a/erpnext/manufacturing/doctype/work_order/work_order.js b/erpnext/manufacturing/doctype/work_order/work_order.js
index c125571..a244f58 100644
--- a/erpnext/manufacturing/doctype/work_order/work_order.js
+++ b/erpnext/manufacturing/doctype/work_order/work_order.js
@@ -449,6 +449,32 @@
}
});
}
+ },
+
+ item_code: function(frm, cdt, cdn) {
+ let row = locals[cdt][cdn];
+
+ if (row.item_code) {
+ frappe.call({
+ method: "erpnext.stock.doctype.item.item.get_item_details",
+ args: {
+ item_code: row.item_code,
+ company: frm.doc.company
+ },
+ callback: function(r) {
+ if (r.message) {
+ frappe.model.set_value(cdt, cdn, {
+ "required_qty": 1,
+ "item_name": r.message.item_name,
+ "description": r.message.description,
+ "source_warehouse": r.message.default_warehouse,
+ "allow_alternative_item": r.message.allow_alternative_item,
+ "include_item_in_manufacturing": r.message.include_item_in_manufacturing
+ });
+ }
+ }
+ });
+ }
}
});
diff --git a/erpnext/manufacturing/doctype/work_order/work_order.py b/erpnext/manufacturing/doctype/work_order/work_order.py
index c278955..e2233a3 100644
--- a/erpnext/manufacturing/doctype/work_order/work_order.py
+++ b/erpnext/manufacturing/doctype/work_order/work_order.py
@@ -8,9 +8,9 @@
from frappe import _
from frappe.utils import flt, get_datetime, getdate, date_diff, cint, nowdate, get_link_to_form, time_diff_in_hours
from frappe.model.document import Document
-from erpnext.manufacturing.doctype.bom.bom import validate_bom_no, get_bom_items_as_dict
+from erpnext.manufacturing.doctype.bom.bom import validate_bom_no, get_bom_items_as_dict, get_bom_item_rate
from dateutil.relativedelta import relativedelta
-from erpnext.stock.doctype.item.item import validate_end_of_life
+from erpnext.stock.doctype.item.item import validate_end_of_life, get_item_defaults
from erpnext.manufacturing.doctype.workstation.workstation import WorkstationHolidayError
from erpnext.projects.doctype.timesheet.timesheet import OverlapError
from erpnext.manufacturing.doctype.manufacturing_settings.manufacturing_settings import get_mins_between_operations
@@ -541,6 +541,8 @@
# For instance in BOM Explosion Item child table, the items coming from sub assembly items
for item in sorted(item_dict.values(), key=lambda d: d['idx'] or 9999):
self.append('required_items', {
+ 'rate': item.rate,
+ 'amount': item.amount,
'operation': item.operation,
'item_code': item.item_code,
'item_name': item.item_name,
@@ -637,9 +639,10 @@
filters = filters, fields = ['operation'], as_list=1)
@frappe.whitelist()
-def get_item_details(item, project = None):
+def get_item_details(item, project = None, skip_bom_info=False):
res = frappe.db.sql("""
- select stock_uom, description
+ select stock_uom, description, item_name, allow_alternative_item,
+ include_item_in_manufacturing
from `tabItem`
where disabled=0
and (end_of_life is null or end_of_life='0000-00-00' or end_of_life > %s)
@@ -650,6 +653,7 @@
return {}
res = res[0]
+ if skip_bom_info: return res
filters = {"item": item, "is_default": 1}
@@ -681,7 +685,7 @@
return res
@frappe.whitelist()
-def make_work_order(bom_no, item, qty=0, project=None):
+def make_work_order(bom_no, item, qty=0, project=None, variant_items=None):
if not frappe.has_permission("Work Order", "write"):
frappe.throw(_("Not permitted"), frappe.PermissionError)
@@ -696,8 +700,44 @@
wo_doc.qty = flt(qty)
wo_doc.get_items_and_operations_from_bom()
+ if variant_items:
+ add_variant_item(variant_items, wo_doc, bom_no, "required_items")
+
return wo_doc
+def add_variant_item(variant_items, wo_doc, bom_no, table_name="items"):
+ if isinstance(variant_items, string_types):
+ variant_items = json.loads(variant_items)
+
+ for item in variant_items:
+ args = frappe._dict({
+ "item_code": item.get("varint_item_code"),
+ "required_qty": item.get("qty"),
+ "qty": item.get("qty"), # for bom
+ "source_warehouse": item.get("source_warehouse"),
+ "operation": item.get("operation")
+ })
+
+ bom_doc = frappe.get_cached_doc("BOM", bom_no)
+ item_data = get_item_details(args.item_code, skip_bom_info=True)
+ args.update(item_data)
+
+ args["rate"] = get_bom_item_rate({
+ "item_code": args.get("item_code"),
+ "qty": args.get("required_qty"),
+ "uom": args.get("stock_uom"),
+ "stock_uom": args.get("stock_uom"),
+ "conversion_factor": 1
+ }, bom_doc)
+
+ if not args.source_warehouse:
+ args["source_warehouse"] = get_item_defaults(item.get("varint_item_code"),
+ wo_doc.company).default_warehouse
+
+ args["amount"] = flt(args.get("required_qty")) * flt(args.get("rate"))
+ args["uom"] = item_data.stock_uom
+ wo_doc.append(table_name, args)
+
@frappe.whitelist()
def check_if_scrap_warehouse_mandatory(bom_no):
res = {"set_scrap_wh_mandatory": False }
diff --git a/erpnext/manufacturing/doctype/work_order_item/work_order_item.json b/erpnext/manufacturing/doctype/work_order_item/work_order_item.json
index 4442162..3acf572 100644
--- a/erpnext/manufacturing/doctype/work_order_item/work_order_item.json
+++ b/erpnext/manufacturing/doctype/work_order_item/work_order_item.json
@@ -1,526 +1,144 @@
{
- "allow_copy": 0,
- "allow_events_in_timeline": 0,
- "allow_guest_to_view": 0,
- "allow_import": 0,
- "allow_rename": 0,
- "beta": 0,
- "creation": "2016-04-18 07:38:26.314642",
- "custom": 0,
- "docstatus": 0,
- "doctype": "DocType",
- "document_type": "",
- "editable_grid": 1,
- "engine": "InnoDB",
+ "actions": [],
+ "creation": "2016-04-18 07:38:26.314642",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "operation",
+ "item_code",
+ "source_warehouse",
+ "column_break_3",
+ "item_name",
+ "description",
+ "allow_alternative_item",
+ "include_item_in_manufacturing",
+ "qty_section",
+ "required_qty",
+ "rate",
+ "amount",
+ "column_break_11",
+ "transferred_qty",
+ "consumed_qty",
+ "available_qty_at_source_warehouse",
+ "available_qty_at_wip_warehouse"
+ ],
"fields": [
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "operation",
- "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": "Operation",
- "length": 0,
- "no_copy": 0,
- "options": "Operation",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "operation",
+ "fieldtype": "Link",
+ "label": "Operation",
+ "options": "Operation"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 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": 1,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "item_code",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "Item Code",
+ "options": "Item"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "source_warehouse",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 1,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Source Warehouse",
- "length": 0,
- "no_copy": 0,
- "options": "Warehouse",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "source_warehouse",
+ "fieldtype": "Link",
+ "ignore_user_permissions": 1,
+ "in_list_view": 1,
+ "label": "Source Warehouse",
+ "options": "Warehouse"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "column_break_3",
- "fieldtype": "Column Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "column_break_3",
+ "fieldtype": "Column Break"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "item_name",
- "fieldtype": "Data",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Item Name",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 1,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "item_name",
+ "fieldtype": "Data",
+ "label": "Item Name",
+ "read_only": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "description",
- "fieldtype": "Text",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Description",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 1,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "description",
+ "fieldtype": "Text",
+ "label": "Description",
+ "read_only": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "qty_section",
- "fieldtype": "Section Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Qty",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "qty_section",
+ "fieldtype": "Section Break",
+ "label": "Qty"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "required_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": "Required Qty",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 1,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "required_qty",
+ "fieldtype": "Float",
+ "in_list_view": 1,
+ "label": "Required Qty"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "depends_on": "eval:!parent.skip_transfer",
- "fieldname": "transferred_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": "Transferred Qty",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 1,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "depends_on": "eval:!parent.skip_transfer",
+ "fieldname": "transferred_qty",
+ "fieldtype": "Float",
+ "in_list_view": 1,
+ "label": "Transferred Qty",
+ "read_only": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "allow_alternative_item",
- "fieldtype": "Check",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Allow Alternative Item",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "default": "0",
+ "fieldname": "allow_alternative_item",
+ "fieldtype": "Check",
+ "label": "Allow Alternative Item"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "include_item_in_manufacturing",
- "fieldtype": "Check",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Include Item In Manufacturing",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "default": "0",
+ "fieldname": "include_item_in_manufacturing",
+ "fieldtype": "Check",
+ "label": "Include Item In Manufacturing"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "column_break_11",
- "fieldtype": "Column Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "column_break_11",
+ "fieldtype": "Column Break"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "depends_on": "eval:!parent.skip_transfer",
- "fieldname": "consumed_qty",
- "fieldtype": "Float",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Consumed Qty",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 1,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "depends_on": "eval:!parent.skip_transfer",
+ "fieldname": "consumed_qty",
+ "fieldtype": "Float",
+ "in_list_view": 1,
+ "label": "Consumed Qty",
+ "read_only": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "available_qty_at_source_warehouse",
- "fieldtype": "Float",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Available Qty at Source Warehouse",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 1,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "available_qty_at_source_warehouse",
+ "fieldtype": "Float",
+ "label": "Available Qty at Source Warehouse",
+ "read_only": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "available_qty_at_wip_warehouse",
- "fieldtype": "Float",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Available Qty at WIP Warehouse",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 1,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
+ "fieldname": "available_qty_at_wip_warehouse",
+ "fieldtype": "Float",
+ "label": "Available Qty at WIP Warehouse",
+ "read_only": 1
+ },
+ {
+ "fieldname": "rate",
+ "fieldtype": "Currency",
+ "label": "Rate",
+ "read_only": 1
+ },
+ {
+ "fieldname": "amount",
+ "fieldtype": "Currency",
+ "label": "Amount",
+ "read_only": 1
}
- ],
- "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-11-20 19:04:38.508839",
- "modified_by": "Administrator",
- "module": "Manufacturing",
- "name": "Work Order Item",
- "name_case": "",
- "owner": "Administrator",
- "permissions": [],
- "quick_entry": 1,
- "read_only": 0,
- "read_only_onload": 0,
- "show_name_in_global_search": 0,
- "sort_field": "modified",
- "sort_order": "DESC",
- "track_changes": 1,
- "track_seen": 0,
- "track_views": 0
+ ],
+ "istable": 1,
+ "links": [],
+ "modified": "2020-04-13 18:46:32.966416",
+ "modified_by": "Administrator",
+ "module": "Manufacturing",
+ "name": "Work Order Item",
+ "owner": "Administrator",
+ "permissions": [],
+ "quick_entry": 1,
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "track_changes": 1
}
\ No newline at end of file
diff --git a/erpnext/stock/doctype/item/item.py b/erpnext/stock/doctype/item/item.py
index 7a1c127..3436a5d 100644
--- a/erpnext/stock/doctype/item/item.py
+++ b/erpnext/stock/doctype/item/item.py
@@ -1144,6 +1144,17 @@
item.clear_cache()
@frappe.whitelist()
+def get_item_details(item_code, company=None):
+ out = frappe._dict()
+ if company:
+ out = get_item_defaults(item_code, company) or frappe._dict()
+
+ doc = frappe.get_cached_doc("Item", item_code)
+ out.update(doc.as_dict())
+
+ return out
+
+@frappe.whitelist()
def get_uom_conv_factor(uom, stock_uom):
uoms = [uom, stock_uom]
value = ""