feat: Value Based and Numeric Quality Inspection
- Acceptance Formula is optional - Choose between Value based and Numeric QI - If numeric, select single or multiple readings - Added Min, Max and Mean Values for numeric inspection to avoid formula usage - Deprecated code cleanup in js file
This commit is contained in:
parent
d4ee05c5fb
commit
d6277cdc7f
@ -8,8 +8,14 @@
|
||||
"field_order": [
|
||||
"specification",
|
||||
"value",
|
||||
"value_based",
|
||||
"single_reading",
|
||||
"column_break_3",
|
||||
"acceptance_formula"
|
||||
"formula_based_criteria",
|
||||
"acceptance_formula",
|
||||
"min_value",
|
||||
"max_value",
|
||||
"mean_value"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
@ -24,10 +30,11 @@
|
||||
"width": "200px"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:(!doc.formula_based_criteria && doc.value_based)",
|
||||
"fieldname": "value",
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
"label": "Acceptance Criteria",
|
||||
"label": "Acceptance Criteria Value",
|
||||
"oldfieldname": "value",
|
||||
"oldfieldtype": "Data"
|
||||
},
|
||||
@ -36,17 +43,56 @@
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"description": "Simple Python formula based on numeric Readings.<br> Example 1: <b>reading_1 > 0.2 and reading_1 < 0.5</b><br>\nExample 2: <b>(reading_1 + reading_2) / 2 < 10</b>",
|
||||
"depends_on": "formula_based_criteria",
|
||||
"description": "Simple Python formula applied on Reading fields.<br> Numeric eg.: <b>reading_1 > 0.2 and reading_1 < 0.5</b><br>\nValue based eg.: <b>reading_value in (\"A\", \"B\", \"C)</b>",
|
||||
"fieldname": "acceptance_formula",
|
||||
"fieldtype": "Code",
|
||||
"in_list_view": 1,
|
||||
"label": "Acceptance Criteria Formula"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "formula_based_criteria",
|
||||
"fieldtype": "Check",
|
||||
"label": "Formula Based Criteria"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "eval:!doc.value_based",
|
||||
"fieldname": "single_reading",
|
||||
"fieldtype": "Check",
|
||||
"label": "Single Reading"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:(!doc.formula_based_criteria && !doc.single_reading && !doc.value_based)",
|
||||
"fieldname": "mean_value",
|
||||
"fieldtype": "Float",
|
||||
"label": "Mean Value"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:(!doc.formula_based_criteria && !doc.value_based)",
|
||||
"fieldname": "min_value",
|
||||
"fieldtype": "Float",
|
||||
"label": "Minimum Value"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:(!doc.formula_based_criteria && !doc.value_based)",
|
||||
"fieldname": "max_value",
|
||||
"fieldtype": "Float",
|
||||
"label": "Maximum Value"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"description": "Non-numeric Inspection.",
|
||||
"fieldname": "value_based",
|
||||
"fieldtype": "Check",
|
||||
"label": "Value Based"
|
||||
}
|
||||
],
|
||||
"idx": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-11-16 16:33:42.421842",
|
||||
"modified": "2020-12-18 21:03:29.828723",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Stock",
|
||||
"name": "Item Quality Inspection Parameter",
|
||||
|
@ -4,6 +4,54 @@
|
||||
cur_frm.cscript.refresh = cur_frm.cscript.inspection_type;
|
||||
|
||||
frappe.ui.form.on("Quality Inspection", {
|
||||
setup: function(frm) {
|
||||
frm.set_query("batch_no", function() {
|
||||
return {
|
||||
filters: {
|
||||
"item": frm.doc.item_code
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Serial No based on item_code
|
||||
frm.set_query("item_serial_no", function() {
|
||||
var filters = {};
|
||||
if (frm.doc.item_code) {
|
||||
filters = {
|
||||
'item_code': frm.doc.item_code
|
||||
}
|
||||
}
|
||||
return { filters: filters }
|
||||
});
|
||||
|
||||
// item code based on GRN/DN
|
||||
frm.set_query("item_code", function(doc) {
|
||||
let doctype = doc.reference_type;
|
||||
|
||||
if (doc.reference_type !== "Job Card") {
|
||||
doctype = (doc.reference_type == "Stock Entry") ?
|
||||
"Stock Entry Detail" : doc.reference_type + " Item";
|
||||
}
|
||||
|
||||
if (doc.reference_type && doc.reference_name) {
|
||||
let filters = {
|
||||
"from": doctype,
|
||||
"inspection_type": doc.inspection_type
|
||||
};
|
||||
|
||||
if (doc.reference_type == doctype)
|
||||
filters["reference_name"] = doc.reference_name;
|
||||
else
|
||||
filters["parent"] = doc.reference_name;
|
||||
|
||||
return {
|
||||
query: "erpnext.stock.doctype.quality_inspection.quality_inspection.item_query",
|
||||
filters: filters
|
||||
};
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
item_code: function(frm) {
|
||||
if (frm.doc.item_code) {
|
||||
return frm.call({
|
||||
@ -26,55 +74,5 @@ frappe.ui.form.on("Quality Inspection", {
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// item code based on GRN/DN
|
||||
cur_frm.fields_dict['item_code'].get_query = function(doc, cdt, cdn) {
|
||||
let doctype = doc.reference_type;
|
||||
|
||||
if (doc.reference_type !== "Job Card") {
|
||||
doctype = (doc.reference_type == "Stock Entry") ?
|
||||
"Stock Entry Detail" : doc.reference_type + " Item";
|
||||
}
|
||||
|
||||
if (doc.reference_type && doc.reference_name) {
|
||||
let filters = {
|
||||
"from": doctype,
|
||||
"inspection_type": doc.inspection_type
|
||||
};
|
||||
|
||||
if (doc.reference_type == doctype)
|
||||
filters["reference_name"] = doc.reference_name;
|
||||
else
|
||||
filters["parent"] = doc.reference_name;
|
||||
|
||||
return {
|
||||
query: "erpnext.stock.doctype.quality_inspection.quality_inspection.item_query",
|
||||
filters: filters
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
// Serial No based on item_code
|
||||
cur_frm.fields_dict['item_serial_no'].get_query = function(doc, cdt, cdn) {
|
||||
var filters = {};
|
||||
if (doc.item_code) {
|
||||
filters = {
|
||||
'item_code': doc.item_code
|
||||
}
|
||||
}
|
||||
return { filters: filters }
|
||||
}
|
||||
|
||||
cur_frm.set_query("batch_no", function(doc) {
|
||||
return {
|
||||
filters: {
|
||||
"item": doc.item_code
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
cur_frm.add_fetch('item_code', 'item_name', 'item_name');
|
||||
cur_frm.add_fetch('item_code', 'description', 'description');
|
||||
|
||||
},
|
||||
})
|
@ -136,6 +136,7 @@
|
||||
"width": "50%"
|
||||
},
|
||||
{
|
||||
"fetch_from": "item_code.item_name",
|
||||
"fieldname": "item_name",
|
||||
"fieldtype": "Data",
|
||||
"in_global_search": 1,
|
||||
@ -143,6 +144,7 @@
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fetch_from": "item_code.description",
|
||||
"fieldname": "description",
|
||||
"fieldtype": "Small Text",
|
||||
"label": "Description",
|
||||
@ -236,7 +238,7 @@
|
||||
"index_web_pages_for_search": 1,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-11-19 17:06:05.409963",
|
||||
"modified": "2020-12-18 19:59:55.710300",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Stock",
|
||||
"name": "Quality Inspection",
|
||||
|
@ -6,7 +6,7 @@ import frappe
|
||||
from frappe.model.document import Document
|
||||
from frappe.model.mapper import get_mapped_doc
|
||||
from frappe import _
|
||||
from frappe.utils import flt
|
||||
from frappe.utils import flt, cint
|
||||
from erpnext.stock.doctype.quality_inspection_template.quality_inspection_template \
|
||||
import get_template_details
|
||||
|
||||
@ -16,7 +16,7 @@ class QualityInspection(Document):
|
||||
self.get_item_specification_details()
|
||||
|
||||
if self.readings:
|
||||
self.set_status_based_on_acceptance_formula()
|
||||
self.inspect_and_set_status()
|
||||
|
||||
def get_item_specification_details(self):
|
||||
if not self.quality_inspection_template:
|
||||
@ -29,9 +29,7 @@ class QualityInspection(Document):
|
||||
parameters = get_template_details(self.quality_inspection_template)
|
||||
for d in parameters:
|
||||
child = self.append('readings', {})
|
||||
child.specification = d.specification
|
||||
child.value = d.value
|
||||
child.acceptance_formula = d.acceptance_formula
|
||||
child.update(d)
|
||||
child.status = "Accepted"
|
||||
|
||||
def get_quality_inspection_template(self):
|
||||
@ -76,28 +74,84 @@ class QualityInspection(Document):
|
||||
""".format(parent_doc=self.reference_type, child_doc=doctype),
|
||||
(quality_inspection, self.modified, self.reference_name, self.item_code))
|
||||
|
||||
def set_status_based_on_acceptance_formula(self):
|
||||
def inspect_and_set_status(self):
|
||||
for reading in self.readings:
|
||||
if not reading.acceptance_formula: continue
|
||||
if reading.formula_based_criteria:
|
||||
self.set_status_based_on_acceptance_formula(reading)
|
||||
else:
|
||||
self.set_status_based_on_acceptance_values(reading)
|
||||
|
||||
def set_status_based_on_acceptance_values(self, reading):
|
||||
if cint(reading.value_based):
|
||||
result = reading.get("reading_value") == reading.get("value")
|
||||
else:
|
||||
# numeric readings
|
||||
if cint(reading.single_reading):
|
||||
reading_1 = flt(reading.get("reading_1"))
|
||||
result = flt(reading.get("min_value")) <= reading_1 <= flt(reading.get("max_value"))
|
||||
else:
|
||||
result = self.min_max_criteria_passed(reading) and self.mean_criteria_passed(reading)
|
||||
|
||||
reading.status = "Accepted" if result else "Rejected"
|
||||
|
||||
def min_max_criteria_passed(self, reading):
|
||||
"""Determine whether all readings fall in the acceptable range."""
|
||||
for i in range(1, 11):
|
||||
reading_field = reading.get("reading_" + str(i))
|
||||
if reading_field is not None:
|
||||
result = flt(reading.get("min_value")) <= flt(reading_field) <= flt(reading.get("max_value"))
|
||||
if not result: return False
|
||||
return True
|
||||
|
||||
def mean_criteria_passed(self, reading):
|
||||
"""Determine whether mean of all readings is acceptable."""
|
||||
if reading.get("mean_value"):
|
||||
from statistics import mean
|
||||
readings_list = []
|
||||
|
||||
condition = reading.acceptance_formula
|
||||
data = {}
|
||||
for i in range(1, 11):
|
||||
field = "reading_" + str(i)
|
||||
data[field] = flt(reading.get(field)) or 0
|
||||
reading_value = reading.get("reading_" + str(i))
|
||||
if reading_value is not None:
|
||||
readings_list.append(flt(reading_value))
|
||||
|
||||
try:
|
||||
result = frappe.safe_eval(condition, None, data)
|
||||
reading.status = "Accepted" if result else "Rejected"
|
||||
except SyntaxError:
|
||||
frappe.throw(_("Row #{0}: Acceptance Criteria Formula is incorrect.").format(reading.idx),
|
||||
title=_("Invalid Formula"))
|
||||
except NameError as e:
|
||||
field = frappe.bold(e.args[0].split()[1])
|
||||
frappe.throw(_("Row #{0}: {1} is not a valid reading field. Please refer to the field description.")
|
||||
.format(reading.idx, field),
|
||||
title=_("Invalid Formula"))
|
||||
actual_mean = mean(readings_list) if readings_list else 0
|
||||
return True if actual_mean == reading.get("mean_value") else False
|
||||
|
||||
return True # no mean value, nothing to check
|
||||
|
||||
def set_status_based_on_acceptance_formula(self, reading):
|
||||
if not reading.acceptance_formula:
|
||||
frappe.throw(_("Row #{0}: Acceptance Criteria Formula is required.").format(reading.idx),
|
||||
title=_("Missing Formula"))
|
||||
|
||||
condition = reading.acceptance_formula
|
||||
data = self.get_formula_evaluation_data(reading)
|
||||
|
||||
try:
|
||||
result = frappe.safe_eval(condition, None, data)
|
||||
reading.status = "Accepted" if result else "Rejected"
|
||||
except NameError as e:
|
||||
field = frappe.bold(e.args[0].split()[1])
|
||||
frappe.throw(_("Row #{0}: {1} is not a valid reading field. Please refer to the field description.")
|
||||
.format(reading.idx, field),
|
||||
title=_("Invalid Formula"))
|
||||
except Exception:
|
||||
frappe.throw(_("Row #{0}: Acceptance Criteria Formula is incorrect.").format(reading.idx),
|
||||
title=_("Invalid Formula"))
|
||||
|
||||
def get_formula_evaluation_data(self, reading):
|
||||
data = {}
|
||||
if cint(reading.value_based):
|
||||
data = {"reading_value": reading.get("reading_value")}
|
||||
else:
|
||||
# numeric readings
|
||||
data = {"reading_1": flt(reading.get("reading_1"))}
|
||||
if not cint(reading.single_reading):
|
||||
# if multiple numeric readings add all readings to data
|
||||
for i in range(2, 11):
|
||||
field = "reading_" + str(i)
|
||||
data[field] = flt(reading.get(field))
|
||||
return data
|
||||
|
||||
@frappe.whitelist()
|
||||
@frappe.validate_and_sanitize_search_inputs
|
||||
|
@ -7,21 +7,30 @@
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"specification",
|
||||
"value",
|
||||
"status",
|
||||
"value",
|
||||
"value_based",
|
||||
"column_break_4",
|
||||
"formula_based_criteria",
|
||||
"acceptance_formula",
|
||||
"min_value",
|
||||
"max_value",
|
||||
"mean_value",
|
||||
"section_break_3",
|
||||
"reading_value",
|
||||
"section_break_14",
|
||||
"single_reading",
|
||||
"section_break_12",
|
||||
"reading_1",
|
||||
"reading_2",
|
||||
"reading_3",
|
||||
"column_break_10",
|
||||
"reading_4",
|
||||
"column_break_10",
|
||||
"reading_5",
|
||||
"reading_6",
|
||||
"column_break_14",
|
||||
"reading_7",
|
||||
"reading_8",
|
||||
"column_break_14",
|
||||
"reading_9",
|
||||
"reading_10"
|
||||
],
|
||||
@ -38,10 +47,11 @@
|
||||
},
|
||||
{
|
||||
"columns": 2,
|
||||
"depends_on": "eval:(!doc.formula_based_criteria && doc.value_based)",
|
||||
"fieldname": "value",
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
"label": "Acceptance Criteria",
|
||||
"label": "Acceptance Criteria Value",
|
||||
"oldfieldname": "value",
|
||||
"oldfieldtype": "Data"
|
||||
},
|
||||
@ -56,6 +66,7 @@
|
||||
},
|
||||
{
|
||||
"columns": 1,
|
||||
"depends_on": "eval:!doc.single_reading",
|
||||
"fieldname": "reading_2",
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
@ -65,6 +76,7 @@
|
||||
},
|
||||
{
|
||||
"columns": 1,
|
||||
"depends_on": "eval:!doc.single_reading",
|
||||
"fieldname": "reading_3",
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
@ -73,6 +85,7 @@
|
||||
"oldfieldtype": "Data"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:!doc.single_reading",
|
||||
"fieldname": "reading_4",
|
||||
"fieldtype": "Data",
|
||||
"label": "Reading 4",
|
||||
@ -80,6 +93,7 @@
|
||||
"oldfieldtype": "Data"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:!doc.single_reading",
|
||||
"fieldname": "reading_5",
|
||||
"fieldtype": "Data",
|
||||
"label": "Reading 5",
|
||||
@ -87,6 +101,7 @@
|
||||
"oldfieldtype": "Data"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:!doc.single_reading",
|
||||
"fieldname": "reading_6",
|
||||
"fieldtype": "Data",
|
||||
"label": "Reading 6",
|
||||
@ -94,6 +109,7 @@
|
||||
"oldfieldtype": "Data"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:!doc.single_reading",
|
||||
"fieldname": "reading_7",
|
||||
"fieldtype": "Data",
|
||||
"label": "Reading 7",
|
||||
@ -101,6 +117,7 @@
|
||||
"oldfieldtype": "Data"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:!doc.single_reading",
|
||||
"fieldname": "reading_8",
|
||||
"fieldtype": "Data",
|
||||
"label": "Reading 8",
|
||||
@ -108,6 +125,7 @@
|
||||
"oldfieldtype": "Data"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:!doc.single_reading",
|
||||
"fieldname": "reading_9",
|
||||
"fieldtype": "Data",
|
||||
"label": "Reading 9",
|
||||
@ -115,6 +133,7 @@
|
||||
"oldfieldtype": "Data"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:!doc.single_reading",
|
||||
"fieldname": "reading_10",
|
||||
"fieldtype": "Data",
|
||||
"label": "Reading 10",
|
||||
@ -133,15 +152,18 @@
|
||||
"options": "Accepted\nRejected"
|
||||
},
|
||||
{
|
||||
"depends_on": "value_based",
|
||||
"fieldname": "section_break_3",
|
||||
"fieldtype": "Section Break"
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Value Based Inspection"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_4",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"description": "Simple Python formula based on numeric Readings.<br> Example 1: <b>reading_1 > 0.2 and reading_1 < 0.5</b><br>\nExample 2: <b>(reading_1 + reading_2) / 2 < 10</b>",
|
||||
"depends_on": "formula_based_criteria",
|
||||
"description": "Simple Python formula applied on Reading fields.<br> Numeric eg.: <b>reading_1 > 0.2 and reading_1 < 0.5</b><br>\nValue based eg.: <b>reading_value in (\"A\", \"B\", \"C)</b>",
|
||||
"fieldname": "acceptance_formula",
|
||||
"fieldtype": "Code",
|
||||
"label": "Acceptance Criteria Formula"
|
||||
@ -153,12 +175,69 @@
|
||||
{
|
||||
"fieldname": "column_break_14",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "formula_based_criteria",
|
||||
"fieldtype": "Check",
|
||||
"label": "Formula Based Criteria"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:(!doc.formula_based_criteria && !doc.single_reading && !doc.value_based)",
|
||||
"fieldname": "mean_value",
|
||||
"fieldtype": "Float",
|
||||
"label": "Mean Value"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "single_reading",
|
||||
"fieldtype": "Check",
|
||||
"label": "Single Reading"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:!doc.value_based",
|
||||
"fieldname": "section_break_12",
|
||||
"fieldtype": "Section Break",
|
||||
"hide_border": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:(!doc.formula_based_criteria && !doc.value_based)",
|
||||
"description": "Applied on each reading.",
|
||||
"fieldname": "min_value",
|
||||
"fieldtype": "Float",
|
||||
"label": "Minimum Value"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:(!doc.formula_based_criteria && !doc.value_based)",
|
||||
"description": "Applied on each reading.",
|
||||
"fieldname": "max_value",
|
||||
"fieldtype": "Float",
|
||||
"label": "Maximum Value"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"description": "Non-numeric Inspection.",
|
||||
"fieldname": "value_based",
|
||||
"fieldtype": "Check",
|
||||
"label": "Value Based"
|
||||
},
|
||||
{
|
||||
"depends_on": "value_based",
|
||||
"fieldname": "reading_value",
|
||||
"fieldtype": "Data",
|
||||
"label": "Reading Value"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:!doc.value_based",
|
||||
"fieldname": "section_break_14",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Numeric Inspection"
|
||||
}
|
||||
],
|
||||
"idx": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-11-16 16:34:29.947856",
|
||||
"modified": "2020-12-18 21:02:04.865777",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Stock",
|
||||
"name": "Quality Inspection Reading",
|
||||
|
@ -13,6 +13,8 @@ def get_template_details(template):
|
||||
if not template: return []
|
||||
|
||||
return frappe.get_all('Item Quality Inspection Parameter',
|
||||
fields=["specification", "value", "acceptance_formula"],
|
||||
fields=["specification", "value", "acceptance_formula",
|
||||
"value_based", "formula_based_criteria", "single_reading",
|
||||
"min_value", "max_value", "mean_value"],
|
||||
filters={'parenttype': 'Quality Inspection Template', 'parent': template},
|
||||
order_by="idx")
|
Loading…
x
Reference in New Issue
Block a user