fix: BOM UX

This commit is contained in:
Rohit Waghchaure 2019-11-21 16:35:47 +05:30
parent 26c46282e8
commit 9326fb78f2
11 changed files with 234 additions and 918 deletions

View File

@ -5,6 +5,12 @@ frappe.provide("erpnext.bom");
frappe.ui.form.on("BOM", {
setup: function(frm) {
frm.custom_make_buttons = {
'BOM': 'Duplicate BOM',
'Work Order': 'Work Order',
'Quality Inspection': 'Quality Inspection'
};
frm.set_query("bom_no", "items", function() {
return {
filters: {
@ -85,9 +91,21 @@ frappe.ui.form.on("BOM", {
}
if(frm.doc.docstatus!=0) {
frm.add_custom_button(__("Duplicate"), function() {
frm.add_custom_button(__("Duplicate BOM"), function() {
frm.copy_doc();
});
}, __("Create"));
frm.add_custom_button(__("Work Order"), function() {
frm.trigger("make_work_order");
}, __("Create"));
if (frm.doc.inspection_required) {
frm.add_custom_button(__("Quality Inspection"), function() {
frm.trigger("make_quality_inspection");
}, __("Create"));
}
frm.page.set_inner_btn_group_as_primary(__('Create'));
}
if(frm.doc.items && frm.doc.allow_alternative_item) {
@ -109,6 +127,41 @@ frappe.ui.form.on("BOM", {
}
},
make_work_order: function(frm) {
const fields = [{
fieldtype: 'Float',
label: __('Qty To Manufacture'),
fieldname: 'qty',
reqd: 1,
default: 1
}];
frappe.prompt(fields, data => {
frappe.call({
method: "erpnext.manufacturing.doctype.work_order.work_order.make_work_order",
args: {
item: frm.doc.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"));
},
make_quality_inspection: function(frm) {
frappe.model.open_mapped_doc({
method: "erpnext.stock.doctype.quality_inspection.quality_inspection.make_quality_inspection",
frm: frm
})
},
update_cost: function(frm) {
return frappe.call({
doc: frm.doc,

View File

@ -3,33 +3,36 @@
"creation": "2013-01-22 15:11:38",
"doctype": "DocType",
"document_type": "Setup",
"engine": "InnoDB",
"field_order": [
"item",
"item_name",
"image",
"uom",
"quantity",
"set_rate_of_sub_assembly_item_based_on_bom",
"cb0",
"is_active",
"is_default",
"with_operations",
"inspection_required",
"allow_alternative_item",
"allow_same_item_multiple_times",
"set_rate_of_sub_assembly_item_based_on_bom",
"quality_inspection_template",
"image",
"item_name",
"uom",
"currency_detail",
"company",
"transfer_material_against",
"project",
"conversion_rate",
"column_break_12",
"currency",
"rm_cost_as_per",
"buying_price_list",
"operations_section",
"section_break_21",
"with_operations",
"column_break_23",
"transfer_material_against",
"routing",
"operations_section",
"operations",
"materials_section",
"inspection_required",
"quality_inspection_template",
"items",
"scrap_section",
"scrap_items",
@ -41,14 +44,9 @@
"base_operating_cost",
"base_raw_material_cost",
"base_scrap_material_cost",
"total_cost_of_bom",
"total_cost",
"column_break_26",
"total_cost",
"base_total_cost",
"more_info_section",
"project",
"amended_from",
"col_break23",
"section_break_25",
"description",
"column_break_27",
@ -57,12 +55,14 @@
"website_section",
"show_in_website",
"route",
"column_break_52",
"website_image",
"thumbnail",
"sb_web_spec",
"web_long_description",
"show_items",
"show_operations"
"show_operations",
"web_long_description",
"amended_from"
],
"fields": [
{
@ -152,7 +152,7 @@
"default": "0",
"fieldname": "inspection_required",
"fieldtype": "Check",
"label": "Inspection Required"
"label": "Quality Inspection Required"
},
{
"default": "0",
@ -160,12 +160,6 @@
"fieldtype": "Check",
"label": "Allow Alternative Item"
},
{
"default": "0",
"fieldname": "allow_same_item_multiple_times",
"fieldtype": "Check",
"label": "Allow Same Item Multiple Times"
},
{
"allow_on_submit": 1,
"default": "1",
@ -193,6 +187,7 @@
"reqd": 1
},
{
"default": "Work Order",
"fieldname": "transfer_material_against",
"fieldtype": "Select",
"label": "Transfer Material Against",
@ -235,10 +230,10 @@
{
"fieldname": "operations_section",
"fieldtype": "Section Break",
"label": "Operations",
"oldfieldtype": "Section Break"
},
{
"depends_on": "with_operations",
"fieldname": "routing",
"fieldtype": "Link",
"label": "Routing",
@ -335,10 +330,6 @@
"options": "Company:company:default_currency",
"read_only": 1
},
{
"fieldname": "total_cost_of_bom",
"fieldtype": "Section Break"
},
{
"fieldname": "total_cost",
"fieldtype": "Currency",
@ -359,10 +350,6 @@
"print_hide": 1,
"read_only": 1
},
{
"fieldname": "more_info_section",
"fieldtype": "Section Break"
},
{
"fieldname": "project",
"fieldtype": "Link",
@ -381,10 +368,6 @@
"print_hide": 1,
"read_only": 1
},
{
"fieldname": "col_break23",
"fieldtype": "Column Break"
},
{
"fieldname": "section_break_25",
"fieldtype": "Section Break"
@ -481,13 +464,26 @@
"fieldname": "show_operations",
"fieldtype": "Check",
"label": "Show Operations"
},
{
"fieldname": "section_break_21",
"fieldtype": "Section Break",
"label": "Operations"
},
{
"fieldname": "column_break_23",
"fieldtype": "Column Break"
},
{
"fieldname": "column_break_52",
"fieldtype": "Column Break"
}
],
"icon": "fa fa-sitemap",
"idx": 1,
"image_field": "image",
"is_submittable": 1,
"modified": "2019-07-30 17:00:09.665068",
"modified": "2019-11-22 14:35:12.142150",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "BOM",

View File

@ -96,6 +96,7 @@ class BOM(WebsiteGenerator):
def get_routing(self):
if self.routing:
self.set("operations", [])
for d in frappe.get_all("BOM Operation", fields = ["*"],
filters = {'parenttype': 'Routing', 'parent': self.routing}):
child = self.append('operations', d)
@ -289,7 +290,7 @@ class BOM(WebsiteGenerator):
if not valuation_rate:
valuation_rate = frappe.db.get_value("Item", args['item_code'], "valuation_rate")
return valuation_rate
return flt(valuation_rate)
def manage_default_bom(self):
""" Uncheck others if current one is selected as default or
@ -362,15 +363,9 @@ class BOM(WebsiteGenerator):
def validate_materials(self):
""" Validate raw material entries """
def get_duplicates(lst):
seen = set()
seen_add = seen.add
for item in lst:
if item.item_code in seen or seen_add(item.item_code):
yield item
if not self.get('items'):
frappe.throw(_("Raw Materials cannot be blank."))
check_list = []
for m in self.get('items'):
if m.bom_no:
@ -379,16 +374,6 @@ class BOM(WebsiteGenerator):
frappe.throw(_("Quantity required for Item {0} in row {1}").format(m.item_code, m.idx))
check_list.append(m)
if not self.allow_same_item_multiple_times:
duplicate_items = list(get_duplicates(check_list))
if duplicate_items:
li = []
for i in duplicate_items:
li.append("{0} on row {1}".format(i.item_code, i.idx))
duplicate_list = '<br>' + '<br>'.join(li)
frappe.throw(_("Same item has been entered multiple times. {0}").format(duplicate_list))
def check_recursion(self, bom_list=[]):
""" Check whether recursion occurs in any bom"""
bom_list = self.traverse_tree()

View File

@ -17,11 +17,13 @@ def get_data():
},
{
'label': _('Manufacture'),
'items': ['BOM', 'Work Order', 'Job Card', 'Production Plan']
'items': ['BOM', 'Work Order', 'Job Card']
},
{
'label': _('Purchase'),
'label': _('Subcontract'),
'items': ['Purchase Order', 'Purchase Receipt', 'Purchase Invoice']
}
]
],
'disable_create_buttons': ["Item", "Purchase Order", "Purchase Receipt",
"Purchase Invoice", "Job Card", "Stock Entry"]
}

File diff suppressed because it is too large Load Diff

View File

@ -529,7 +529,6 @@ def get_material_request_items(row, sales_order,
required_qty = ceil(required_qty)
if required_qty > 0:
print(row)
return {
'item_code': row.item_code,
'item_name': row.item_name,

View File

@ -609,6 +609,22 @@ def get_item_details(item, project = None):
return res
@frappe.whitelist()
def make_work_order(item, qty=0, project=None):
if not frappe.has_permission("Work Order", "write"):
frappe.throw(_("Not permitted"), frappe.PermissionError)
item_details = get_item_details(item, project)
wo_doc = frappe.new_doc("Work Order")
wo_doc.production_item = item
wo_doc.update(item_details)
if qty > 0:
wo_doc.qty = qty
wo_doc.get_items_and_operations_from_bom()
return wo_doc
@frappe.whitelist()
def check_if_scrap_warehouse_mandatory(bom_no):
res = {"set_scrap_wh_mandatory": False }

View File

@ -15,13 +15,6 @@ def execute():
rename_field(doctype, "allow_transfer_for_manufacture", "include_item_in_manufacturing")
if frappe.db.has_column('BOM', 'allow_same_item_multiple_times'):
frappe.db.sql(""" UPDATE tabBOM
SET
allow_same_item_multiple_times = 0
WHERE
trim(coalesce(allow_same_item_multiple_times, '')) = '' """)
for doctype in ['BOM', 'Work Order']:
frappe.reload_doc('manufacturing', 'doctype', frappe.scrub(doctype))

View File

@ -6,6 +6,7 @@ import frappe
from frappe.model.document import Document
from erpnext.stock.doctype.quality_inspection_template.quality_inspection_template \
import get_template_details
from frappe.model.mapper import get_mapped_doc
class QualityInspection(Document):
def validate(self):
@ -84,3 +85,37 @@ def item_query(doctype, txt, searchfield, start, page_len, filters):
parent=filters.get('parent'), cond = cond, mcond = mcond, start = start,
page_len = page_len, qi_condition = qi_condition),
{'parent': filters.get('parent'), 'txt': "%%%s%%" % txt})
def quality_inspection_query(doctype, txt, searchfield, start, page_len, filters):
return frappe.get_all('Quality Inspection',
limit_start=start,
limit_page_length=page_len,
filters = {
'docstatus': 1,
'name': ('like', '%%%s%%' % txt),
'item_code': filters.get("item_code"),
'reference_name': ('in', [filters.get("reference_name", ''), ''])
}, as_list=1)
@frappe.whitelist()
def make_quality_inspection(source_name, target_doc=None):
def postprocess(source, doc):
doc.inspected_by = frappe.session.user
doc.get_quality_inspection_template()
doc = get_mapped_doc("BOM", source_name, {
'BOM': {
"doctype": "Quality Inspection",
"validation": {
"docstatus": ["=", 1]
},
"field_map": {
"name": "bom_no",
"item": "item_code",
"stock_uom": "uom",
"stock_qty": "qty"
},
}
}, target_doc, postprocess)
return doc

View File

@ -102,11 +102,12 @@ frappe.ui.form.on('Stock Entry', {
frm.set_query("quality_inspection", "items", function(doc, cdt, cdn) {
var d = locals[cdt][cdn];
return {
query:"erpnext.stock.doctype.quality_inspection.quality_inspection.quality_inspection_query",
filters: {
docstatus: 1,
item_code: d.item_code,
reference_name: doc.name
'item_code': d.item_code,
'reference_name': doc.name
}
}
});

View File

@ -91,6 +91,7 @@ class StockEntry(StockController):
self.update_cost_in_project()
self.validate_reserved_serial_no_consumption()
self.update_transferred_qty()
self.update_quality_inspection()
if self.work_order and self.purpose == "Manufacture":
self.update_so_in_serial_number()
@ -108,6 +109,7 @@ class StockEntry(StockController):
self.make_gl_entries_on_cancel()
self.update_cost_in_project()
self.update_transferred_qty()
self.update_quality_inspection()
def set_job_card_data(self):
if self.job_card and not self.work_order:
@ -1285,6 +1287,20 @@ class StockEntry(StockController):
self._update_percent_field_in_targets(args, update_modified=True)
def update_quality_inspection(self):
if self.inspection_required:
reference_type = reference_name = ''
if self.docstatus == 1:
reference_name = self.name
reference_type = 'Stock Entry'
for d in self.items:
if d.quality_inspection:
frappe.db.set_value("Quality Inspection", d.quality_inspection, {
'reference_type': reference_type,
'reference_name': reference_name
})
@frappe.whitelist()
def move_sample_to_retention_warehouse(company, items):
if isinstance(items, string_types):