mirror of
https://github.com/frappe/erpnext.git
synced 2026-05-16 03:29:16 +00:00
Merge pull request #52323 from frappe/mergify/bp/version-15-hotfix/pr-52184
fix(subcontracting): include item bom in supplied items grouping key (backport #52184)
This commit is contained in:
@@ -254,10 +254,10 @@ class SubcontractingController(StockController):
|
|||||||
):
|
):
|
||||||
for row in frappe.get_all(
|
for row in frappe.get_all(
|
||||||
f"{self.subcontract_data.order_doctype} Item",
|
f"{self.subcontract_data.order_doctype} Item",
|
||||||
fields=["item_code", "(qty - received_qty) as qty", "parent", "name"],
|
fields=["item_code", "(qty - received_qty) as qty", "parent", "bom"],
|
||||||
filters={"docstatus": 1, "parent": ("in", self.subcontract_orders)},
|
filters={"docstatus": 1, "parent": ("in", self.subcontract_orders)},
|
||||||
):
|
):
|
||||||
self.qty_to_be_received[(row.item_code, row.parent)] += row.qty
|
self.qty_to_be_received[(row.item_code, row.parent, row.bom)] += row.qty
|
||||||
|
|
||||||
def __get_transferred_items(self):
|
def __get_transferred_items(self):
|
||||||
se = frappe.qb.DocType("Stock Entry")
|
se = frappe.qb.DocType("Stock Entry")
|
||||||
@@ -829,13 +829,17 @@ class SubcontractingController(StockController):
|
|||||||
self.__set_serial_nos(item_row, rm_obj)
|
self.__set_serial_nos(item_row, rm_obj)
|
||||||
|
|
||||||
def __get_qty_based_on_material_transfer(self, item_row, transfer_item):
|
def __get_qty_based_on_material_transfer(self, item_row, transfer_item):
|
||||||
key = (item_row.item_code, item_row.get(self.subcontract_data.order_field))
|
key = (
|
||||||
|
item_row.item_code,
|
||||||
|
item_row.get(self.subcontract_data.order_field),
|
||||||
|
item_row.get("bom"),
|
||||||
|
)
|
||||||
|
|
||||||
if self.qty_to_be_received == item_row.qty:
|
if self.qty_to_be_received == item_row.qty:
|
||||||
return transfer_item.qty
|
return transfer_item.qty
|
||||||
|
|
||||||
if self.qty_to_be_received:
|
if self.qty_to_be_received.get(key):
|
||||||
qty = (flt(item_row.qty) * flt(transfer_item.qty)) / flt(self.qty_to_be_received.get(key, 0))
|
qty = (flt(item_row.qty) * flt(transfer_item.qty)) / flt(self.qty_to_be_received.get(key))
|
||||||
transfer_item.item_details.required_qty = transfer_item.qty
|
transfer_item.item_details.required_qty = transfer_item.qty
|
||||||
|
|
||||||
if transfer_item.serial_no or frappe.get_cached_value(
|
if transfer_item.serial_no or frappe.get_cached_value(
|
||||||
@@ -880,7 +884,11 @@ class SubcontractingController(StockController):
|
|||||||
|
|
||||||
if self.qty_to_be_received:
|
if self.qty_to_be_received:
|
||||||
self.qty_to_be_received[
|
self.qty_to_be_received[
|
||||||
(row.item_code, row.get(self.subcontract_data.order_field))
|
(
|
||||||
|
row.item_code,
|
||||||
|
row.get(self.subcontract_data.order_field),
|
||||||
|
row.get("bom"),
|
||||||
|
)
|
||||||
] -= row.qty
|
] -= row.qty
|
||||||
|
|
||||||
def __set_rate_for_serial_and_batch_bundle(self):
|
def __set_rate_for_serial_and_batch_bundle(self):
|
||||||
|
|||||||
@@ -618,6 +618,117 @@ class TestSubcontractingReceipt(FrappeTestCase):
|
|||||||
for item in scr.supplied_items:
|
for item in scr.supplied_items:
|
||||||
self.assertFalse(item.available_qty_for_consumption)
|
self.assertFalse(item.available_qty_for_consumption)
|
||||||
|
|
||||||
|
def test_supplied_items_consumed_qty_for_similar_finished_goods(self):
|
||||||
|
"""
|
||||||
|
Test that supplied raw material consumption is calculated correctly
|
||||||
|
when multiple subcontracted service items use the same finished good
|
||||||
|
but different BOMs.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from erpnext.controllers.subcontracting_controller import (
|
||||||
|
make_rm_stock_entry as make_subcontract_transfer_entry,
|
||||||
|
)
|
||||||
|
from erpnext.manufacturing.doctype.production_plan.test_production_plan import make_bom
|
||||||
|
|
||||||
|
# Configuration: Backflush based on subcontract material transfer
|
||||||
|
set_backflush_based_on("Material Transferred for Subcontract")
|
||||||
|
|
||||||
|
# Create Raw Materials
|
||||||
|
raw_material_1 = make_item("_RM Item 1", properties={"is_stock_item": 1}).name
|
||||||
|
|
||||||
|
raw_material_2 = make_item("_RM Item 2", properties={"is_stock_item": 1}).name
|
||||||
|
|
||||||
|
# Create Subcontracted Finished Good
|
||||||
|
finished_good = make_item("_Finished Good Item", properties={"is_stock_item": 1})
|
||||||
|
finished_good.is_sub_contracted_item = 1
|
||||||
|
finished_good.save()
|
||||||
|
|
||||||
|
# Receive Raw Materials into Warehouse
|
||||||
|
for raw_material in (raw_material_1, raw_material_2):
|
||||||
|
make_stock_entry(
|
||||||
|
item_code=raw_material,
|
||||||
|
qty=10,
|
||||||
|
target="_Test Warehouse - _TC",
|
||||||
|
basic_rate=100,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Create BOMs for the same Finished Good with different RMs
|
||||||
|
bom_rm_1 = make_bom(
|
||||||
|
item=finished_good.name,
|
||||||
|
quantity=1,
|
||||||
|
raw_materials=[raw_material_1],
|
||||||
|
).name
|
||||||
|
|
||||||
|
_bom_rm_2 = make_bom(
|
||||||
|
item=finished_good.name,
|
||||||
|
quantity=1,
|
||||||
|
raw_materials=[raw_material_2],
|
||||||
|
).name
|
||||||
|
|
||||||
|
# Define Subcontracted Service Items
|
||||||
|
service_items = [
|
||||||
|
{
|
||||||
|
"warehouse": "_Test Warehouse - _TC",
|
||||||
|
"item_code": "Subcontracted Service Item 1",
|
||||||
|
"qty": 1,
|
||||||
|
"rate": 100,
|
||||||
|
"fg_item": finished_good.name,
|
||||||
|
"fg_item_qty": 10,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"warehouse": "_Test Warehouse - _TC",
|
||||||
|
"item_code": "Subcontracted Service Item 1",
|
||||||
|
"qty": 1,
|
||||||
|
"rate": 150,
|
||||||
|
"fg_item": finished_good.name,
|
||||||
|
"fg_item_qty": 10,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
# Create Subcontracting Order
|
||||||
|
subcontracting_order = get_subcontracting_order(
|
||||||
|
service_items=service_items,
|
||||||
|
do_not_save=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Assign BOM only to the first service item
|
||||||
|
subcontracting_order.items[0].bom = bom_rm_1
|
||||||
|
subcontracting_order.save()
|
||||||
|
subcontracting_order.submit()
|
||||||
|
|
||||||
|
# Prepare Raw Material Transfer Items
|
||||||
|
raw_material_transfer_items = []
|
||||||
|
for supplied_item in subcontracting_order.supplied_items:
|
||||||
|
raw_material_transfer_items.append(
|
||||||
|
{
|
||||||
|
"item_code": supplied_item.main_item_code,
|
||||||
|
"rm_item_code": supplied_item.rm_item_code,
|
||||||
|
"qty": supplied_item.required_qty,
|
||||||
|
"warehouse": "_Test Warehouse - _TC",
|
||||||
|
"stock_uom": "Nos",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
# Transfer Raw Materials to Subcontractor Warehouse
|
||||||
|
stock_entry = frappe.get_doc(
|
||||||
|
make_subcontract_transfer_entry(
|
||||||
|
subcontracting_order.name,
|
||||||
|
raw_material_transfer_items,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
stock_entry.to_warehouse = "_Test Warehouse 1 - _TC"
|
||||||
|
stock_entry.save()
|
||||||
|
stock_entry.submit()
|
||||||
|
|
||||||
|
# Create Subcontracting Receipt
|
||||||
|
subcontracting_receipt = make_subcontracting_receipt(subcontracting_order.name)
|
||||||
|
subcontracting_receipt.save()
|
||||||
|
|
||||||
|
# Check consumed_qty for each supplied item
|
||||||
|
self.assertEqual(len(subcontracting_receipt.supplied_items), 2)
|
||||||
|
self.assertEqual(subcontracting_receipt.supplied_items[0].consumed_qty, 10)
|
||||||
|
self.assertEqual(subcontracting_receipt.supplied_items[1].consumed_qty, 10)
|
||||||
|
|
||||||
def test_supplied_items_cost_after_reposting(self):
|
def test_supplied_items_cost_after_reposting(self):
|
||||||
# Set Backflush Based On as "BOM"
|
# Set Backflush Based On as "BOM"
|
||||||
set_backflush_based_on("BOM")
|
set_backflush_based_on("BOM")
|
||||||
|
|||||||
Reference in New Issue
Block a user