mirror of
https://github.com/frappe/erpnext.git
synced 2026-09-19 19:37:56 +00:00
fix(stock): stop treating a Repack secondary item as a finished good (#57735)
* fix(stock): stop treating a Repack secondary item as a finished good mark_finished_and_secondary_items flagged every incoming Repack row as a finished item, secondary rows included. Two things followed from that. The row never reached the secondary-item branch in _set_incoming_item_rate, so its own cost_allocation_per was never applied, and the BOM's finished-good percentage was applied to every incoming row rather than to the finished good alone. Value was destroyed as a result. Repacking 1000 of raw material under a BOM that allocates 75% to the finished good and 25% to scrap booked 500 to the finished good and 250 to the scrap: 750 in against 1000 out. Leave secondary rows unflagged so each side takes the share the BOM declares. * test(stock): cover cost allocation for a Repack secondary item A BOM allocating 75% to the finished good and 25% to scrap must split 1000 of raw material into 750 and 250, leaving no difference.
This commit is contained in:
@@ -913,7 +913,9 @@ class StockEntry(StockController, SubcontractingInwardController):
|
|||||||
|
|
||||||
for d in self.items:
|
for d in self.items:
|
||||||
if d.t_warehouse and not d.s_warehouse:
|
if d.t_warehouse and not d.s_warehouse:
|
||||||
if self.purpose == "Repack" or d.item_code == finished_item:
|
if d.secondary_item_type or d.is_legacy_scrap_item:
|
||||||
|
d.is_finished_item = 0
|
||||||
|
elif self.purpose == "Repack" or d.item_code == finished_item:
|
||||||
d.is_finished_item = 1
|
d.is_finished_item = 1
|
||||||
else:
|
else:
|
||||||
d.is_finished_item = 0
|
d.is_finished_item = 0
|
||||||
|
|||||||
@@ -2808,6 +2808,62 @@ class TestStockEntry(ERPNextTestSuite):
|
|||||||
self.assertEqual(flt(se.total_outgoing_value), 1000.0)
|
self.assertEqual(flt(se.total_outgoing_value), 1000.0)
|
||||||
self.assertEqual(flt(se.value_difference), 0.0)
|
self.assertEqual(flt(se.value_difference), 0.0)
|
||||||
|
|
||||||
|
def test_repack_allocates_cost_to_secondary_item(self):
|
||||||
|
"""A Repack secondary item takes its own BOM share, not the finished good's."""
|
||||||
|
rm_item = make_item(properties={"is_stock_item": 1, "valuation_rate": 100}).name
|
||||||
|
fg_item = make_item(properties={"is_stock_item": 1}).name
|
||||||
|
scrap_item = make_item(properties={"is_stock_item": 1, "valuation_rate": 20}).name
|
||||||
|
warehouse = "_Test Warehouse - _TC"
|
||||||
|
|
||||||
|
bom = frappe.get_doc(
|
||||||
|
{
|
||||||
|
"doctype": "BOM",
|
||||||
|
"item": fg_item,
|
||||||
|
"currency": "INR",
|
||||||
|
"quantity": 10,
|
||||||
|
"company": "_Test Company",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
bom.append("items", {"item_code": rm_item, "qty": 10})
|
||||||
|
bom.append(
|
||||||
|
"secondary_items",
|
||||||
|
{
|
||||||
|
"secondary_item_type": "Scrap",
|
||||||
|
"item_code": scrap_item,
|
||||||
|
"item_name": scrap_item,
|
||||||
|
"qty": 5,
|
||||||
|
"cost_allocation_per": 25,
|
||||||
|
"process_loss_per": 0,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
bom.insert()
|
||||||
|
bom.submit()
|
||||||
|
self.assertEqual(flt(bom.cost_allocation_per), 75.0)
|
||||||
|
|
||||||
|
make_stock_entry(item_code=rm_item, target=warehouse, qty=100, basic_rate=100)
|
||||||
|
|
||||||
|
se = frappe.new_doc("Stock Entry")
|
||||||
|
se.purpose = se.stock_entry_type = "Repack"
|
||||||
|
se.company = "_Test Company"
|
||||||
|
se.from_bom = 1
|
||||||
|
se.bom_no = bom.name
|
||||||
|
se.fg_completed_qty = 10
|
||||||
|
se.from_warehouse = warehouse
|
||||||
|
se.to_warehouse = warehouse
|
||||||
|
se.get_items()
|
||||||
|
se.save()
|
||||||
|
|
||||||
|
fg_row = next(d for d in se.items if d.is_finished_item)
|
||||||
|
scrap_row = next(d for d in se.items if d.secondary_item_type)
|
||||||
|
|
||||||
|
self.assertFalse(scrap_row.is_finished_item)
|
||||||
|
self.assertEqual(flt(scrap_row.basic_amount), 250.0)
|
||||||
|
self.assertEqual(flt(fg_row.basic_amount), 750.0)
|
||||||
|
|
||||||
|
self.assertEqual(flt(se.total_incoming_value), 1000.0)
|
||||||
|
self.assertEqual(flt(se.total_outgoing_value), 1000.0)
|
||||||
|
self.assertEqual(flt(se.value_difference), 0.0)
|
||||||
|
|
||||||
def _make_wo_for_free_raw_material(self, rm_item, fg_item, bom_no):
|
def _make_wo_for_free_raw_material(self, rm_item, fg_item, bom_no):
|
||||||
from erpnext.manufacturing.doctype.work_order.test_work_order import make_wo_order_test_record
|
from erpnext.manufacturing.doctype.work_order.test_work_order import make_wo_order_test_record
|
||||||
from erpnext.manufacturing.doctype.work_order.work_order import (
|
from erpnext.manufacturing.doctype.work_order.work_order import (
|
||||||
|
|||||||
Reference in New Issue
Block a user