mirror of
https://github.com/frappe/erpnext.git
synced 2026-09-24 13:57:05 +00:00
fix: retain bom components without warehouse stock (#59116)
Co-authored-by: Mihir Kandoi <kandoimihir@gmail.com>
(cherry picked from commit 60913b722a)
This commit is contained in:
@@ -196,10 +196,29 @@ def get_bom_data(filters):
|
||||
bom_item = frappe.qb.DocType(bom_item_table)
|
||||
bin = frappe.qb.DocType("Bin")
|
||||
|
||||
stock_join_condition = bom_item.item_code == bin.item_code
|
||||
if filters.get("warehouse"):
|
||||
warehouse_details = frappe.db.get_value(
|
||||
"Warehouse", filters.get("warehouse"), ["lft", "rgt"], as_dict=1
|
||||
)
|
||||
if warehouse_details:
|
||||
wh = frappe.qb.DocType("Warehouse")
|
||||
stock_join_condition &= ExistsCriterion(
|
||||
frappe.qb.from_(wh)
|
||||
.select(wh.name)
|
||||
.where(
|
||||
(wh.lft >= warehouse_details.lft)
|
||||
& (wh.rgt <= warehouse_details.rgt)
|
||||
& (bin.warehouse == wh.name)
|
||||
)
|
||||
)
|
||||
else:
|
||||
stock_join_condition &= bin.warehouse == filters.get("warehouse")
|
||||
|
||||
query = (
|
||||
frappe.qb.from_(bom_item)
|
||||
.left_join(bin)
|
||||
.on(bom_item.item_code == bin.item_code)
|
||||
.on(stock_join_condition)
|
||||
.select(
|
||||
bom_item.item_code,
|
||||
bom_item.description,
|
||||
@@ -212,26 +231,6 @@ def get_bom_data(filters):
|
||||
.orderby(bom_item.idx)
|
||||
)
|
||||
|
||||
if filters.get("warehouse"):
|
||||
warehouse_details = frappe.db.get_value(
|
||||
"Warehouse", filters.get("warehouse"), ["lft", "rgt"], as_dict=1
|
||||
)
|
||||
if warehouse_details:
|
||||
wh = frappe.qb.DocType("Warehouse")
|
||||
query = query.where(
|
||||
ExistsCriterion(
|
||||
frappe.qb.from_(wh)
|
||||
.select(wh.name)
|
||||
.where(
|
||||
(wh.lft >= warehouse_details.lft)
|
||||
& (wh.rgt <= warehouse_details.rgt)
|
||||
& (bin.warehouse == wh.name)
|
||||
)
|
||||
)
|
||||
)
|
||||
else:
|
||||
query = query.where(bin.warehouse == filters.get("warehouse"))
|
||||
|
||||
if bom_item_table == "BOM Item":
|
||||
query = query.select(bom_item.bom_no, bom_item.is_phantom_item)
|
||||
|
||||
|
||||
@@ -8,6 +8,10 @@ from erpnext.manufacturing.report.bom_stock_analysis.bom_stock_analysis import (
|
||||
execute as bom_stock_analysis_report,
|
||||
)
|
||||
from erpnext.stock.doctype.item.test_item import make_item
|
||||
from erpnext.stock.doctype.stock_reconciliation.test_stock_reconciliation import (
|
||||
create_stock_reconciliation,
|
||||
)
|
||||
from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse
|
||||
from erpnext.tests.utils import ERPNextTestSuite
|
||||
|
||||
|
||||
@@ -79,6 +83,41 @@ 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 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."""
|
||||
|
||||
Reference in New Issue
Block a user