Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
88 changes: 57 additions & 31 deletions erpnext/controllers/stock_controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -356,42 +356,68 @@ def update_billing_percentage(self, update_modified=True):
}, update_modified)

def validate_inspection(self):
'''Checks if quality inspection is set for Items that require inspection.
On submit, throw an exception'''
inspection_required_fieldname = None
if self.doctype in ["Purchase Receipt", "Purchase Invoice"]:
inspection_required_fieldname = "inspection_required_before_purchase"
elif self.doctype in ["Delivery Note", "Sales Invoice"]:
inspection_required_fieldname = "inspection_required_before_delivery"

"""Checks if quality inspection is set/ is valid for Items that require inspection."""
inspection_fieldname_map = {
"Purchase Receipt": "inspection_required_before_purchase",
"Purchase Invoice": "inspection_required_before_purchase",
"Sales Invoice": "inspection_required_before_delivery",
"Delivery Note": "inspection_required_before_delivery"
}
inspection_required_fieldname = inspection_fieldname_map.get(self.doctype)

# return if inspection is not required on document level
if ((not inspection_required_fieldname and self.doctype != "Stock Entry") or
(self.doctype == "Stock Entry" and not self.inspection_required) or
(self.doctype in ["Sales Invoice", "Purchase Invoice"] and not self.update_stock)):
return

for d in self.get('items'):
qa_required = False
if (inspection_required_fieldname and not d.quality_inspection and
frappe.db.get_value("Item", d.item_code, inspection_required_fieldname)):
qa_required = True
elif self.doctype == "Stock Entry" and not d.quality_inspection and d.t_warehouse:
qa_required = True
if self.docstatus == 1 and d.quality_inspection:
qa_doc = frappe.get_doc("Quality Inspection", d.quality_inspection)
if qa_doc.docstatus == 0:
link = frappe.utils.get_link_to_form('Quality Inspection', d.quality_inspection)
frappe.throw(_("Quality Inspection: {0} is not submitted for the item: {1} in row {2}").format(link, d.item_code, d.idx), QualityInspectionNotSubmittedError)

if qa_doc.status != 'Accepted':
frappe.throw(_("Row {0}: Quality Inspection rejected for item {1}")
.format(d.idx, d.item_code), QualityInspectionRejectedError)
elif qa_required :
action = frappe.get_doc('Stock Settings').action_if_quality_inspection_is_not_submitted
if self.docstatus==1 and action == 'Stop':
frappe.throw(_("Quality Inspection required for Item {0} to submit").format(frappe.bold(d.item_code)),
exc=QualityInspectionRequiredError)
else:
frappe.msgprint(_("Create Quality Inspection for Item {0}").format(frappe.bold(d.item_code)))
for row in self.get('items'):
qi_required = False
if (inspection_required_fieldname and frappe.db.get_value("Item", row.item_code, inspection_required_fieldname)):
qi_required = True
elif self.doctype == "Stock Entry" and row.t_warehouse:
qi_required = True # inward stock needs inspection

if qi_required: # validate row only if inspection is required on item level
self.validate_qi_presence(row)
if self.docstatus == 1:
self.validate_qi_submission(row)
self.validate_qi_rejection(row)

def validate_qi_presence(self, row):
"""Check if QI is present on row level. Warn on save and stop on submit if missing."""
if not row.quality_inspection:
msg = f"Row #{row.idx}: Quality Inspection is required for Item {frappe.bold(row.item_code)}"
if self.docstatus == 1:
frappe.throw(_(msg), title=_("Inspection Required"), exc=QualityInspectionRequiredError)
else:
frappe.msgprint(_(msg), title=_("Inspection Required"), indicator="blue")

def validate_qi_submission(self, row):
"""Check if QI is submitted on row level, during submission"""
action = frappe.db.get_single_value("Stock Settings", "action_if_quality_inspection_is_not_submitted")
qa_docstatus = frappe.db.get_value("Quality Inspection", row.quality_inspection, "docstatus")

if not qa_docstatus == 1:
link = frappe.utils.get_link_to_form('Quality Inspection', row.quality_inspection)
msg = f"Row #{row.idx}: Quality Inspection {link} is not submitted for the item: {row.item_code}"
if action == "Stop":
frappe.throw(_(msg), title=_("Inspection Submission"), exc=QualityInspectionNotSubmittedError)
else:
frappe.msgprint(_(msg), alert=True, indicator="orange")

def validate_qi_rejection(self, row):
"""Check if QI is rejected on row level, during submission"""
action = frappe.db.get_single_value("Stock Settings", "action_if_quality_inspection_is_rejected")
qa_status = frappe.db.get_value("Quality Inspection", row.quality_inspection, "status")

