mirror of
https://github.com/frappe/erpnext.git
synced 2026-08-17 00:18:39 +00:00
fix(manufacturing): correct nested BOM quantities
This commit is contained in:
@@ -25,10 +25,12 @@ def fetch_exploded_bom_items(root_bom):
|
||||
recursive CTE -- replaces a query-per-node walk with a single query. UNION keeps it cycle-safe
|
||||
and fetches each sub-BOM's items only once even when it is reused across the tree."""
|
||||
bom_item = frappe.qb.DocType("BOM Item")
|
||||
child_bom = frappe.qb.DocType("BOM").as_("child_bom")
|
||||
tree = frappe.qb.Table("exploded_bom")
|
||||
fields = [
|
||||
bom_item.parent,
|
||||
bom_item.qty,
|
||||
bom_item.stock_qty,
|
||||
bom_item.bom_no,
|
||||
bom_item.item_code,
|
||||
bom_item.item_name,
|
||||
@@ -46,7 +48,11 @@ def fetch_exploded_bom_items(root_bom):
|
||||
.where(tree.bom_no != "")
|
||||
)
|
||||
rows = (
|
||||
frappe.qb.with_(seed + recursion, "exploded_bom", recursive=True).from_(tree).select(tree.star)
|
||||
frappe.qb.with_(seed + recursion, "exploded_bom", recursive=True)
|
||||
.from_(tree)
|
||||
.left_join(child_bom)
|
||||
.on(tree.bom_no == child_bom.name)
|
||||
.select(tree.star, child_bom.quantity.as_("child_bom_qty"))
|
||||
).run(as_dict=True)
|
||||
|
||||
children_map = defaultdict(list)
|
||||
@@ -71,7 +77,13 @@ def build_exploded_rows(bom, children_map, data, indent=0, qty=1):
|
||||
}
|
||||
)
|
||||
if item.bom_no:
|
||||
build_exploded_rows(item.bom_no, children_map, data, indent + 1, item.qty)
|
||||
build_exploded_rows(
|
||||
item.bom_no,
|
||||
children_map,
|
||||
data,
|
||||
indent + 1,
|
||||
qty * item.stock_qty / item.child_bom_qty,
|
||||
)
|
||||
|
||||
|
||||
def get_columns():
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
import frappe
|
||||
|
||||
from erpnext.manufacturing.doctype.bom.test_bom import create_nested_bom
|
||||
from erpnext.manufacturing.report.bom_explorer.bom_explorer import execute
|
||||
from erpnext.manufacturing.report.bom_explorer.bom_explorer import build_exploded_rows, execute
|
||||
from erpnext.tests.utils import ERPNextTestSuite
|
||||
|
||||
|
||||
@@ -78,3 +78,55 @@ class TestBOMExplorer(ERPNextTestSuite):
|
||||
# The leaf belongs to the sub-assembly, so it is exploded one level deeper.
|
||||
self.assertEqual(rows_by_item[leaf_item]["indent"], 1)
|
||||
self.assertEqual(rows_by_item[leaf_item]["bom_level"], 1)
|
||||
|
||||
def test_nested_bom_uses_stock_qty_for_output_normalization(self):
|
||||
parent_bom = create_nested_bom(
|
||||
{"parent": {"sub": {"leaf": {}}}},
|
||||
prefix="_Test explorer converted quantity ",
|
||||
)
|
||||
sub_bom = frappe.get_doc("BOM", parent_bom.items[0].bom_no)
|
||||
|
||||
# The parent needs two boxes (20 units). The child BOM produces five units per batch.
|
||||
frappe.db.set_value("BOM", sub_bom.name, "quantity", 5)
|
||||
frappe.db.set_value("BOM Item", sub_bom.items[0].name, {"qty": 3, "stock_qty": 3})
|
||||
frappe.db.set_value(
|
||||
"BOM Item",
|
||||
parent_bom.items[0].name,
|
||||
{"qty": 2, "uom": "Box", "conversion_factor": 10, "stock_qty": 20},
|
||||
)
|
||||
|
||||
data = self.run_report(parent_bom.name)
|
||||
rows_by_item = {row["item_code"]: row for row in data}
|
||||
|
||||
self.assertEqual(rows_by_item["_Test explorer converted quantity sub"]["qty"], 2)
|
||||
self.assertEqual(rows_by_item["_Test explorer converted quantity leaf"]["qty"], 12)
|
||||
|
||||
def test_nested_bom_multiplies_qty_at_every_level(self):
|
||||
children_map = {
|
||||
"root": [
|
||||
frappe._dict(
|
||||
item_code="parent",
|
||||
idx=1,
|
||||
bom_no="parent-bom",
|
||||
child_bom_qty=1,
|
||||
qty=8,
|
||||
stock_qty=8,
|
||||
)
|
||||
],
|
||||
"parent-bom": [
|
||||
frappe._dict(
|
||||
item_code="child",
|
||||
idx=1,
|
||||
bom_no="child-bom",
|
||||
child_bom_qty=1,
|
||||
qty=4,
|
||||
stock_qty=4,
|
||||
)
|
||||
],
|
||||
"child-bom": [frappe._dict(item_code="raw-material", idx=1, bom_no="", qty=2, stock_qty=2)],
|
||||
}
|
||||
data = []
|
||||
|
||||
build_exploded_rows("root", children_map, data)
|
||||
|
||||
self.assertEqual([row["qty"] for row in data], [8, 32, 64])
|
||||
|
||||
Reference in New Issue
Block a user