feat: BOM型号可编辑 + 生产订单BOM去括号 + 物料明细各工厂实时库存

- BOM编辑页「型号」输入框取消 readonly,可修改;bom_edit 增加非空与唯一校验(重复提示「BOM 型号已存在」、空值提示「BOM 型号必填」),改名不影响关联生产订单与流水
- 生产订单列表/详情/表单中 BOM 显示由 CODE (NAME) 改为 CODE · NAME,去掉括号
- 物料明细页新增「各工厂实时库存」表(工厂编号/名称/实时库存),显示每个工厂的实时聚合库存(入−出),不受顶栏工厂过滤影响
This commit is contained in:
2026-10-15 14:03:16 +08:00
parent 012e018b12
commit 639d7f1545
6 changed files with 44 additions and 5 deletions
+24 -1
View File
@@ -831,11 +831,21 @@ def material_detail(mid):
stmt_out.order_by(Outbound.occur_date.desc(), Outbound.id.desc())
).all()
fmap = {f.id: f.name for f in db.session.scalars(db.select(Factory)).all()}
# 各工厂实时库存(按工厂拆分, 实时聚合 inbound-outbound)
inb_by_f = {r[0]: float(r[1]) for r in db.session.execute(
db.text("SELECT factory_id, COALESCE(SUM(qty),0) FROM inbound WHERE material_id=:mid GROUP BY factory_id"),
{"mid": mid}).fetchall()}
out_by_f = {r[0]: float(r[1]) for r in db.session.execute(
db.text("SELECT factory_id, COALESCE(SUM(qty),0) FROM outbound WHERE material_id=:mid GROUP BY factory_id"),
{"mid": mid}).fetchall()}
per_factory = [{"f": f, "stock": inb_by_f.get(f.id, 0) - out_by_f.get(f.id, 0)}
for f in db.session.scalars(db.select(Factory).order_by(Factory.code)).all()]
return render_template("material_detail.html", m=m,
stock=sm.get(m.id, 0),
inb_total=inb_total.get(m.id, 0),
out_total=out_total.get(m.id, 0),
inbounds=inbounds, outbounds=outbounds, fmap=fmap)
inbounds=inbounds, outbounds=outbounds, fmap=fmap,
per_factory=per_factory)
# ---------------- 入库 / 出库 ----------------
@@ -1366,8 +1376,21 @@ def bom_edit(bid):
return redirect(url_for("boms"))
mats = db.session.scalars(db.select(Material).order_by(Material.part_number)).all()
if request.method == "POST":
# BOM 型号(简码)可编辑: 校验非空 + 不与其它 BOM 重复
new_code = request.form.get("code", "").strip()
b.name = request.form.get("name", "")
b.note = request.form.get("note", "")
raw = db.session.scalars(db.select(BomItem).where(BomItem.bom_id == b.id)).all()
items = [{"pn": db.session.get(Material, it.material_id).part_number,
"qty": it.qty_per_unit} for it in raw]
if not new_code:
flash("BOM 型号必填", "error")
return render_template("bom_form.html", b=b, mats=mats, items=items, form_code=new_code)
dup = db.session.scalar(db.select(Bom).where(Bom.code == new_code, Bom.id != b.id))
if dup:
flash("BOM 型号已存在", "error")
return render_template("bom_form.html", b=b, mats=mats, items=items, form_code=new_code)
b.code = new_code
db.session.execute(db.delete(BomItem).where(BomItem.bom_id == b.id))
pns = request.form.getlist("pn")
qts = request.form.getlist("qty")