diff --git a/erpnext/manufacturing/doctype/job_card/job_card.py b/erpnext/manufacturing/doctype/job_card/job_card.py index e3ee2baaa53..3d9494ee523 100644 --- a/erpnext/manufacturing/doctype/job_card/job_card.py +++ b/erpnext/manufacturing/doctype/job_card/job_card.py @@ -24,6 +24,14 @@ from frappe.utils import ( time_diff_in_seconds, ) +<<<<<<< HEAD +======= +from erpnext.controllers.stock_controller import ( + QualityInspectionNotSubmittedError, + QualityInspectionRejectedError, +) +from erpnext.manufacturing.doctype.bom.bom import add_additional_cost, get_bom_items_as_dict +>>>>>>> 46b4cf3add (fix: Added validation for quality inspection in job card) from erpnext.manufacturing.doctype.manufacturing_settings.manufacturing_settings import ( get_mins_between_operations, ) @@ -669,6 +677,7 @@ class JobCard(Document): self.set_process_loss() def on_submit(self): + self.validate_inspection() self.validate_transfer_qty() self.validate_job_card() self.update_work_order() @@ -678,6 +687,66 @@ class JobCard(Document): self.update_work_order() self.set_transferred_qty() + def validate_inspection(self): + action_submit, action_reject = frappe.get_single_value( + "Stock Settings", + ["action_if_quality_inspection_is_not_submitted", "action_if_quality_inspection_is_rejected"], + ) + + item = self.finished_good or self.production_item + bom_inspection_required = frappe.db.get_value( + "BOM", self.semi_fg_bom or self.bom_no, "inspection_required" + ) + if bom_inspection_required: + if not self.quality_inspection: + frappe.throw( + _( + "Quality Inspection is required for the item {0} before completing the job card {1}" + ).format(get_link_to_form("Item", item), bold(self.name)) + ) + qa_status, docstatus = frappe.db.get_value( + "Quality Inspection", self.quality_inspection, ["status", "docstatus"] + ) + + if docstatus != 1: + if action_submit == "Stop": + frappe.throw( + _("Quality Inspection {0} is not submitted for the item: {1}").format( + get_link_to_form("Quality Inspection", self.quality_inspection), + get_link_to_form("Item", item), + ), + title=_("Inspection Submission"), + exc=QualityInspectionNotSubmittedError, + ) + else: + frappe.msgprint( + _("Quality Inspection {0} is not submitted for the item: {1}").format( + get_link_to_form("Quality Inspection", self.quality_inspection), + get_link_to_form("Item", item), + ), + alert=True, + indicator="orange", + ) + elif qa_status == "Rejected": + if action_reject == "Stop": + frappe.throw( + _("Quality Inspection {0} is rejected for the item: {1}").format( + get_link_to_form("Quality Inspection", self.quality_inspection), + get_link_to_form("Item", item), + ), + title=_("Inspection Rejected"), + exc=QualityInspectionRejectedError, + ) + else: + frappe.msgprint( + _("Quality Inspection {0} is rejected for the item: {1}").format( + get_link_to_form("Quality Inspection", self.quality_inspection), + get_link_to_form("Item", item), + ), + alert=True, + indicator="orange", + ) + def validate_transfer_qty(self): if not self.is_corrective_job_card and self.items and self.transferred_qty < self.for_quantity: frappe.throw( diff --git a/erpnext/manufacturing/doctype/job_card/test_job_card.py b/erpnext/manufacturing/doctype/job_card/test_job_card.py index 12205a80a2b..d8353fe930c 100644 --- a/erpnext/manufacturing/doctype/job_card/test_job_card.py +++ b/erpnext/manufacturing/doctype/job_card/test_job_card.py @@ -21,8 +21,9 @@ from erpnext.manufacturing.doctype.job_card.job_card import ( make_stock_entry as make_stock_entry_from_jc, ) from erpnext.manufacturing.doctype.work_order.test_work_order import make_wo_order_test_record -from erpnext.manufacturing.doctype.work_order.work_order import WorkOrder +from erpnext.manufacturing.doctype.work_order.work_order import WorkOrder, make_work_order from erpnext.manufacturing.doctype.workstation.test_workstation import make_workstation +from erpnext.stock.doctype.item.test_item import create_item from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry @@ -58,6 +59,50 @@ class TestJobCard(FrappeTestCase): def tearDown(self): frappe.db.rollback() + def test_quality_inspection_mandatory_check(self): + from erpnext.manufacturing.doctype.operation.test_operation import make_operation + + raw = create_item("Fabric-Raw") + cut_fg = create_item("Cut-Fabric-SFG") + stitch_fg = create_item("Stitched-TShirt-SFG") + final = create_item("Finished-TShirt") + + row = {"operation": "Cutting", "workstation": "_Test Workstation 1"} + + cutting = make_operation(row) + stitching = make_operation({"operation": "Stitching", "workstation": "_Test Workstation 1"}) + ironing = make_operation({"operation": "Ironing", "workstation": "_Test Workstation 1"}) + + cut_bom = create_semi_fg_bom(cut_fg.name, raw.name, inspection_required=1) + stitch_bom = create_semi_fg_bom(stitch_fg.name, cut_fg.name, inspection_required=0) + final_bom = frappe.new_doc("BOM") + final_bom.item = final.name + final_bom.quantity = 1 + final_bom.with_operations = 1 + final_bom.track_semi_finished_goods = 1 + final_bom.append("items", {"item_code": raw.name, "qty": 1}) + final_bom.append( + "operations", {"operation": cutting.name, "workstation": "_Test Workstation 1", "bom_no": cut_bom} + ) + final_bom.append( + "operations", + {"operation": stitching.name, "workstation": "_Test Workstation 1", "bom_no": stitch_bom}, + ) + final_bom.append("operations", {"operation": ironing.name, "workstation": "_Test Workstation 1"}) + final_bom.insert() + final_bom.submit() + work_order = make_work_order(final_bom.name, final.name, 1, variant_items=[], use_multi_level_bom=0) + work_order.wip_warehouse = "Work In Progress - WP" + work_order.fg_warehouse = "Finished Goods - WP" + work_order.scrap_warehouse = "All Warehouses - WP" + for operation in work_order.operations: + operation.time_in_mins = 60 + + work_order.submit() + job_card = frappe.get_all("Job Card", filters={"work_order": work_order.name, "operation": "Cutting"}) + job_card_doc = frappe.get_doc("Job Card", job_card[0].name) + self.assertRaises(frappe.ValidationError, job_card_doc.submit) + def test_job_card_operations(self): job_cards = frappe.get_all( "Job Card", filters={"work_order": self.work_order.name}, fields=["operation_id", "name"] @@ -750,6 +795,7 @@ def make_wo_with_transfer_against_jc(): return work_order +<<<<<<< HEAD def make_bom_for_jc_tests(): test_records = frappe.get_test_records("BOM") bom = frappe.copy_doc(test_records[2]) @@ -758,3 +804,13 @@ def make_bom_for_jc_tests(): bom.items[0].uom = "_Test UOM 1" bom.items[0].conversion_factor = 5 bom.insert() +======= +def create_semi_fg_bom(semi_fg_item, raw_item, inspection_required): + bom = frappe.new_doc("BOM") + bom.item = semi_fg_item + bom.quantity = 1 + bom.inspection_required = inspection_required + bom.append("items", {"item_code": raw_item, "qty": 1}) + bom.submit() + return bom.name +>>>>>>> 46b4cf3add (fix: Added validation for quality inspection in job card)