feat: 生产订单支持一次多选 BOM 且各 BOM 独立设置生产数量
- 新增中间表 prod_order_bom(order_id, bom_id, qty),一个订单可挂多个 BOM,每个 BOM 单独设生产数量 - prod_order.qty 改为各 BOM 数量汇总,bom_id 仅作主 BOM 冗余摘要 - 物料需求按 Σ(BOM 单套用量 × 各自生产数量) 跨 BOM 合并聚合,覆盖详情/领料/调入/导出 - 订单表单改为动态多行(BOM 下拉 + 数量输入 + 删除行),列表与导出展示「BOM清单(型号×数量)」 - 导出 Excel 增至 3 个 sheet:订单信息 / BOM清单 / 领料明细 - 修复 _parse_bom_pairs 表单字段名 bom_id[]/qty[] 解析;order_delete 一并级联删除 prod_order_bom - build_exe.py 增加构建后自动拷贝 dist_pkg→dist(EXE 同目录即数据库所在) Co-Authored-By: WorkBuddy <workbuddy@tencent.com>
This commit is contained in:
@@ -20,7 +20,7 @@ from flask import (Flask, render_template, request, redirect, url_for,
|
||||
from sqlalchemy import update
|
||||
|
||||
from models import (db, User, Material, Inbound, Outbound,
|
||||
Bom, BomItem, ProdOrder, ProdOrderAlloc, OperationLog, Factory, Transfer, Substitute, PrepPlan)
|
||||
Bom, BomItem, ProdOrder, ProdOrderAlloc, ProdOrderBom, OperationLog, Factory, Transfer, Substitute, PrepPlan)
|
||||
|
||||
# ---------------- 路径(兼容 PyInstaller 冻结) ----------------
|
||||
if getattr(sys, "frozen", False):
|
||||
@@ -1462,9 +1462,10 @@ def orders():
|
||||
os_ = db.session.scalars(db.select(ProdOrder).order_by(ProdOrder.order_no.desc())).all()
|
||||
data = []
|
||||
for o in os_:
|
||||
bom = db.session.get(Bom, o.bom_id)
|
||||
lines = order_bom_lines(o)
|
||||
fac = db.session.get(Factory, o.factory_id) if o.factory_id else None
|
||||
data.append({"o": o, "bom": bom, "factory": fac})
|
||||
total = sum(q for _, q in lines)
|
||||
data.append({"o": o, "lines": lines, "factory": fac, "total": total})
|
||||
return render_template("orders.html", data=data)
|
||||
|
||||
|
||||
@@ -1476,9 +1477,10 @@ def orders_export():
|
||||
os_ = db.session.scalars(db.select(ProdOrder).order_by(ProdOrder.order_no.desc())).all()
|
||||
data = []
|
||||
for o in os_:
|
||||
bom = db.session.get(Bom, o.bom_id)
|
||||
lines = order_bom_lines(o)
|
||||
fac = db.session.get(Factory, o.factory_id) if o.factory_id else None
|
||||
data.append({"o": o, "bom": bom, "factory": fac})
|
||||
total = sum(q for _, q in lines)
|
||||
data.append({"o": o, "lines": lines, "factory": fac, "total": total})
|
||||
from excel_io import export_orders
|
||||
path = os.path.join(EXE_DIR, f"生产订单_{date.today().isoformat()}.xlsx")
|
||||
export_orders(path, data)
|
||||
@@ -1492,24 +1494,30 @@ def order_new():
|
||||
boms_all = db.session.scalars(db.select(Bom).order_by(Bom.code)).all()
|
||||
factories = db.session.scalars(db.select(Factory).order_by(Factory.code)).all()
|
||||
if request.method == "POST":
|
||||
bom_id = int(request.form["bom_id"])
|
||||
qty = float(request.form["qty"])
|
||||
pairs = _parse_bom_pairs(request.form)
|
||||
fid_raw = (request.form.get("factory_id") or "").strip()
|
||||
factory_id = int(fid_raw) if fid_raw.isdigit() else None
|
||||
if qty <= 0:
|
||||
flash("接单量必须大于 0", "error")
|
||||
if not pairs:
|
||||
flash("请至少选择一个 BOM 并填写大于 0 的生产数量", "error")
|
||||
else:
|
||||
order_no = request.form.get("order_no", "").strip() or _gen_order_no()
|
||||
if db.session.scalar(db.select(ProdOrder).where(ProdOrder.order_no == order_no)):
|
||||
order_no = _gen_order_no()
|
||||
o = ProdOrder(order_no=order_no, bom_id=bom_id, factory_id=factory_id, qty=qty,
|
||||
status="planned", operator=(g.user.display_name or g.user.username),
|
||||
total = sum(q for _, q in pairs)
|
||||
o = ProdOrder(order_no=order_no, bom_id=pairs[0][0], factory_id=factory_id,
|
||||
qty=total, status="planned",
|
||||
operator=(g.user.display_name or g.user.username),
|
||||
remark=request.form.get("remark", "").strip())
|
||||
db.session.add(o)
|
||||
db.session.flush() # 需要 o.id 供关联行使用
|
||||
for bid, qv in pairs:
|
||||
db.session.add(ProdOrderBom(order_id=o.id, bom_id=bid, qty=qv))
|
||||
db.session.commit()
|
||||
log_action("新建生产订单", order_no, f"接单量 {qty}")
|
||||
log_action("新建生产订单", order_no,
|
||||
f"含 {len(pairs)} 个BOM, 总接单量 {total}")
|
||||
return redirect(url_for("order_detail", oid=o.id))
|
||||
return render_template("order_form.html", boms=boms_all, factories=factories, o=None)
|
||||
return render_template("order_form.html", boms=boms_all, factories=factories,
|
||||
o=None, initial_lines=[])
|
||||
|
||||
|
||||
@app.route("/order/<int:oid>")
|
||||
@@ -1519,8 +1527,8 @@ def order_detail(oid):
|
||||
o = db.session.get(ProdOrder, oid)
|
||||
if not o:
|
||||
return redirect(url_for("orders"))
|
||||
bom = db.session.get(Bom, o.bom_id)
|
||||
items = db.session.scalars(db.select(BomItem).where(BomItem.bom_id == o.bom_id)).all()
|
||||
lines = order_bom_lines(o)
|
||||
total_qty = sum(q for _, q in lines)
|
||||
# 查看订单时, 库存/缺口按「下单之前」的工厂库存快照展示(即按下单那一刻的库存),
|
||||
# 按下单时间截断, 自动排除该订单自身后续的调入/领料及下单后发生的其他出入库,
|
||||
# 用于还原下单时的可供料情况, 不受后续出入库影响。
|
||||
@@ -1531,9 +1539,7 @@ def order_detail(oid):
|
||||
db.select(ProdOrderAlloc).where(ProdOrderAlloc.order_id == oid)).all()
|
||||
alloc_map = {a.material_id: a for a in allocs} # material_id -> alloc 行
|
||||
reqs = []
|
||||
for it in items:
|
||||
m = db.session.get(Material, it.material_id)
|
||||
need = it.qty_per_unit * o.qty
|
||||
for m, need in order_aggregated_materials(o):
|
||||
have = sm.get(m.id, 0)
|
||||
a = alloc_map.get(m.id)
|
||||
subs = [{"pn": s["sub"].part_number, "mf": s["sub"].manufacturer or "-",
|
||||
@@ -1551,8 +1557,8 @@ def order_detail(oid):
|
||||
posted_count = sum(1 for a in allocs if a.posted)
|
||||
factories = db.session.scalars(
|
||||
db.select(Factory).where(Factory.enabled == True).order_by(Factory.code)).all()
|
||||
return render_template("order_detail.html", o=o, bom=bom, reqs=reqs, factory=fac,
|
||||
factories=factories,
|
||||
return render_template("order_detail.html", o=o, lines=lines, total_qty=total_qty,
|
||||
reqs=reqs, factory=fac, factories=factories,
|
||||
posted_total=posted_total, planned_total=planned_total,
|
||||
posted_count=posted_count)
|
||||
|
||||
@@ -1565,17 +1571,15 @@ def order_export(oid):
|
||||
o = db.session.get(ProdOrder, oid)
|
||||
if not o:
|
||||
return redirect(url_for("orders"))
|
||||
bom = db.session.get(Bom, o.bom_id)
|
||||
items = db.session.scalars(db.select(BomItem).where(BomItem.bom_id == o.bom_id)).all()
|
||||
lines = order_bom_lines(o)
|
||||
total_qty = sum(q for _, q in lines)
|
||||
sm = stock_map_as_of(o.factory_id, o.created_at)
|
||||
fac = db.session.get(Factory, o.factory_id) if o.factory_id else None
|
||||
allocs = db.session.scalars(
|
||||
db.select(ProdOrderAlloc).where(ProdOrderAlloc.order_id == oid)).all()
|
||||
alloc_map = {a.material_id: a for a in allocs}
|
||||
reqs = []
|
||||
for it in items:
|
||||
m = db.session.get(Material, it.material_id)
|
||||
need = it.qty_per_unit * o.qty
|
||||
for m, need in order_aggregated_materials(o):
|
||||
have = sm.get(m.id, 0)
|
||||
a = alloc_map.get(m.id)
|
||||
subs = [{"pn": s["sub"].part_number, "mf": s["sub"].manufacturer or "-",
|
||||
@@ -1591,7 +1595,7 @@ def order_export(oid):
|
||||
posted_count = sum(1 for a in allocs if a.posted)
|
||||
from excel_io import export_order_detail
|
||||
path = os.path.join(EXE_DIR, f"生产订单_{o.order_no}.xlsx")
|
||||
export_order_detail(path, o, bom, reqs, fac, posted_total, planned_total, posted_count)
|
||||
export_order_detail(path, o, lines, reqs, fac, posted_total, planned_total, posted_count)
|
||||
return send_file(path, as_attachment=True, download_name=os.path.basename(path))
|
||||
|
||||
|
||||
@@ -1602,13 +1606,10 @@ def order_pick(oid):
|
||||
o = db.session.get(ProdOrder, oid)
|
||||
if not o or o.status == "picked":
|
||||
return redirect(url_for("orders"))
|
||||
items = db.session.scalars(db.select(BomItem).where(BomItem.bom_id == o.bom_id)).all()
|
||||
sm = stock_map(o.factory_id)
|
||||
picks = 0
|
||||
shorts = []
|
||||
for it in items:
|
||||
m = db.session.get(Material, it.material_id)
|
||||
need = it.qty_per_unit * o.qty
|
||||
for m, need in order_aggregated_materials(o):
|
||||
if need <= 0:
|
||||
continue
|
||||
db.session.add(Outbound(material_id=m.id, factory_id=o.factory_id, qty=need,
|
||||
@@ -1638,51 +1639,57 @@ def order_edit(oid):
|
||||
boms_all = db.session.scalars(db.select(Bom).order_by(Bom.code)).all()
|
||||
factories = db.session.scalars(db.select(Factory).order_by(Factory.code)).all()
|
||||
if request.method == "POST":
|
||||
bom_id = int(request.form["bom_id"])
|
||||
qty = float(request.form["qty"])
|
||||
pairs = _parse_bom_pairs(request.form)
|
||||
fid_raw = (request.form.get("factory_id") or "").strip()
|
||||
factory_id = int(fid_raw) if fid_raw.isdigit() else None
|
||||
if qty <= 0:
|
||||
flash("接单量必须大于 0", "error")
|
||||
if not pairs:
|
||||
flash("请至少选择一个 BOM 并填写大于 0 的生产数量", "error")
|
||||
return render_template("order_form.html", boms=boms_all, factories=factories,
|
||||
o=o, initial_lines=[[b.id, q] for b, q in order_bom_lines(o)])
|
||||
old_no = o.order_no
|
||||
order_no = request.form.get("order_no", "").strip() or o.order_no
|
||||
if order_no != o.order_no and db.session.scalar(
|
||||
db.select(ProdOrder).where(ProdOrder.order_no == order_no)):
|
||||
flash(f"订单号 {order_no} 已存在, 已保留原单号", "warn")
|
||||
order_no = o.order_no
|
||||
o.order_no = order_no
|
||||
o.bom_id = pairs[0][0]
|
||||
o.factory_id = factory_id
|
||||
o.qty = sum(q for _, q in pairs)
|
||||
o.remark = request.form.get("remark", "").strip()
|
||||
# 同步 BOM 行(删除旧行, 按表单重建); 已录入的「调入数量」计划与已入账流水保持不变
|
||||
db.session.execute(
|
||||
db.delete(ProdOrderBom).where(ProdOrderBom.order_id == o.id))
|
||||
for bid, qv in pairs:
|
||||
db.session.add(ProdOrderBom(order_id=o.id, bom_id=bid, qty=qv))
|
||||
# (需求: 改 BOM/改数量均不冲销调入计划与已入账流水, 它们保持编辑前的值)
|
||||
if order_no != old_no:
|
||||
# 备份后, 把关联流水(订单号作为关联键 order_ref)同步为新单号, 使其余数据保持一致
|
||||
try:
|
||||
_bak = os.path.join(EXE_DIR, "_backup",
|
||||
f"edit_bak_{datetime.now():%Y%m%d_%H%M%S}.db")
|
||||
os.makedirs(os.path.dirname(_bak), exist_ok=True)
|
||||
shutil.copy(db_path, _bak)
|
||||
except Exception:
|
||||
_bak = None
|
||||
db.session.execute(
|
||||
update(Inbound).where(Inbound.order_ref == old_no)
|
||||
.values(order_ref=order_no))
|
||||
db.session.execute(
|
||||
update(Outbound).where(Outbound.order_ref == old_no)
|
||||
.values(order_ref=order_no))
|
||||
log_action("编辑生产订单", order_no,
|
||||
f"含 {len(pairs)} 个BOM, 总接单量 {o.qty}; 订单号 {old_no}→{order_no}, 已同步关联流水 order_ref"
|
||||
+ (f"; 备份 {os.path.basename(_bak)}" if _bak else ""))
|
||||
flash(f"订单已更新(订单号 {old_no}→{order_no}),调入数量与已入账流水保持不变", "ok")
|
||||
else:
|
||||
old_no = o.order_no
|
||||
order_no = request.form.get("order_no", "").strip() or o.order_no
|
||||
if order_no != o.order_no and db.session.scalar(
|
||||
db.select(ProdOrder).where(ProdOrder.order_no == order_no)):
|
||||
flash(f"订单号 {order_no} 已存在, 已保留原单号", "warn")
|
||||
order_no = o.order_no
|
||||
o.order_no = order_no
|
||||
o.bom_id = bom_id
|
||||
o.factory_id = factory_id
|
||||
o.qty = qty
|
||||
o.remark = request.form.get("remark", "").strip()
|
||||
# 仅更新订单头; 已录入的「调入数量」计划与已入账流水保持不变
|
||||
# (需求: 改订单号/改生产数量均不冲销, 调入数量保持编辑前的值)。
|
||||
if order_no != old_no:
|
||||
# 备份后, 把关联流水(订单号作为关联键 order_ref)同步为新单号, 使其余数据保持一致
|
||||
try:
|
||||
_bak = os.path.join(EXE_DIR, "_backup",
|
||||
f"edit_bak_{datetime.now():%Y%m%d_%H%M%S}.db")
|
||||
os.makedirs(os.path.dirname(_bak), exist_ok=True)
|
||||
shutil.copy(db_path, _bak)
|
||||
except Exception:
|
||||
_bak = None
|
||||
db.session.execute(
|
||||
update(Inbound).where(Inbound.order_ref == old_no)
|
||||
.values(order_ref=order_no))
|
||||
db.session.execute(
|
||||
update(Outbound).where(Outbound.order_ref == old_no)
|
||||
.values(order_ref=order_no))
|
||||
log_action("编辑生产订单", order_no,
|
||||
f"接单量 {qty}; 订单号 {old_no}→{order_no}, 已同步关联流水 order_ref"
|
||||
+ (f"; 备份 {os.path.basename(_bak)}" if _bak else ""))
|
||||
flash(f"订单已更新(订单号 {old_no}→{order_no}),调入数量与已入账流水保持不变", "ok")
|
||||
else:
|
||||
log_action("编辑生产订单", order_no, f"接单量 {qty}; 订单头字段更新(其余数据不变)")
|
||||
flash("订单已更新,调入数量与已入账流水保持不变", "ok")
|
||||
db.session.commit()
|
||||
return redirect(url_for("order_detail", oid=oid))
|
||||
return render_template("order_form.html", boms=boms_all, factories=factories, o=o)
|
||||
log_action("编辑生产订单", order_no,
|
||||
f"含 {len(pairs)} 个BOM, 总接单量 {o.qty}; 订单头字段更新(其余数据不变)")
|
||||
flash("订单已更新,调入数量与已入账流水保持不变", "ok")
|
||||
db.session.commit()
|
||||
return redirect(url_for("order_detail", oid=oid))
|
||||
return render_template("order_form.html", boms=boms_all, factories=factories,
|
||||
o=o, initial_lines=[[b.id, q] for b, q in order_bom_lines(o)])
|
||||
|
||||
|
||||
@app.route("/order/<int:oid>/delete", methods=["POST"])
|
||||
@@ -1696,6 +1703,9 @@ def order_delete(oid):
|
||||
# 删除关联的「调入数量」计划行
|
||||
db.session.execute(
|
||||
db.delete(ProdOrderAlloc).where(ProdOrderAlloc.order_id == oid))
|
||||
# 删除关联的「多 BOM 清单」行(订单头删除时一并清除, 避免孤儿行)
|
||||
db.session.execute(
|
||||
db.delete(ProdOrderBom).where(ProdOrderBom.order_id == oid))
|
||||
# 级联删除该订单关联的领料出库流水(用途=生产领用, order_ref=订单号)
|
||||
n_out = db.session.execute(
|
||||
db.delete(Outbound).where(Outbound.order_ref == no)).rowcount
|
||||
@@ -1709,6 +1719,66 @@ def order_delete(oid):
|
||||
return redirect(url_for("orders"))
|
||||
|
||||
|
||||
def order_bom_lines(o):
|
||||
"""返回该订单的 BOM 行 [(bom, qty)], 按 BOM code 排序。
|
||||
|
||||
优先读 prod_order_bom 关联表(一订单多 BOM); 若该表为空(旧库未迁移或异常),
|
||||
回退到 ProdOrder.bom_id / qty 单值。
|
||||
"""
|
||||
rows = db.session.scalars(
|
||||
db.select(ProdOrderBom)
|
||||
.where(ProdOrderBom.order_id == o.id)
|
||||
.join(Bom, Bom.id == ProdOrderBom.bom_id)
|
||||
.order_by(Bom.code)).all()
|
||||
out = []
|
||||
for r in rows:
|
||||
b = db.session.get(Bom, r.bom_id)
|
||||
if b:
|
||||
out.append((b, r.qty))
|
||||
if not out and o.bom_id:
|
||||
b = db.session.get(Bom, o.bom_id)
|
||||
if b:
|
||||
out.append((b, o.qty or 0))
|
||||
return out
|
||||
|
||||
|
||||
def order_aggregated_materials(o):
|
||||
"""聚合该订单所有 BOM 的物料需求。
|
||||
|
||||
返回 [(material, need_qty)]; need_qty = Σ(该BOM单套用量 × 该BOM本订单生产数量)。
|
||||
同一物料出现在多个 BOM 中时, 需求数量累加合并(对应一个 ProdOrderAlloc 行)。
|
||||
"""
|
||||
agg = {}
|
||||
for b, qty in order_bom_lines(o):
|
||||
items = db.session.scalars(
|
||||
db.select(BomItem).where(BomItem.bom_id == b.id)).all()
|
||||
for it in items:
|
||||
agg[it.material_id] = agg.get(it.material_id, 0) + (it.qty_per_unit or 0) * qty
|
||||
return [(db.session.get(Material, mid), need) for mid, need in agg.items() if mid]
|
||||
|
||||
|
||||
def _parse_bom_pairs(form):
|
||||
"""从表单解析 (bom_id, qty) 列表: 仅保留已选 BOM 且数量 > 0 的行。
|
||||
|
||||
表单字段: bom_id[] / qty[] (数组, 逐行对应; 方括号为 HTML 原生多值命名)。
|
||||
"""
|
||||
bom_ids = form.getlist("bom_id[]")
|
||||
qtys = form.getlist("qty[]")
|
||||
pairs = []
|
||||
for bid, q in zip(bom_ids, qtys):
|
||||
bid = (bid or "").strip()
|
||||
if not bid or not bid.isdigit():
|
||||
continue
|
||||
try:
|
||||
qv = float((q or "").strip() or 0)
|
||||
except ValueError:
|
||||
qv = 0
|
||||
if qv <= 0:
|
||||
continue
|
||||
pairs.append((int(bid), qv))
|
||||
return pairs
|
||||
|
||||
|
||||
def _find_posted_flows(order_no, material_id):
|
||||
"""返回某生产订单某物料已入账调入流水(源厂出库 + 生产厂入库)。"""
|
||||
outs = db.session.scalars(db.select(Outbound).where(
|
||||
@@ -1824,10 +1894,10 @@ def order_alloc_save(oid):
|
||||
if not o:
|
||||
return redirect(url_for("orders"))
|
||||
op = g.user.display_name or g.user.username
|
||||
items = db.session.scalars(db.select(BomItem).where(BomItem.bom_id == o.bom_id)).all()
|
||||
items = order_aggregated_materials(o)
|
||||
edited = 0
|
||||
for it in items:
|
||||
raw = request.form.get(f"alloc_{it.material_id}", "").strip()
|
||||
for m, _need in items:
|
||||
raw = request.form.get(f"alloc_{m.id}", "").strip()
|
||||
if raw == "":
|
||||
continue
|
||||
try:
|
||||
@@ -1836,7 +1906,7 @@ def order_alloc_save(oid):
|
||||
continue
|
||||
if qty < 0:
|
||||
qty = 0
|
||||
st = _apply_alloc_change(o, it.material_id, qty, op, account_new=False)
|
||||
st = _apply_alloc_change(o, m.id, qty, op, account_new=False)
|
||||
if st in ("posted", "cancel"):
|
||||
edited += 1
|
||||
db.session.commit()
|
||||
@@ -1873,12 +1943,12 @@ def order_alloc_post(oid):
|
||||
flash("调入来源工厂不存在", "error")
|
||||
return redirect(url_for("order_detail", oid=oid))
|
||||
|
||||
items = db.session.scalars(db.select(BomItem).where(BomItem.bom_id == o.bom_id)).all()
|
||||
items = order_aggregated_materials(o)
|
||||
op = g.user.display_name or g.user.username
|
||||
n = 0
|
||||
shorts = []
|
||||
for it in items:
|
||||
raw = request.form.get(f"alloc_{it.material_id}", "").strip()
|
||||
for m, _need in items:
|
||||
raw = request.form.get(f"alloc_{m.id}", "").strip()
|
||||
qty = None
|
||||
if raw != "":
|
||||
try:
|
||||
@@ -1887,7 +1957,7 @@ def order_alloc_post(oid):
|
||||
qty = None
|
||||
a = db.session.scalar(db.select(ProdOrderAlloc).where(
|
||||
ProdOrderAlloc.order_id == oid,
|
||||
ProdOrderAlloc.material_id == it.material_id))
|
||||
ProdOrderAlloc.material_id == m.id))
|
||||
if qty is None:
|
||||
if a and a.posted:
|
||||
qty = a.alloc_qty # 已入账且无输入 = 维持, helper 判定 unchanged 跳过
|
||||
@@ -1895,12 +1965,11 @@ def order_alloc_post(oid):
|
||||
continue # 未入账且无输入 = 不处理
|
||||
if qty < 0:
|
||||
qty = 0
|
||||
st = _apply_alloc_change(o, it.material_id, qty, op,
|
||||
st = _apply_alloc_change(o, m.id, qty, op,
|
||||
from_id=from_id, account_new=True)
|
||||
if st == "posted":
|
||||
n += 1
|
||||
elif st == "short":
|
||||
m = db.session.get(Material, it.material_id)
|
||||
if a and a.posted:
|
||||
shorts.append(f"{m.part_number}(已入账调入修改, 源厂{ff.code}库存不足)")
|
||||
else:
|
||||
@@ -2529,6 +2598,19 @@ def seed_admin():
|
||||
db.session.add(User(username="admin", password=sha256("admin123"),
|
||||
role="admin", display_name="管理员"))
|
||||
db.session.commit()
|
||||
# ---- 一订单多 BOM: 为旧库(仅 bom_id 单值)的订单补齐 prod_order_bom 行(幂等) ----
|
||||
try:
|
||||
legacy = db.session.scalars(db.select(ProdOrder)).all()
|
||||
for o in legacy:
|
||||
exists = db.session.scalar(
|
||||
db.select(db.func.count(ProdOrderBom.id))
|
||||
.where(ProdOrderBom.order_id == o.id))
|
||||
if exists == 0 and o.bom_id:
|
||||
db.session.add(ProdOrderBom(
|
||||
order_id=o.id, bom_id=o.bom_id, qty=o.qty or 0))
|
||||
db.session.commit()
|
||||
except Exception:
|
||||
db.session.rollback()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
Reference in New Issue
Block a user