diff --git a/erpnext/manufacturing/doctype/job_card/test_job_card.py b/erpnext/manufacturing/doctype/job_card/test_job_card.py index 8551d5e04ff..f4b3622c9ca 100644 --- a/erpnext/manufacturing/doctype/job_card/test_job_card.py +++ b/erpnext/manufacturing/doctype/job_card/test_job_card.py @@ -457,6 +457,49 @@ class TestJobCard(ERPNextTestSuite): job_card.reload() self.assertEqual(job_card.transferred_qty, 0.0) + def test_work_order_transferred_qty_with_multiple_job_cards(self): + create_bom_with_multiple_operations() + work_order = make_wo_with_transfer_against_jc() + self.generate_required_stock(work_order) + + job_cards = frappe.get_all( + "Job Card", + filters={"work_order": work_order.name}, + pluck="name", + order_by="sequence_id", + ) + completed_qty = (4, 3) + + for job_card_name, qty in zip(job_cards, completed_qty, strict=True): + job_card = frappe.get_doc("Job Card", job_card_name) + job_card.for_quantity = qty + job_card.save() + + transfer_entry = make_stock_entry_from_jc(job_card.name) + transfer_entry.fg_completed_qty = qty + transfer_entry.get_items() + transfer_entry.submit() + + job_card.reload() + job_card.append( + "time_logs", + { + "from_time": now(), + "to_time": add_to_date(now(), hours=1), + "completed_qty": qty, + }, + ) + job_card.submit() + + work_order.reload() + self.assertEqual(work_order.material_transferred_for_manufacturing, min(completed_qty)) + + # Refreshing required items must not replace the Job Card roll-up with the sum + # of FG quantities from Material Transfer Stock Entries (4 + 3). + work_order.update_required_items() + work_order.reload() + self.assertEqual(work_order.material_transferred_for_manufacturing, min(completed_qty)) + def test_job_card_material_transfer_correctness(self): """ 1. Test if only current Job Card Items are pulled in a Stock Entry against a Job Card diff --git a/erpnext/manufacturing/doctype/work_order/work_order.py b/erpnext/manufacturing/doctype/work_order/work_order.py index 2454341dc39..488700b4208 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.py +++ b/erpnext/manufacturing/doctype/work_order/work_order.py @@ -1806,6 +1806,10 @@ class WorkOrder(Document): def recompute_material_transferred_for_manufacturing(self, transferred_items): """Set material_transferred_for_manufacturing based on actual item-level transfers, not fg_completed_qty.""" + # Job Card transfers use the minimum completed quantity across operations. + if self.operations and self.transfer_material_against == "Job Card": + return + # When fg_completed_qty > 0 (direct stock entries, excess transfer), preserve the # SUM(fg_completed_qty) approach so excess-transfer tracking works correctly. sum_fg_completed_qty = self.get_transferred_or_manufactured_qty(