mirror of
https://github.com/frappe/erpnext.git
synced 2026-09-19 11:27:55 +00:00
fix: retain bom components without warehouse stock (#59116)
Co-authored-by: Mihir Kandoi <kandoimihir@gmail.com>
This commit is contained in:
@@ -229,11 +229,10 @@ def get_bom_data(filters):
|
||||
bom_item = frappe.qb.DocType(bom_item_table)
|
||||
stock_qty = get_stock_qty_by_item(filters).as_("stock_qty")
|
||||
|
||||
base = frappe.qb.from_(bom_item)
|
||||
base = base.join(stock_qty) if filters.get("warehouse") else base.left_join(stock_qty)
|
||||
|
||||
query = (
|
||||
base.on(bom_item.item_code == stock_qty.item_code)
|
||||
frappe.qb.from_(bom_item)
|
||||
.left_join(stock_qty)
|
||||
.on(bom_item.item_code == stock_qty.item_code)
|
||||
.select(
|
||||
bom_item.item_code,
|
||||
# non-grouped columns are constant per grouped item_code -> Max() keeps the GROUP BY valid
|
||||
|
||||
@@ -84,6 +84,29 @@ class TestBOMStockAnalysis(ERPNextTestSuite):
|
||||
)
|
||||
self.assertEqual(footer.get("description"), expected_min)
|
||||
|
||||
def test_components_without_stock_in_selected_warehouse_remain_visible(self):
|
||||
group = create_warehouse("_Test BOM Stock Analysis Group", {"is_group": 1})
|
||||
warehouse = create_warehouse("_Test BOM Stock Analysis Stores", {"parent_warehouse": group})
|
||||
stocked_item, missing_item = self.rm_items
|
||||
create_stock_reconciliation(item_code=stocked_item, warehouse=warehouse, qty=100, rate=100)
|
||||
self.assertFalse(frappe.db.exists("Bin", {"item_code": missing_item, "warehouse": warehouse}))
|
||||
|
||||
for selected_warehouse in (warehouse, group):
|
||||
for exploded in (False, True):
|
||||
with self.subTest(warehouse=selected_warehouse, exploded=exploded):
|
||||
items, footer = run_report(self.boms[0].name, selected_warehouse, exploded, qty_to_make=1)
|
||||
self.assertEqual(set(items), {stocked_item, missing_item})
|
||||
self.assertEqual(items[stocked_item]["available_qty"], fmt_qty(100))
|
||||
self.assertEqual(items[missing_item]["available_qty"], fmt_qty(0))
|
||||
self.assertEqual(items[missing_item]["required_qty"], fmt_qty(10))
|
||||
self.assertEqual(items[missing_item]["difference_qty"], fmt_qty(-10))
|
||||
self.assertEqual(footer["description"], 0)
|
||||
|
||||
items, footer = run_report(self.boms[0].name, warehouse, exploded=False, qty_to_make=0)
|
||||
self.assertEqual(set(items), {stocked_item, missing_item})
|
||||
self.assertEqual(items[missing_item]["available_qty"], fmt_qty(0))
|
||||
self.assertEqual(footer["description"], 0)
|
||||
|
||||
def _build_duplicate_component_bom(self, phantom_first):
|
||||
"""Parent BOM that lists one `component` twice, once via a phantom sub-BOM and once via a
|
||||
non-phantom sub-BOM. `phantom_first` controls which line is at idx 1. Returns the names of
|
||||
@@ -187,6 +210,18 @@ class TestBOMStockAnalysis(ERPNextTestSuite):
|
||||
self.assertAlmostEqual(flt(rows[0].actual_qty), 10.0, places=6)
|
||||
|
||||
|
||||
def run_report(bom, warehouse, exploded, qty_to_make):
|
||||
"""Component rows keyed by item code, plus the footer row."""
|
||||
filters = {
|
||||
"bom": bom,
|
||||
"warehouse": warehouse,
|
||||
"show_exploded_view": exploded,
|
||||
"qty_to_make": qty_to_make,
|
||||
}
|
||||
data, footer = split_data_and_footer(bom_stock_analysis_report(filters)[1])
|
||||
return {row["item"]: row for row in data}, footer
|
||||
|
||||
|
||||
def split_data_and_footer(raw_data):
|
||||
"""Separate component rows from the footer row. Skips blank spacer rows."""
|
||||
data = [row for row in raw_data if row and not row.get("bold")]
|
||||
|
||||
Reference in New Issue
Block a user