mirror of
https://github.com/frappe/erpnext.git
synced 2026-08-24 20:46:38 +00:00
fix(manufacturing): scope secondary items to job card (#58260)
This commit is contained in:
@@ -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}
|
||||
)
|
||||
|
||||
@@ -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]))
|
||||
|
||||
Reference in New Issue
Block a user