From a93b514b2f433eb5f4c0ad3dd28e5f219cbb5ae4 Mon Sep 17 00:00:00 2001 From: Rohan Bansal Date: Tue, 6 Apr 2021 17:10:52 +0530 Subject: [PATCH 1/7] feat: create Quality Inspections from account and stock documents --- erpnext/controllers/accounts_controller.py | 32 +++++ erpnext/public/js/controllers/transaction.js | 133 ++++++++++++++++++- 2 files changed, 161 insertions(+), 4 deletions(-) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 36d399cf19..0c966566fd 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -1412,6 +1412,38 @@ def validate_and_delete_children(parent, data): for d in deleted_children: update_bin_on_delete(d, parent.doctype) + +@frappe.whitelist() +def make_quality_inspections(doctype, docname, items): + items = json.loads(items).get('items') + inspections = [] + + for item in items: + if item.get("sample_size") > item.get("qty"): + frappe.throw(_("{item_name}'s Sample Size ({sample_size}) cannot be greater than the Accepted Quantity ({accepted_quantity})").format( + item_name=item.get("item_name"), + sample_size=item.get("sample_size"), + accepted_quantity=item.get("qty") + )) + + quality_inspection = frappe.get_doc({ + "doctype": "Quality Inspection", + "inspection_type": "Incoming", + "inspected_by": frappe.session.user, + "reference_type": doctype, + "reference_name": docname, + "item_code": item.get("item_code"), + "description": item.get("description"), + "sample_size": item.get("sample_size"), + "item_serial_no": item.get("serial_no").split("\n")[0] if item.get("serial_no") else None, + "batch_no": item.get("batch_no") + }).insert() + quality_inspection.save() + inspections.append(quality_inspection) + + return [get_link_to_form("Quality Inspection", inspection.name) for inspection in inspections] + + @frappe.whitelist() def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, child_docname="items"): def check_doc_permissions(doc, perm_type='create'): diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index 21a20a7bce..da0c87dd85 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -261,11 +261,19 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ if(!in_list(["Delivery Note", "Sales Invoice", "Purchase Receipt", "Purchase Invoice"], this.frm.doc.doctype)) { return; } - var me = this; - var inspection_type = in_list(["Purchase Receipt", "Purchase Invoice"], this.frm.doc.doctype) + + const me = this; + if (!this.frm.is_new() && this.frm.doc.docstatus === 0) { + this.frm.add_custom_button(__("Quality Inspection(s)"), () => { + me.make_quality_inspection(); + }, __("Create")); + this.frm.page.set_inner_btn_group_as_primary(__('Create')); + } + + const inspection_type = in_list(["Purchase Receipt", "Purchase Invoice"], this.frm.doc.doctype) ? "Incoming" : "Outgoing"; - var quality_inspection_field = this.frm.get_docfield("items", "quality_inspection"); + let quality_inspection_field = this.frm.get_docfield("items", "quality_inspection"); quality_inspection_field.get_route_options_for_new_doc = function(row) { if(me.frm.is_new()) return; return { @@ -280,7 +288,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ } this.frm.set_query("quality_inspection", "items", function(doc, cdt, cdn) { - var d = locals[cdt][cdn]; + let d = locals[cdt][cdn]; return { filters: { docstatus: 1, @@ -1909,6 +1917,123 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ }); }, + make_quality_inspection: function () { + let data = []; + const fields = [ + { + label: "Items", + fieldtype: "Table", + fieldname: "items", + cannot_add_rows: true, + in_place_edit: true, + data: data, + get_data: () => { return data }, + fields: [ + { + fieldtype: "Data", + fieldname: "docname", + hidden: true + }, + { + fieldtype: "Read Only", + fieldname: "item_code", + label: __("Item Code"), + in_list_view: true + }, + { + fieldtype: "Read Only", + fieldname: "item_name", + label: __("Item Name"), + in_list_view: true + }, + { + fieldtype: "Float", + fieldname: "qty", + label: __("Accepted Quantity"), + in_list_view: true, + read_only: true + }, + { + fieldtype: "Float", + fieldname: "sample_size", + label: __("Sample Size"), + reqd: true, + in_list_view: true + }, + { + fieldtype: "Data", + fieldname: "description", + label: __("Description"), + hidden: true + }, + { + fieldtype: "Data", + fieldname: "serial_no", + label: __("Serial No"), + hidden: true + }, + { + fieldtype: "Data", + fieldname: "batch_no", + label: __("Batch No"), + hidden: true + } + ] + } + ]; + + const me = this; + const dialog = new frappe.ui.Dialog({ + title: __("Select Items for Quality Inspection"), + fields: fields, + primary_action: function () { + const data = dialog.get_values(); + frappe.call({ + method: "erpnext.controllers.accounts_controller.make_quality_inspections", + args: { + doctype: me.frm.doc.doctype, + docname: me.frm.doc.name, + items: data + }, + freeze: true, + callback: function (r) { + if (r.message) { + frappe.msgprint({ + message: __("Quality Inspections Created: {0}", [r.message.join(", ")]), + indicator: "green" + }) + } + dialog.hide(); + } + }); + }, + primary_action_label: __("Create") + }); + + this.frm.doc.items.forEach(item => { + if (!item.quality_inspection) { + let dialog_items = dialog.fields_dict.items; + dialog_items.df.data.push({ + "docname": item.name, + "item_code": item.item_code, + "item_name": item.item_name, + "qty": item.qty, + "description": item.description, + "serial_no": item.serial_no, + "batch_no": item.batch_no + }); + dialog_items.grid.refresh(); + } + }) + + data = dialog.fields_dict.items.df.data; + if (!data.length) { + frappe.msgprint(__("All items in this document already have a linked Quality Inspection.")); + } else { + dialog.show(); + } + }, + get_method_for_payment: function(){ var method = "erpnext.accounts.doctype.payment_entry.payment_entry.get_payment_entry"; if(cur_frm.doc.__onload && cur_frm.doc.__onload.make_payment_via_journal_entry){ From 7f8b95efe8c6c16cf50faf85480b8f97f8eda089 Mon Sep 17 00:00:00 2001 From: Rohan Bansal Date: Wed, 14 Apr 2021 14:12:03 +0530 Subject: [PATCH 2/7] fix: move QI logic to stock module --- erpnext/controllers/accounts_controller.py | 31 ------------ erpnext/controllers/stock_controller.py | 53 ++++++++++++++++---- erpnext/public/js/controllers/transaction.js | 2 +- 3 files changed, 45 insertions(+), 41 deletions(-) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 0c966566fd..ea0b495c10 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -1413,37 +1413,6 @@ def validate_and_delete_children(parent, data): update_bin_on_delete(d, parent.doctype) -@frappe.whitelist() -def make_quality_inspections(doctype, docname, items): - items = json.loads(items).get('items') - inspections = [] - - for item in items: - if item.get("sample_size") > item.get("qty"): - frappe.throw(_("{item_name}'s Sample Size ({sample_size}) cannot be greater than the Accepted Quantity ({accepted_quantity})").format( - item_name=item.get("item_name"), - sample_size=item.get("sample_size"), - accepted_quantity=item.get("qty") - )) - - quality_inspection = frappe.get_doc({ - "doctype": "Quality Inspection", - "inspection_type": "Incoming", - "inspected_by": frappe.session.user, - "reference_type": doctype, - "reference_name": docname, - "item_code": item.get("item_code"), - "description": item.get("description"), - "sample_size": item.get("sample_size"), - "item_serial_no": item.get("serial_no").split("\n")[0] if item.get("serial_no") else None, - "batch_no": item.get("batch_no") - }).insert() - quality_inspection.save() - inspections.append(quality_inspection) - - return [get_link_to_form("Quality Inspection", inspection.name) for inspection in inspections] - - @frappe.whitelist() def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, child_docname="items"): def check_doc_permissions(doc, perm_type='create'): diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py index f352bae30e..7f8da516aa 100644 --- a/erpnext/controllers/stock_controller.py +++ b/erpnext/controllers/stock_controller.py @@ -1,17 +1,21 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals -import frappe, erpnext -from frappe.utils import cint, flt, cstr, get_link_to_form, today, getdate -from frappe import _ -import frappe.defaults +import json from collections import defaultdict -from erpnext.accounts.utils import get_fiscal_year, check_if_stock_and_account_balance_synced + +import frappe +import frappe.defaults +from frappe import _ +from frappe.utils import cint, cstr, flt, get_link_to_form, getdate + +import erpnext from erpnext.accounts.general_ledger import make_gl_entries, make_reverse_gl_entries, process_gl_map +from erpnext.accounts.utils import check_if_stock_and_account_balance_synced, get_fiscal_year from erpnext.controllers.accounts_controller import AccountsController -from erpnext.stock.stock_ledger import get_valuation_rate from erpnext.stock import get_warehouse_account_map +from erpnext.stock.stock_ledger import get_valuation_rate + class QualityInspectionRequiredError(frappe.ValidationError): pass class QualityInspectionRejectedError(frappe.ValidationError): pass @@ -190,7 +194,6 @@ class StockController(AccountsController): if hasattr(self, "items"): item_doclist = self.get("items") elif self.doctype == "Stock Reconciliation": - import json item_doclist = [] data = json.loads(self.reconciliation_json) for row in data[data.index(self.head_row)+1:]: @@ -320,7 +323,7 @@ class StockController(AccountsController): return serialized_items def validate_warehouse(self): - from erpnext.stock.utils import validate_warehouse_company, validate_disabled_warehouse + from erpnext.stock.utils import validate_disabled_warehouse, validate_warehouse_company warehouses = list(set([d.warehouse for d in self.get("items") if getattr(d, "warehouse", None)])) @@ -501,6 +504,38 @@ class StockController(AccountsController): check_if_stock_and_account_balance_synced(self.posting_date, self.company, self.doctype, self.name) + +@frappe.whitelist() +def make_quality_inspections(doctype, docname, items): + items = json.loads(items).get('items') + inspections = [] + + for item in items: + if item.get("sample_size") > item.get("qty"): + frappe.throw(_("{item_name}'s Sample Size ({sample_size}) cannot be greater than the Accepted Quantity ({accepted_quantity})").format( + item_name=item.get("item_name"), + sample_size=item.get("sample_size"), + accepted_quantity=item.get("qty") + )) + + quality_inspection = frappe.get_doc({ + "doctype": "Quality Inspection", + "inspection_type": "Incoming", + "inspected_by": frappe.session.user, + "reference_type": doctype, + "reference_name": docname, + "item_code": item.get("item_code"), + "description": item.get("description"), + "sample_size": item.get("sample_size"), + "item_serial_no": item.get("serial_no").split("\n")[0] if item.get("serial_no") else None, + "batch_no": item.get("batch_no") + }).insert() + quality_inspection.save() + inspections.append(quality_inspection) + + return [get_link_to_form("Quality Inspection", inspection.name) for inspection in inspections] + + def is_reposting_pending(): return frappe.db.exists("Repost Item Valuation", {'docstatus': 1, 'status': ['in', ['Queued','In Progress']]}) diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index da0c87dd85..0e0a87a8b5 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -1989,7 +1989,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ primary_action: function () { const data = dialog.get_values(); frappe.call({ - method: "erpnext.controllers.accounts_controller.make_quality_inspections", + method: "erpnext.controllers.stock_controller.make_quality_inspections", args: { doctype: me.frm.doc.doctype, docname: me.frm.doc.name, From 03f711e3a2c6ce12fad80a4f375728a077a8a958 Mon Sep 17 00:00:00 2001 From: Rohan Bansal Date: Wed, 14 Apr 2021 14:41:55 +0530 Subject: [PATCH 3/7] style: sider issues --- erpnext/public/js/controllers/transaction.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index 0e0a87a8b5..478b21b441 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -1927,7 +1927,9 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ cannot_add_rows: true, in_place_edit: true, data: data, - get_data: () => { return data }, + get_data: () => { + return data; + }, fields: [ { fieldtype: "Data", @@ -2001,7 +2003,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ frappe.msgprint({ message: __("Quality Inspections Created: {0}", [r.message.join(", ")]), indicator: "green" - }) + }); } dialog.hide(); } @@ -2024,7 +2026,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ }); dialog_items.grid.refresh(); } - }) + }); data = dialog.fields_dict.items.df.data; if (!data.length) { From 1cdf5a0dbad7eea6111839bfc14e4b59f8970f59 Mon Sep 17 00:00:00 2001 From: Rohan Bansal Date: Wed, 26 May 2021 14:42:15 +0530 Subject: [PATCH 4/7] fix: add requested changes --- erpnext/controllers/stock_controller.py | 8 +- erpnext/public/js/controllers/transaction.js | 15 +- .../stock/doctype/stock_entry/stock_entry.js | 130 ++++++++++++++++++ 3 files changed, 144 insertions(+), 9 deletions(-) diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py index 6f97743b60..abc966a477 100644 --- a/erpnext/controllers/stock_controller.py +++ b/erpnext/controllers/stock_controller.py @@ -508,7 +508,7 @@ def make_quality_inspections(doctype, docname, items): inspections = [] for item in items: - if item.get("sample_size") > item.get("qty"): + if flt(item.get("sample_size")) > flt(item.get("qty")): frappe.throw(_("{item_name}'s Sample Size ({sample_size}) cannot be greater than the Accepted Quantity ({accepted_quantity})").format( item_name=item.get("item_name"), sample_size=item.get("sample_size"), @@ -523,14 +523,14 @@ def make_quality_inspections(doctype, docname, items): "reference_name": docname, "item_code": item.get("item_code"), "description": item.get("description"), - "sample_size": item.get("sample_size"), + "sample_size": flt(item.get("sample_size")), "item_serial_no": item.get("serial_no").split("\n")[0] if item.get("serial_no") else None, "batch_no": item.get("batch_no") }).insert() quality_inspection.save() - inspections.append(quality_inspection) + inspections.append(quality_inspection.name) - return [get_link_to_form("Quality Inspection", inspection.name) for inspection in inspections] + return inspections def is_reposting_pending(): diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index 8738957166..95562baa8e 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -2039,11 +2039,16 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ }, freeze: true, callback: function (r) { - if (r.message) { - frappe.msgprint({ - message: __("Quality Inspections Created: {0}", [r.message.join(", ")]), - indicator: "green" - }); + if (r.message.length > 0) { + if (r.message.length === 1) { + frappe.set_route("Form", "Quality Inspection", r.message[0]); + } else { + frappe.route_options = { + "reference_type": me.frm.doc.doctype, + "reference_name": me.frm.doc.name + }; + frappe.set_route("List", "Quality Inspection"); + } } dialog.hide(); } diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.js b/erpnext/stock/doctype/stock_entry/stock_entry.js index de23e769f8..3524c41720 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.js +++ b/erpnext/stock/doctype/stock_entry/stock_entry.js @@ -115,6 +115,13 @@ frappe.ui.form.on('Stock Entry', { return; } + if (!frm.is_new() && frm.doc.docstatus === 0) { + frm.add_custom_button(__("Quality Inspection(s)"), () => { + frm.trigger("make_quality_inspection"); + }, __("Create")); + frm.page.set_inner_btn_group_as_primary(__('Create')); + } + let quality_inspection_field = frm.get_docfield("items", "quality_inspection"); quality_inspection_field.get_route_options_for_new_doc = function(row) { if (frm.is_new()) return; @@ -142,6 +149,129 @@ frappe.ui.form.on('Stock Entry', { }); }, + make_quality_inspection: function (frm) { + let data = []; + const fields = [ + { + label: "Items", + fieldtype: "Table", + fieldname: "items", + cannot_add_rows: true, + in_place_edit: true, + data: data, + get_data: () => { + return data; + }, + fields: [ + { + fieldtype: "Data", + fieldname: "docname", + hidden: true + }, + { + fieldtype: "Read Only", + fieldname: "item_code", + label: __("Item Code"), + in_list_view: true + }, + { + fieldtype: "Read Only", + fieldname: "item_name", + label: __("Item Name"), + in_list_view: true + }, + { + fieldtype: "Float", + fieldname: "qty", + label: __("Accepted Quantity"), + in_list_view: true, + read_only: true + }, + { + fieldtype: "Float", + fieldname: "sample_size", + label: __("Sample Size"), + reqd: true, + in_list_view: true + }, + { + fieldtype: "Data", + fieldname: "description", + label: __("Description"), + hidden: true + }, + { + fieldtype: "Data", + fieldname: "serial_no", + label: __("Serial No"), + hidden: true + }, + { + fieldtype: "Data", + fieldname: "batch_no", + label: __("Batch No"), + hidden: true + } + ] + } + ]; + + const dialog = new frappe.ui.Dialog({ + title: __("Select Items for Quality Inspection"), + fields: fields, + primary_action: function () { + const data = dialog.get_values(); + frappe.call({ + method: "erpnext.controllers.stock_controller.make_quality_inspections", + args: { + doctype: frm.doc.doctype, + docname: frm.doc.name, + items: data + }, + freeze: true, + callback: function (r) { + if (r.message.length > 0) { + if (r.message.length === 1) { + frappe.set_route("Form", "Quality Inspection", r.message[0]); + } else { + frappe.route_options = { + "reference_type": frm.doc.doctype, + "reference_name": frm.doc.name + }; + frappe.set_route("List", "Quality Inspection"); + } + } + dialog.hide(); + } + }); + }, + primary_action_label: __("Create") + }); + + frm.doc.items.forEach(item => { + if (!item.quality_inspection) { + let dialog_items = dialog.fields_dict.items; + dialog_items.df.data.push({ + "docname": item.name, + "item_code": item.item_code, + "item_name": item.item_name, + "qty": item.qty, + "description": item.description, + "serial_no": item.serial_no, + "batch_no": item.batch_no + }); + dialog_items.grid.refresh(); + } + }); + + data = dialog.fields_dict.items.df.data; + if (!data.length) { + frappe.msgprint(__("All items in this document already have a linked Quality Inspection.")); + } else { + dialog.show(); + } + }, + outgoing_stock_entry: function(frm) { frappe.call({ doc: frm.doc, From 1e3a3b27c6e4f66aa38c9adaf86b5bde39293a00 Mon Sep 17 00:00:00 2001 From: Rohan Bansal Date: Wed, 26 May 2021 15:18:10 +0530 Subject: [PATCH 5/7] style: semgrep issues --- erpnext/controllers/accounts_controller.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 2c3519b04a..c77ea55775 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -225,7 +225,7 @@ class AccountsController(TransactionBase): def validate_date_with_fiscal_year(self): if self.meta.get_field("fiscal_year"): - date_field = "" + date_field = None if self.meta.get_field("posting_date"): date_field = "posting_date" elif self.meta.get_field("transaction_date"): From 9857d63523d3bffc1377622cbca2ff1c4c0815d0 Mon Sep 17 00:00:00 2001 From: Rohan Bansal Date: Wed, 26 May 2021 15:41:36 +0530 Subject: [PATCH 6/7] fix: add requested changes --- .../stock/doctype/stock_entry/stock_entry.js | 130 +----------------- 1 file changed, 4 insertions(+), 126 deletions(-) diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.js b/erpnext/stock/doctype/stock_entry/stock_entry.js index 3524c41720..93a6fc0e0a 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.js +++ b/erpnext/stock/doctype/stock_entry/stock_entry.js @@ -117,7 +117,8 @@ frappe.ui.form.on('Stock Entry', { if (!frm.is_new() && frm.doc.docstatus === 0) { frm.add_custom_button(__("Quality Inspection(s)"), () => { - frm.trigger("make_quality_inspection"); + let transaction_controller = new erpnext.TransactionController({ frm: frm }); + transaction_controller.make_quality_inspection(); }, __("Create")); frm.page.set_inner_btn_group_as_primary(__('Create')); } @@ -149,129 +150,6 @@ frappe.ui.form.on('Stock Entry', { }); }, - make_quality_inspection: function (frm) { - let data = []; - const fields = [ - { - label: "Items", - fieldtype: "Table", - fieldname: "items", - cannot_add_rows: true, - in_place_edit: true, - data: data, - get_data: () => { - return data; - }, - fields: [ - { - fieldtype: "Data", - fieldname: "docname", - hidden: true - }, - { - fieldtype: "Read Only", - fieldname: "item_code", - label: __("Item Code"), - in_list_view: true - }, - { - fieldtype: "Read Only", - fieldname: "item_name", - label: __("Item Name"), - in_list_view: true - }, - { - fieldtype: "Float", - fieldname: "qty", - label: __("Accepted Quantity"), - in_list_view: true, - read_only: true - }, - { - fieldtype: "Float", - fieldname: "sample_size", - label: __("Sample Size"), - reqd: true, - in_list_view: true - }, - { - fieldtype: "Data", - fieldname: "description", - label: __("Description"), - hidden: true - }, - { - fieldtype: "Data", - fieldname: "serial_no", - label: __("Serial No"), - hidden: true - }, - { - fieldtype: "Data", - fieldname: "batch_no", - label: __("Batch No"), - hidden: true - } - ] - } - ]; - - const dialog = new frappe.ui.Dialog({ - title: __("Select Items for Quality Inspection"), - fields: fields, - primary_action: function () { - const data = dialog.get_values(); - frappe.call({ - method: "erpnext.controllers.stock_controller.make_quality_inspections", - args: { - doctype: frm.doc.doctype, - docname: frm.doc.name, - items: data - }, - freeze: true, - callback: function (r) { - if (r.message.length > 0) { - if (r.message.length === 1) { - frappe.set_route("Form", "Quality Inspection", r.message[0]); - } else { - frappe.route_options = { - "reference_type": frm.doc.doctype, - "reference_name": frm.doc.name - }; - frappe.set_route("List", "Quality Inspection"); - } - } - dialog.hide(); - } - }); - }, - primary_action_label: __("Create") - }); - - frm.doc.items.forEach(item => { - if (!item.quality_inspection) { - let dialog_items = dialog.fields_dict.items; - dialog_items.df.data.push({ - "docname": item.name, - "item_code": item.item_code, - "item_name": item.item_name, - "qty": item.qty, - "description": item.description, - "serial_no": item.serial_no, - "batch_no": item.batch_no - }); - dialog_items.grid.refresh(); - } - }); - - data = dialog.fields_dict.items.df.data; - if (!data.length) { - frappe.msgprint(__("All items in this document already have a linked Quality Inspection.")); - } else { - dialog.show(); - } - }, - outgoing_stock_entry: function(frm) { frappe.call({ doc: frm.doc, @@ -285,7 +163,7 @@ frappe.ui.form.on('Stock Entry', { refresh: function(frm) { if(!frm.doc.docstatus) { frm.trigger('validate_purpose_consumption'); - frm.add_custom_button(__('Create Material Request'), function() { + frm.add_custom_button(__('Material Request'), function() { frappe.model.with_doctype('Material Request', function() { var mr = frappe.model.get_new_doc('Material Request'); var items = frm.get_field('items').grid.get_selected_children(); @@ -308,7 +186,7 @@ frappe.ui.form.on('Stock Entry', { }); frappe.set_route('Form', 'Material Request', mr.name); }); - }); + }, __("Create")); } if(frm.doc.items) { From a06ec03efcaedeeffaf9b8f9818bfb866f44e2d1 Mon Sep 17 00:00:00 2001 From: Rohan Bansal Date: Wed, 2 Jun 2021 14:55:31 +0530 Subject: [PATCH 7/7] test: add test for new QI function --- erpnext/controllers/stock_controller.py | 5 +- erpnext/public/js/controllers/transaction.js | 2 +- .../test_quality_inspection.py | 152 ++++++++++++------ 3 files changed, 103 insertions(+), 56 deletions(-) diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py index abc966a477..0da723d56e 100644 --- a/erpnext/controllers/stock_controller.py +++ b/erpnext/controllers/stock_controller.py @@ -504,9 +504,10 @@ class StockController(AccountsController): @frappe.whitelist() def make_quality_inspections(doctype, docname, items): - items = json.loads(items).get('items') - inspections = [] + if isinstance(items, str): + items = json.loads(items) + inspections = [] for item in items: if flt(item.get("sample_size")) > flt(item.get("qty")): frappe.throw(_("{item_name}'s Sample Size ({sample_size}) cannot be greater than the Accepted Quantity ({accepted_quantity})").format( diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index 95562baa8e..982b1fe1eb 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -2035,7 +2035,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ args: { doctype: me.frm.doc.doctype, docname: me.frm.doc.name, - items: data + items: data.items }, freeze: true, callback: function (r) { diff --git a/erpnext/stock/doctype/quality_inspection/test_quality_inspection.py b/erpnext/stock/doctype/quality_inspection/test_quality_inspection.py index 56b046a92e..7f3d701034 100644 --- a/erpnext/stock/doctype/quality_inspection/test_quality_inspection.py +++ b/erpnext/stock/doctype/quality_inspection/test_quality_inspection.py @@ -1,29 +1,45 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors and Contributors # See license.txt -from __future__ import unicode_literals -import frappe import unittest + +import frappe from frappe.utils import nowdate -from erpnext.stock.doctype.item.test_item import create_item + +from erpnext.controllers.stock_controller import ( + QualityInspectionNotSubmittedError, + QualityInspectionRejectedError, + QualityInspectionRequiredError, + make_quality_inspections, +) from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note +from erpnext.stock.doctype.item.test_item import create_item from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry -from erpnext.controllers.stock_controller import QualityInspectionRejectedError, QualityInspectionRequiredError, QualityInspectionNotSubmittedError # test_records = frappe.get_test_records('Quality Inspection') + class TestQualityInspection(unittest.TestCase): def setUp(self): create_item("_Test Item with QA") - frappe.db.set_value("Item", "_Test Item with QA", "inspection_required_before_delivery", 1) + frappe.db.set_value( + "Item", "_Test Item with QA", "inspection_required_before_delivery", 1 + ) def test_qa_for_delivery(self): - make_stock_entry(item_code="_Test Item with QA", target="_Test Warehouse - _TC", qty=1, basic_rate=100) + make_stock_entry( + item_code="_Test Item with QA", + target="_Test Warehouse - _TC", + qty=1, + basic_rate=100 + ) dn = create_delivery_note(item_code="_Test Item with QA", do_not_submit=True) self.assertRaises(QualityInspectionRequiredError, dn.submit) - qa = create_quality_inspection(reference_type="Delivery Note", reference_name=dn.name, status="Rejected") + qa = create_quality_inspection( + reference_type="Delivery Note", reference_name=dn.name, status="Rejected" + ) dn.reload() self.assertRaises(QualityInspectionRejectedError, dn.submit) @@ -38,7 +54,9 @@ class TestQualityInspection(unittest.TestCase): def test_qa_not_submit(self): dn = create_delivery_note(item_code="_Test Item with QA", do_not_submit=True) - qa = create_quality_inspection(reference_type="Delivery Note", reference_name=dn.name, do_not_submit=True) + qa = create_quality_inspection( + reference_type="Delivery Note", reference_name=dn.name, do_not_submit=True + ) dn.items[0].quality_inspection = qa.name self.assertRaises(QualityInspectionNotSubmittedError, dn.submit) @@ -48,21 +66,28 @@ class TestQualityInspection(unittest.TestCase): def test_value_based_qi_readings(self): # Test QI based on acceptance values (Non formula) dn = create_delivery_note(item_code="_Test Item with QA", do_not_submit=True) - readings = [{ - "specification": "Iron Content", # numeric reading - "min_value": 0.1, - "max_value": 0.9, - "reading_1": "0.4" - }, - { - "specification": "Particle Inspection Needed", # non-numeric reading - "numeric": 0, - "value": "Yes", - "reading_value": "Yes" - }] + readings = [ + { + "specification": "Iron Content", # numeric reading + "min_value": 0.1, + "max_value": 0.9, + "reading_1": "0.4" + }, + { + "specification": "Particle Inspection Needed", # non-numeric reading + "numeric": 0, + "value": "Yes", + "reading_value": "Yes" + } + ] + + qa = create_quality_inspection( + reference_type="Delivery Note", + reference_name=dn.name, + readings=readings, + do_not_save=True + ) - qa = create_quality_inspection(reference_type="Delivery Note", reference_name=dn.name, - readings=readings, do_not_save=True) qa.save() # status must be auto set as per formula @@ -74,36 +99,43 @@ class TestQualityInspection(unittest.TestCase): def test_formula_based_qi_readings(self): dn = create_delivery_note(item_code="_Test Item with QA", do_not_submit=True) - readings = [{ - "specification": "Iron Content", # numeric reading - "formula_based_criteria": 1, - "acceptance_formula": "reading_1 > 0.35 and reading_1 < 0.50", - "reading_1": "0.4" - }, - { - "specification": "Calcium Content", # numeric reading - "formula_based_criteria": 1, - "acceptance_formula": "reading_1 > 0.20 and reading_1 < 0.50", - "reading_1": "0.7" - }, - { - "specification": "Mg Content", # numeric reading - "formula_based_criteria": 1, - "acceptance_formula": "mean < 0.9", - "reading_1": "0.5", - "reading_2": "0.7", - "reading_3": "random text" # check if random string input causes issues - }, - { - "specification": "Calcium Content", # non-numeric reading - "formula_based_criteria": 1, - "numeric": 0, - "acceptance_formula": "reading_value in ('Grade A', 'Grade B', 'Grade C')", - "reading_value": "Grade B" - }] + readings = [ + { + "specification": "Iron Content", # numeric reading + "formula_based_criteria": 1, + "acceptance_formula": "reading_1 > 0.35 and reading_1 < 0.50", + "reading_1": "0.4" + }, + { + "specification": "Calcium Content", # numeric reading + "formula_based_criteria": 1, + "acceptance_formula": "reading_1 > 0.20 and reading_1 < 0.50", + "reading_1": "0.7" + }, + { + "specification": "Mg Content", # numeric reading + "formula_based_criteria": 1, + "acceptance_formula": "mean < 0.9", + "reading_1": "0.5", + "reading_2": "0.7", + "reading_3": "random text" # check if random string input causes issues + }, + { + "specification": "Calcium Content", # non-numeric reading + "formula_based_criteria": 1, + "numeric": 0, + "acceptance_formula": "reading_value in ('Grade A', 'Grade B', 'Grade C')", + "reading_value": "Grade B" + } + ] + + qa = create_quality_inspection( + reference_type="Delivery Note", + reference_name=dn.name, + readings=readings, + do_not_save=True + ) - qa = create_quality_inspection(reference_type="Delivery Note", reference_name=dn.name, - readings=readings, do_not_save=True) qa.save() # status must be auto set as per formula @@ -115,6 +147,19 @@ class TestQualityInspection(unittest.TestCase): qa.delete() dn.delete() + def test_make_quality_inspections_from_linked_document(self): + dn = create_delivery_note(item_code="_Test Item with QA", do_not_submit=True) + for item in dn.items: + item.sample_size = item.qty + quality_inspections = make_quality_inspections(dn.doctype, dn.name, dn.items) + self.assertEqual(len(dn.items), len(quality_inspections)) + + # cleanup + for qi in quality_inspections: + frappe.delete_doc("Quality Inspection", qi) + dn.delete() + + def create_quality_inspection(**args): args = frappe._dict(args) qa = frappe.new_doc("Quality Inspection") @@ -134,7 +179,7 @@ def create_quality_inspection(**args): readings = args.readings if args.status == "Rejected": - readings["reading_1"] = "12" # status is auto set in child on save + readings["reading_1"] = "12" # status is auto set in child on save if isinstance(readings, list): for entry in readings: @@ -150,10 +195,11 @@ def create_quality_inspection(**args): return qa + def create_quality_inspection_parameter(parameter): if not frappe.db.exists("Quality Inspection Parameter", parameter): frappe.get_doc({ "doctype": "Quality Inspection Parameter", "parameter": parameter, "description": parameter - }).insert() \ No newline at end of file + }).insert()