if qa_status == "Rejected":
link = frappe.utils.get_link_to_form('Quality Inspection', row.quality_inspection)
msg = f"Row #{row.idx}: Quality Inspection {link} was rejected for item {row.item_code}"
if action == "Stop":
frappe.throw(_(msg), title=_("Inspection Rejected"), exc=QualityInspectionRejectedError)
else:
frappe.msgprint(_(msg), alert=True, indicator="orange")

def update_blanket_order(self):
blanket_orders = list(set([d.blanket_order for d in self.items if d.blanket_order]))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
)
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.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry

# test_records = frappe.get_test_records('Quality Inspection')

Expand Down Expand Up @@ -159,6 +159,47 @@ def test_make_quality_inspections_from_linked_document(self):
frappe.delete_doc("Quality Inspection", qi)
dn.delete()

def test_rejected_qi_validation(self):
"""Test if rejected QI blocks Stock Entry as per Stock Settings."""
se = make_stock_entry(
item_code="_Test Item with QA",
target="_Test Warehouse - _TC",
qty=1,
basic_rate=100,
inspection_required=True,
do_not_submit=True
)

readings = [
{
"specification": "Iron Content",
"min_value": 0.1,
"max_value": 0.9,
"reading_1": "0.4"
}
]

qa = create_quality_inspection(
reference_type="Stock Entry",
reference_name=se.name,
readings=readings,
status="Rejected"
)

frappe.db.set_value("Stock Settings", None, "action_if_quality_inspection_is_rejected", "Stop")
se.reload()
self.assertRaises(QualityInspectionRejectedError, se.submit) # when blocked in Stock settings, block rejected QI

frappe.db.set_value("Stock Settings", None, "action_if_quality_inspection_is_rejected", "Warn")
se.reload()
se.submit() # when allowed in Stock settings, allow rejected QI

# teardown
qa.reload()
qa.cancel()
se.reload()
se.cancel()
frappe.db.set_value("Stock Settings", None, "action_if_quality_inspection_is_rejected", "Stop")

def create_quality_inspection(**args):
args = frappe._dict(args)
Expand All @@ -175,12 +216,11 @@ def create_quality_inspection(**args):
if not args.readings:
create_quality_inspection_parameter("Size")
readings = {"specification": "Size", "min_value": 0, "max_value": 10}
if args.status == "Rejected":
readings["reading_1"] = "12" # status is auto set in child on save
else:
readings = args.readings

if args.status == "Rejected":
readings["reading_1"] = "12" # status is auto set in child on save

if isinstance(readings, list):
for entry in readings:
create_quality_inspection_parameter(entry["specification"])
Expand Down
2 changes: 2 additions & 0 deletions erpnext/stock/doctype/stock_entry/stock_entry_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ def process_serial_numbers(serial_nos_list):
s.posting_date = args.posting_date
if args.posting_time:
s.posting_time = args.posting_time
if args.inspection_required:
s.inspection_required = args.inspection_required

# map names
if args.from_warehouse:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -307,6 +307,7 @@
"fieldname": "quality_inspection",
"fieldtype": "Link",
"label": "Quality Inspection",
"no_copy": 1,
"options": "Quality Inspection"
},
{
Expand Down Expand Up @@ -548,7 +549,7 @@
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2021-04-22 20:08:23.799715",
"modified": "2021-06-21 16:03:18.834880",
"modified_by": "Administrator",
"module": "Stock",
"name": "Stock Entry Detail",
Expand Down
21 changes: 20 additions & 1 deletion erpnext/stock/doctype/stock_settings/stock_settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,10 @@
"allow_negative_stock",
"show_barcode_field",
"clean_description_html",
"quality_inspection_settings_section",
"action_if_quality_inspection_is_not_submitted",
"column_break_21",
"action_if_quality_inspection_is_rejected",
"section_break_7",
"automatically_set_serial_nos_based_on_fifo",
"set_qty_in_transactions_based_on_serial_no_input",
Expand Down Expand Up @@ -264,14 +267,30 @@
{
"fieldname": "column_break_31",
"fieldtype": "Column Break"
},
{
"fieldname": "quality_inspection_settings_section",
"fieldtype": "Section Break",
"label": "Quality Inspection Settings"
},
{
"fieldname": "column_break_21",
"fieldtype": "Column Break"
},
{
"default": "Stop",
"fieldname": "action_if_quality_inspection_is_rejected",
"fieldtype": "Select",
"label": "Action If Quality Inspection Is Rejected",
"options": "Stop\nWarn"
}
],
"icon": "icon-cog",
"idx": 1,
"index_web_pages_for_search": 1,
"issingle": 1,
"links": [],
"modified": "2021-04-30 17:27:42.709231",
"modified": "2021-07-10 16:17:42.159829",
"modified_by": "Administrator",
"module": "Stock",
"name": "Stock Settings",
Expand Down