feat: BOM型号可编辑 + 生产订单BOM去括号 + 物料明细各工厂实时库存
- BOM编辑页「型号」输入框取消 readonly,可修改;bom_edit 增加非空与唯一校验(重复提示「BOM 型号已存在」、空值提示「BOM 型号必填」),改名不影响关联生产订单与流水 - 生产订单列表/详情/表单中 BOM 显示由 CODE (NAME) 改为 CODE · NAME,去掉括号 - 物料明细页新增「各工厂实时库存」表(工厂编号/名称/实时库存),显示每个工厂的实时聚合库存(入−出),不受顶栏工厂过滤影响
This commit is contained in:
@@ -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")
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<form method="post" class="card form-card" style="max-width:640px">
|
||||
<h3>{{ '编辑 BOM' if b else '新增 BOM' }}</h3>
|
||||
<label>BOM 型号 *</label>
|
||||
<input name="code" value="{{ b.code if b else '' }}" required {% if b %}readonly{% endif %}>
|
||||
<input name="code" value="{{ form_code if form_code is defined else (b.code if b else '') }}" required>
|
||||
<label>名称</label>
|
||||
<input name="name" value="{{ b.name if b else '' }}">
|
||||
<label>备注</label>
|
||||
|
||||
@@ -27,6 +27,22 @@
|
||||
<div class="stat"><div class="num">{{ '%.2f'|format(out_total) }}</div><div class="lbl">消耗总数量</div></div>
|
||||
</div>
|
||||
|
||||
<h3>各工厂实时库存(实时聚合)</h3>
|
||||
<table class="tbl">
|
||||
<thead><tr><th>工厂编号</th><th>工厂名称</th><th>实时库存</th></tr></thead>
|
||||
<tbody>
|
||||
{% for pf in per_factory %}
|
||||
<tr>
|
||||
<td>{{ pf.f.code }}</td>
|
||||
<td>{{ pf.f.name or '-' }}</td>
|
||||
<td class="num">{{ '%.2f'|format(pf.stock) }}</td>
|
||||
</tr>
|
||||
{% else %}
|
||||
<tr><td colspan="3" class="muted">暂无工厂</td></tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h3>入库流水(每次入库时间 / 数量)</h3>
|
||||
<table class="tbl">
|
||||
<thead><tr><th>入库日期</th><th>工厂</th><th>送货日期</th><th>录入时间</th><th>数量</th><th>批次号</th><th>来源 / 供应商</th><th>经手人</th><th>备注</th></tr></thead>
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
</div>
|
||||
<div class="card" style="margin-bottom:16px">
|
||||
<p class="small muted" style="margin-top:0"><b>下单信息(按下单时记录)</b></p>
|
||||
<p>BOM: <b>{{ bom.code if bom else '-' }}</b> ({{ bom.name if bom else '' }}) | 接单量: <b>{{ '%.2f'|format(o.qty) }}</b> | 生产工厂: <b>{% if factory %}{{ factory.code }}{% if factory.name %} · {{ factory.name }}{% endif %}{% else %}未指定(按汇总库存){% endif %}</b> | 经手人: {{ o.operator or '-' }}{% if o.created_at %} | 下单时间: {{ o.created_at.strftime('%Y-%m-%d %H:%M') }}{% endif %}</p>
|
||||
<p>BOM: <b>{{ bom.code if bom else '-' }}</b>{% if bom and bom.name %} · {{ bom.name }}{% endif %} | 接单量: <b>{{ '%.2f'|format(o.qty) }}</b> | 生产工厂: <b>{% if factory %}{{ factory.code }}{% if factory.name %} · {{ factory.name }}{% endif %}{% else %}未指定(按汇总库存){% endif %}</b> | 经手人: {{ o.operator or '-' }}{% if o.created_at %} | 下单时间: {{ o.created_at.strftime('%Y-%m-%d %H:%M') }}{% endif %}</p>
|
||||
{% if o.remark %}<p class="small muted">备注: {{ o.remark }}</p>{% endif %}
|
||||
<p class="small muted" style="margin-bottom:0">提示:点击「编辑订单」保存后,该订单会冲销已产生的调入/领料流水并清空调入计划,自动回到「刚下单」状态(待领料、未调拨),可重新规划。</p>
|
||||
</div>
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
<select name="bom_id" required>
|
||||
<option value="">-- 选择 BOM --</option>
|
||||
{% for b in boms %}
|
||||
<option value="{{ b.id }}" {% if o and o.bom_id == b.id %}selected{% endif %}>{{ b.code }} ({{ b.name or '-' }})</option>
|
||||
<option value="{{ b.id }}" {% if o and o.bom_id == b.id %}selected{% endif %}>{{ b.code }}{% if b.name %} · {{ b.name }}{% endif %}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<label>接单量 (台/套) *</label>
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
{% for d in data %}
|
||||
<tr>
|
||||
<td>{{ d.o.order_no }}</td>
|
||||
<td>{{ d.bom.code if d.bom else '-' }} ({{ d.bom.name if d.bom else '' }})</td>
|
||||
<td>{{ d.bom.code if d.bom else '-' }}{% if d.bom and d.bom.name %} · {{ d.bom.name }}{% endif %}</td>
|
||||
<td class="num">{{ '%.2f'|format(d.o.qty) }}</td>
|
||||
<td>{% if d.factory %}{{ d.factory.code }}{% if d.factory.name %} · {{ d.factory.name }}{% endif %}{% else %}未指定{% endif %}</td>
|
||||
<td><span class="badge {{ 'ok' if d.o.status=='picked' else 'warn' }}">{{ '已领料' if d.o.status=='picked' else '待领料' }}</span></td>
|
||||
|
||||
Reference in New Issue
Block a user