feat: 库存看板增加 BOM 物料齐套视角 + BOM 详情显示当前在库/状态

- 库存看板新增「BOM 物料齐套」标签页(仅 admin/manager), 逐棵 BOM 列出组成物料的
  单套用量/当前在库/状态, 并给出整体齐套结论
- BOM 详情页组成物料表新增「当前在库」「状态」两列
- 抽出统一 _status_of() 判定函数, 红/黄/绿逻辑与看板一致
- 新增 tools/verify_bom_board.py 端到端验证脚本(已通过)
- 新增使用教程 物料入库出库系统_使用教程.md
- .gitignore 忽略 _backup/(旧 EXE 备份)
This commit is contained in:
2026-09-22 11:47:07 +08:00
parent 7d34a481b3
commit 68b4831d8b
7 changed files with 476 additions and 37 deletions
+46 -8
View File
@@ -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)