diff --git a/erpnext/manufacturing/doctype/job_card/job_card.py b/erpnext/manufacturing/doctype/job_card/job_card.py index 715941dd5e9..572ff5f12bd 100644 --- a/erpnext/manufacturing/doctype/job_card/job_card.py +++ b/erpnext/manufacturing/doctype/job_card/job_card.py @@ -1236,7 +1236,7 @@ class JobCard(Document): def set_status(self, update_status=False): self.status = {0: "Open", 1: "Submitted", 2: "Cancelled"}[self.docstatus or 0] if self.finished_good and self.docstatus == 1: - if (self.manufactured_qty + self.process_loss_qty) >= self.for_quantity: + if (self.manufactured_qty + self.process_loss_qty) >= self.get_qty_to_produce(): self.status = "Completed" elif self.transferred_qty > 0 or self.skip_material_transfer: self.status = "Work In Progress" @@ -1267,7 +1267,8 @@ class JobCard(Document): self.status = "Work In Progress" if self.docstatus == 1 and ( - self.for_quantity <= (self.total_completed_qty + self.process_loss_qty) or not self.items + self.get_qty_to_produce() <= (self.total_completed_qty + self.process_loss_qty) + or not self.items ): self.status = "Completed" @@ -1280,53 +1281,10 @@ class JobCard(Document): if self.workstation: self.update_workstation_status() -<<<<<<< HEAD -======= def get_qty_to_produce(self): """Qty this job card is expected to produce, the pending qty is left to another job card.""" return flt(self.for_quantity) - flt(self.pending_qty) - def set_finished_good_status(self): - # Only reached for a submitted job card (docstatus == 1) with a finished good, see set_status(). - qty_to_produce = self.get_qty_to_produce() - - if (self.manufactured_qty + self.process_loss_qty) >= qty_to_produce: - self.status = "Completed" - elif (self.total_completed_qty + self.process_loss_qty) >= qty_to_produce: - # Production is done and the card is submitted, but the finished goods have not been - # booked into stock yet (Manufacture Stock Entry pending) — distinct from active WIP. - self.status = "To Manufacture" - elif self.transferred_qty > 0 or self.skip_material_transfer: - self.status = "Work In Progress" - - def set_non_semi_fg_status(self): - if self.items: - item_data = frappe.get_all( - "Job Card Item", - filters={"parent": self.name}, - fields=["transferred_qty", "required_qty"], - ) - all_transferred = item_data and all( - flt(d.transferred_qty) >= flt(d.required_qty) for d in item_data - ) - any_transferred = any(flt(d.transferred_qty) > 0 for d in item_data) - - if all_transferred: - self.status = "Material Transferred" - elif any_transferred: - self.status = "Partially Transferred" - elif flt(self.for_quantity) <= flt(self.transferred_qty): - self.status = "Material Transferred" - - if self.time_logs: - self.status = "Work In Progress" - - if self.docstatus == 1 and ( - self.get_qty_to_produce() <= (self.total_completed_qty + self.process_loss_qty) or not self.items - ): - self.status = "Completed" - ->>>>>>> 970039d8ec (fix(job_card): leave the pending qty out of the job card's own output (#57686)) def set_wip_warehouse(self): if not self.wip_warehouse: self.wip_warehouse = frappe.get_cached_value("Company", self.company, "default_wip_warehouse") @@ -1625,13 +1583,8 @@ class JobCard(Document): ste = ManufactureEntry( { -<<<<<<< HEAD - "for_quantity": self.for_quantity - self.manufactured_qty, - "process_loss_qty": max(self.process_loss_qty - get_consumed_process_loss(), 0), -======= "for_quantity": self.get_qty_to_produce() - self.manufactured_qty, - "process_loss_qty": max(self.process_loss_qty - self.get_consumed_process_loss(), 0), ->>>>>>> 970039d8ec (fix(job_card): leave the pending qty out of the job card's own output (#57686)) + "process_loss_qty": max(self.process_loss_qty - get_consumed_process_loss(), 0), "job_card": self.name, "skip_material_transfer": self.skip_material_transfer, "backflush_from_wip_warehouse": self.backflush_from_wip_warehouse, diff --git a/erpnext/manufacturing/doctype/job_card/test_job_card.py b/erpnext/manufacturing/doctype/job_card/test_job_card.py index 85c73b4ff1a..9a8d978cc1f 100644 --- a/erpnext/manufacturing/doctype/job_card/test_job_card.py +++ b/erpnext/manufacturing/doctype/job_card/test_job_card.py @@ -1265,8 +1265,6 @@ class TestJobCard(ERPNextTestSuite): 8, ) -<<<<<<< HEAD -======= def test_semi_fg_pending_qty_is_left_to_another_job_card(self): from erpnext.manufacturing.doctype.operation.test_operation import make_operation from erpnext.stock.doctype.item.test_item import make_item @@ -1319,7 +1317,16 @@ class TestJobCard(ERPNextTestSuite): make_stock_entry(item_code=rm, target=warehouse, qty=100, basic_rate=100) - job_card = self.get_first_job_card(work_order.name) + job_card = frappe.get_doc( + "Job Card", + frappe.get_all( + "Job Card", + filters={"work_order": work_order.name}, + order_by="sequence_id, creation", + limit=1, + pluck="name", + )[0], + ) job_card.append("time_logs", {"from_time": "2024-04-01 08:00:00"}) job_card.save() @@ -1337,7 +1344,7 @@ class TestJobCard(ERPNextTestSuite): self.assertEqual(flt(job_card.process_loss_qty), 0) job_card.submit() - self.assertEqual(job_card.status, "To Manufacture") + self.assertEqual(job_card.status, "Work In Progress") manufacturing_entry = frappe.get_doc(job_card.make_stock_entry_for_semi_fg_item()) finished_item = next(row for row in manufacturing_entry.items if row.is_finished_item) @@ -1348,145 +1355,6 @@ class TestJobCard(ERPNextTestSuite): self.assertEqual(flt(job_card.manufactured_qty), 3) self.assertEqual(job_card.status, "Completed") - def test_semi_fg_sequence_needs_previous_operations_manufactured(self): - from erpnext.manufacturing.doctype.operation.test_operation import make_operation - from erpnext.stock.doctype.item.test_item import make_item - - warehouse = "Stores - _TC" - rm1 = make_item("Sequence Check RM 1", {"is_stock_item": 1}).name - rm2 = make_item("Sequence Check RM 2", {"is_stock_item": 1}).name - sfg1 = make_item("Sequence Check SFG 1", {"is_stock_item": 1}).name - sfg2 = make_item("Sequence Check SFG 2", {"is_stock_item": 1}).name - fg = make_item("Sequence Check FG 1", {"is_stock_item": 1}).name - - semi_fg_boms = {} - for semi_fg_item, raw_material in ((sfg1, rm1), (sfg2, rm2)): - bom = frappe.new_doc("BOM", company="_Test Company", item=semi_fg_item, quantity=1) - bom.append("items", {"item_code": raw_material, "qty": 1}) - bom.insert() - bom.submit() - semi_fg_boms[semi_fg_item] = bom.name - - fg_bom = frappe.new_doc( - "BOM", - company="_Test Company", - item=fg, - quantity=1, - with_operations=1, - track_semi_finished_goods=1, - ) - - operations = [ - { - "operation": "Sequence Check Op A", - "finished_good": sfg1, - "bom_no": semi_fg_boms[sfg1], - "sequence_id": 1, - }, - { - "operation": "Sequence Check Op B", - "finished_good": sfg2, - "bom_no": semi_fg_boms[sfg2], - "sequence_id": 1, - }, - { - "operation": "Sequence Check Op C", - "finished_good": fg, - "is_final_finished_good": 1, - "sequence_id": 2, - }, - ] - - for row in operations: - row.update( - { - "workstation": "_Test Workstation A", - "finished_good_qty": 1, - "time_in_mins": 60, - "source_warehouse": warehouse, - "fg_warehouse": warehouse, - "skip_material_transfer": 1, - } - ) - - make_workstation(row) - make_operation(row) - fg_bom.append("operations", row) - - fg_bom.append("items", {"item_code": sfg1, "qty": 1, "operation_row_id": 3}) - fg_bom.append("items", {"item_code": sfg2, "qty": 1, "operation_row_id": 3}) - fg_bom.insert() - fg_bom.submit() - - work_order = make_wo_order_test_record( - item=fg, - qty=5, - source_warehouse=warehouse, - fg_warehouse=warehouse, - bom_no=fg_bom.name, - skip_transfer=1, - do_not_save=True, - ) - - for row in work_order.operations: - row.time_in_mins = 60 - - work_order.save() - work_order.submit() - - make_stock_entry(item_code=rm1, target=warehouse, qty=10, basic_rate=100) - make_stock_entry(item_code=rm2, target=warehouse, qty=10, basic_rate=100) - - def get_job_card(operation): - return frappe.get_doc( - "Job Card", - frappe.db.get_value( - "Job Card", - {"work_order": work_order.name, "operation": operation, "docstatus": 0}, - "name", - ), - ) - - def add_time_log(job_card, day, qty): - job_card.append( - "time_logs", - { - "from_time": f"2024-01-{day} 08:00:00", - "to_time": f"2024-01-{day} 09:00:00", - "completed_qty": qty, - }, - ) - - jc_a = get_job_card("Sequence Check Op A") - jc_a.for_quantity = 3 - add_time_log(jc_a, "01", 3) - jc_a.submit() - - jc_b = get_job_card("Sequence Check Op B") - add_time_log(jc_b, "02", jc_b.for_quantity) - jc_b.submit() - frappe.get_doc(jc_b.make_stock_entry_for_semi_fg_item()).submit() - - jc_c = get_job_card("Sequence Check Op C") - jc_c.for_quantity = 3 - add_time_log(jc_c, "03", 3) - self.assertRaises(OperationSequenceError, jc_c.save) - - frappe.get_doc(jc_a.make_stock_entry_for_semi_fg_item()).submit() - - jc_c.reload() - jc_c.for_quantity = 4 - add_time_log(jc_c, "03", 4) - self.assertRaises(OperationSequenceError, jc_c.save) - - jc_c.reload() - jc_c.for_quantity = 3 - add_time_log(jc_c, "03", 3) - jc_c.submit() - - self.assertEqual(jc_c.docstatus, 1) - ->>>>>>> 970039d8ec (fix(job_card): leave the pending qty out of the job card's own output (#57686)) def test_semi_fg_batch_auto_pull_on_manufacture(self): from erpnext.manufacturing.doctype.operation.test_operation import make_operation from erpnext.stock.doctype.item.test_item import make_item