feat: 库存看板增加 BOM 物料齐套视角 + BOM 详情显示当前在库/状态
- 库存看板新增「BOM 物料齐套」标签页(仅 admin/manager), 逐棵 BOM 列出组成物料的 单套用量/当前在库/状态, 并给出整体齐套结论 - BOM 详情页组成物料表新增「当前在库」「状态」两列 - 抽出统一 _status_of() 判定函数, 红/黄/绿逻辑与看板一致 - 新增 tools/verify_bom_board.py 端到端验证脚本(已通过) - 新增使用教程 物料入库出库系统_使用教程.md - .gitignore 忽略 _backup/(旧 EXE 备份)
This commit is contained in:
@@ -99,6 +99,15 @@ def stock_map():
|
||||
return {i: inb.get(i, 0) - out.get(i, 0) for i in ids}
|
||||
|
||||
|
||||
def _status_of(stock, safety):
|
||||
"""库存状态: danger=负库存, warn=低于安全库存, ok=正常"""
|
||||
if stock < 0:
|
||||
return "danger"
|
||||
if stock < (safety or 0):
|
||||
return "warn"
|
||||
return "ok"
|
||||
|
||||
|
||||
def inbound_total_map():
|
||||
"""返回 {material_id: 入库总数量}"""
|
||||
rows = db.session.execute(
|
||||
@@ -276,16 +285,40 @@ def dashboard():
|
||||
low = 0
|
||||
for m in mats:
|
||||
stock = sm.get(m.id, 0)
|
||||
if stock < 0:
|
||||
status = _status_of(stock, m.safety_stock)
|
||||
if status == "danger":
|
||||
negative += 1
|
||||
status = "danger"
|
||||
elif stock < (m.safety_stock or 0):
|
||||
elif status == "warn":
|
||||
low += 1
|
||||
status = "warn"
|
||||
else:
|
||||
status = "ok"
|
||||
rows.append({"m": m, "stock": stock, "status": status})
|
||||
return render_template("dashboard.html", rows=rows, negative=negative, low=low)
|
||||
|
||||
# BOM 物料齐套看板(仅管理员/主管可见)
|
||||
can_bom = bool(g.user and g.user.role in ("admin", "manager"))
|
||||
bom_board = []
|
||||
if can_bom:
|
||||
boms = db.session.scalars(db.select(Bom).order_by(Bom.code)).all()
|
||||
for b in boms:
|
||||
items = db.session.scalars(
|
||||
db.select(BomItem).where(BomItem.bom_id == b.id).order_by(BomItem.id)
|
||||
).all()
|
||||
item_rows = []
|
||||
for it in items:
|
||||
m = db.session.get(Material, it.material_id)
|
||||
if not m:
|
||||
continue
|
||||
stock = sm.get(m.id, 0)
|
||||
item_rows.append({"m": m, "qty": it.qty_per_unit,
|
||||
"stock": stock, "status": _status_of(stock, m.safety_stock)})
|
||||
if any(x["status"] == "danger" for x in item_rows):
|
||||
bstatus = "danger"
|
||||
elif any(x["status"] == "warn" for x in item_rows):
|
||||
bstatus = "warn"
|
||||
else:
|
||||
bstatus = "ok"
|
||||
bom_board.append({"b": b, "parts": item_rows, "status": bstatus})
|
||||
|
||||
return render_template("dashboard.html", rows=rows, negative=negative, low=low,
|
||||
bom_board=bom_board, can_bom=can_bom)
|
||||
|
||||
|
||||
# ---------------- 物料 ----------------
|
||||
@@ -555,10 +588,15 @@ def bom_detail(bid):
|
||||
db.select(BomItem).where(BomItem.bom_id == b.id)
|
||||
.order_by(BomItem.id)
|
||||
).all()
|
||||
sm = stock_map()
|
||||
rows = []
|
||||
for it in items:
|
||||
m = db.session.get(Material, it.material_id)
|
||||
rows.append({"m": m, "qty": it.qty_per_unit})
|
||||
if not m:
|
||||
continue
|
||||
stock = sm.get(m.id, 0)
|
||||
rows.append({"m": m, "qty": it.qty_per_unit,
|
||||
"stock": stock, "status": _status_of(stock, m.safety_stock)})
|
||||
return render_template("bom_detail.html", b=b, rows=rows)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user