diff --git a/erpnext/manufacturing/doctype/job_card/test_job_card.py b/erpnext/manufacturing/doctype/job_card/test_job_card.py index 0f429bc27cb..2aece0c677c 100644 --- a/erpnext/manufacturing/doctype/job_card/test_job_card.py +++ b/erpnext/manufacturing/doctype/job_card/test_job_card.py @@ -1507,15 +1507,14 @@ class TestJobCard(ERPNextTestSuite): return bom rm1 = create_item("RM 1") - scrap1 = create_item("Scrap 1") + shared_scrap = create_item("Shared Scrap") sfg = create_item("SFG 1") - sfg_bom = create_bom(rm1.name, sfg.name, scrap1.name) + sfg_bom = create_bom(rm1.name, sfg.name, shared_scrap.name) rm2 = create_item("RM 2") fg1 = create_item("FG 1") - scrap2 = create_item("Scrap 2") scrap_extra = create_item("Scrap Extra") - fg_bom = create_bom(rm2.name, fg1.name, scrap2.name, submit=False) + fg_bom = create_bom(rm2.name, fg1.name, shared_scrap.name, submit=False) fg_bom.with_operations = 1 fg_bom.track_semi_finished_goods = 1 @@ -1596,7 +1595,7 @@ class TestJobCard(ERPNextTestSuite): manufacturing_entry = frappe.get_doc(job_card.make_stock_entry_for_semi_fg_item()) manufacturing_entry.submit() - self.assertEqual(manufacturing_entry.items[2].item_code, scrap1.name) + self.assertEqual(manufacturing_entry.items[2].item_code, shared_scrap.name) self.assertEqual(manufacturing_entry.items[2].qty, 9) self.assertEqual(flt(manufacturing_entry.items[2].basic_rate, 3), 5.556) self.assertEqual(manufacturing_entry.items[3].item_code, scrap_extra.name) @@ -1637,7 +1636,7 @@ class TestJobCard(ERPNextTestSuite): sfg_row = next(row for row in manufacturing_entry.items if row.item_code == sfg.name) self.assertEqual(flt(sfg_row.basic_rate, 3), 95.0) - self.assertEqual(manufacturing_entry.items[2].item_code, scrap2.name) + self.assertEqual(manufacturing_entry.items[2].item_code, shared_scrap.name) self.assertEqual(manufacturing_entry.items[2].qty, 9) self.assertEqual(flt(manufacturing_entry.items[2].basic_rate, 3), 5.278) @@ -2777,6 +2776,18 @@ class TestJobCard(ERPNextTestSuite): self.assertEqual(s.items[3].item_code, "_Test Item") self.assertEqual(s.items[3].transfer_qty, 2) + frappe.db.set_value( + "Stock Entry Detail", + s.items[3].name, + {"secondary_item_type": None, "is_legacy_scrap_item": 1}, + ) + + from erpnext.stock.doctype.stock_entry.services.manufacturing import ManufactureStockEntry + + stock_entry = frappe.get_doc({"doctype": "Stock Entry", "work_order": self.work_order.name}) + used_secondary_items = ManufactureStockEntry(stock_entry).get_used_secondary_items() + self.assertEqual(used_secondary_items[("_Test Item", "Scrap")], 2) + @ERPNextTestSuite.change_settings( "Manufacturing Settings", {"overproduction_percentage_for_work_order": 100} ) diff --git a/erpnext/stock/doctype/stock_entry/services/manufacturing.py b/erpnext/stock/doctype/stock_entry/services/manufacturing.py index 0971ac3b54a..d1b5178c95b 100644 --- a/erpnext/stock/doctype/stock_entry/services/manufacturing.py +++ b/erpnext/stock/doctype/stock_entry/services/manufacturing.py @@ -862,6 +862,9 @@ class ManufactureStockEntry(BaseManufactureStockEntry): secondary_items = self.get_secondary_items_from_job_card() for row in secondary_items: + if row.stock_qty <= 0: + continue + row.uom = row.uom or row.stock_uom row.qty = ceil_qty_if_uom_has_whole_number(row.stock_qty, row.stock_uom) row.transfer_qty = row.qty @@ -888,33 +891,38 @@ class ManufactureStockEntry(BaseManufactureStockEntry): def _adjust_secondary_item_qtys(self, secondary_items, used_secondary_items, pending_qty): for row in secondary_items: - row.stock_qty -= flt(used_secondary_items.get(row.item_code)) + key = (row.item_code, row.secondary_item_type or "") + row.stock_qty -= flt(used_secondary_items.get(key)) row.stock_qty = row.stock_qty * flt(self.doc.fg_completed_qty) / flt(pending_qty) - if used_secondary_items.get(row.item_code): - used_secondary_items[row.item_code] -= row.stock_qty def get_used_secondary_items(self): data = self._query_used_secondary_items() used_secondary_items = defaultdict(float) for row in data: - used_secondary_items[row.item_code] += row.qty + secondary_item_type = row.secondary_item_type or ("Scrap" if row.is_legacy_scrap_item else "") + key = (row.item_code, secondary_item_type) + used_secondary_items[key] += row.qty return used_secondary_items def _query_used_secondary_items(self): se = frappe.qb.DocType("Stock Entry") sed = frappe.qb.DocType("Stock Entry Detail") - return ( + query = ( frappe.qb.from_(se) .inner_join(sed) .on(sed.parent == se.name) - .select(sed.item_code, sed.qty) + .select(sed.item_code, sed.secondary_item_type, sed.is_legacy_scrap_item, sed.qty) .where( (se.work_order == self.doc.work_order) & ((sed.secondary_item_type.isnotnull()) | (sed.is_legacy_scrap_item == 1)) & (se.docstatus == 1) & (se.purpose.isin(["Repack", "Manufacture"])) ) - ).run(as_dict=1) + ) + if self.doc.job_card: + query = query.where(se.job_card == self.doc.job_card) + + return query.run(as_dict=1) def get_completed_job_card_qty(self): return flt(min([d.completed_qty for d in self.wo_doc.operations]))