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:
@@ -28,12 +28,13 @@
|
||||
</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>{% 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>
|
||||
<p><b>BOM 清单:</b>{% for b, q in lines %}<span style="display:inline-block;margin-right:14px">{{ b.code }}{% if b.name %} · {{ b.name }}{% endif %} × <b>{{ '%.2f'|format(q) }}</b></span>{% else %}-{% endfor %}</p>
|
||||
<p>总接单量(合计): <b>{{ '%.2f'|format(total_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 print-hide" style="margin-bottom:0">提示:点击「编辑订单」保存后,该订单会冲销已产生的调入/领料流水并清空调入计划,自动回到「刚下单」状态(待领料、未调拨),可重新规划。</p>
|
||||
</div>
|
||||
|
||||
<h3>领料需求与调入 (单套用量 × 接单量){% if factory %} | 按下单前库存核算{% endif %}</h3>
|
||||
<h3>领料需求与调入 (按各 BOM 单套用量 × 各自生产数量 汇总){% if factory %} | 按下单前库存核算{% endif %}</h3>
|
||||
<p class="small muted" style="margin-top:-6px">库存口径:下方「下单前库存」为 <b>订单下单那一刻、所属工厂的库存快照</b>(按下单时间截断,已自动排除该订单自身后续的调入/领料及下单后发生的其他出入库),用于还原下单时的可供料情况。{% if not factory %}本单未指定生产工厂,按下单前全部工厂汇总库存展示。{% endif %}</p>
|
||||
|
||||
<form method="post" action="{{ url_for('order_alloc_save', oid=o.id) }}">
|
||||
|
||||
+42
-12
@@ -1,21 +1,18 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}{{ '编辑生产订单' if o else '新建生产订单' }}{% endblock %}
|
||||
{% block content %}
|
||||
<form method="post" class="card form-card" style="max-width:520px"
|
||||
<form method="post" class="card form-card" style="max-width:720px"
|
||||
action="{{ url_for('order_edit', oid=o.id) if o else url_for('order_new') }}"
|
||||
{% if o %}onsubmit="return confirm('确认保存修改?已录入的调入数量与已入账流水将保持不变。');"{% endif %}>
|
||||
{% if o %}onsubmit="return confirm('确认保存修改?已录入的调入数量与已入账流水将保持不变(被移除 BOM 中已入账的物料仍保留其流水)。');"{% endif %}>
|
||||
<h3>{{ '编辑生产订单' if o else '新建生产订单' }}</h3>
|
||||
<label>订单号 {% if o %}(单号不可重复){% else %}(留空自动生成){% endif %}</label>
|
||||
<input name="order_no" value="{{ o.order_no if o else '' }}" placeholder="如 WO20260911xxx">
|
||||
<label>BOM 型号 *</label>
|
||||
<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 }}{% if b.name %} · {{ b.name }}{% endif %}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<label>接单量 (台/套) *</label>
|
||||
<input name="qty" type="number" step="0.01" min="0.01" required value="{{ '%.2f'|format(o.qty) if o else '' }}">
|
||||
|
||||
<label>BOM 与生产数量 *</label>
|
||||
<p class="small muted" style="margin:0 0 6px">可在同一订单中加入多个 BOM,每个 BOM 独立填写生产数量;点击「+ 添加 BOM」增加一行,不需要的行可删除。</p>
|
||||
<div id="bomRows" class="bom-rows"></div>
|
||||
<button type="button" class="btn" style="margin:2px 0 12px" onclick="addBomRow()">+ 添加 BOM</button>
|
||||
|
||||
<label>生产工厂</label>
|
||||
<select name="factory_id">
|
||||
<option value="">-- 不指定(汇总库存校验)--</option>
|
||||
@@ -29,6 +26,39 @@
|
||||
<button class="primary">{{ '保存修改' if o else '生成订单' }}</button>
|
||||
<a class="btn" href="{{ url_for('orders') }}">取消</a>
|
||||
</div>
|
||||
<p class="small muted">生成后可在订单详情页按 BOM 展开领料需求, 并手动录入每个物料的「调入数量」。</p>
|
||||
<p class="small muted">生成后可在订单详情页按各 BOM 展开领料需求, 并手动录入每个物料的「调入数量」。</p>
|
||||
</form>
|
||||
|
||||
{# 隐藏模板: 供 JS 克隆(含全部 BOM 选项) #}
|
||||
<template id="bomTpl">
|
||||
<div class="bom-row" style="display:flex; gap:10px; align-items:center; margin-bottom:6px">
|
||||
<select name="bom_id[]" required>
|
||||
<option value="">-- 选择 BOM --</option>
|
||||
{% for b in boms %}<option value="{{ b.id }}">{{ b.code }}{% if b.name %} · {{ b.name }}{% endif %}</option>{% endfor %}
|
||||
</select>
|
||||
<input name="qty[]" type="number" step="0.01" min="0.01" required placeholder="生产数量" style="width:130px">
|
||||
<button type="button" class="link-danger" onclick="this.closest('.bom-row').remove();checkRows()">删除</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
function addBomRow(sel, qty){
|
||||
var tpl = document.getElementById('bomTpl');
|
||||
var row = tpl.content.firstElementChild.cloneNode(true);
|
||||
if (sel) row.querySelector('select').value = String(sel);
|
||||
if (qty != null && qty !== undefined) row.querySelector('input').value = qty;
|
||||
document.getElementById('bomRows').appendChild(row);
|
||||
checkRows();
|
||||
}
|
||||
function checkRows(){
|
||||
var rows = document.querySelectorAll('#bomRows .bom-row');
|
||||
rows.forEach(function(r){
|
||||
var btn = r.querySelector('button');
|
||||
if (btn) btn.style.display = rows.length > 1 ? '' : 'none';
|
||||
});
|
||||
}
|
||||
var init = {{ initial_lines|tojson }};
|
||||
if (init && init.length){ init.forEach(function(x){ addBomRow(x[0], x[1]); }); }
|
||||
else { addBomRow(); }
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
@@ -7,13 +7,13 @@
|
||||
<a class="btn" href="{{ url_for('orders_export') }}">导出 Excel</a>
|
||||
</div>
|
||||
<table class="tbl">
|
||||
<thead><tr><th>订单号</th><th>BOM</th><th>接单量</th><th>生产工厂</th><th>状态</th><th>经手人</th><th>操作</th></tr></thead>
|
||||
<thead><tr><th>订单号</th><th>BOM 清单(型号 × 数量)</th><th>总接单量</th><th>生产工厂</th><th>状态</th><th>经手人</th><th>操作</th></tr></thead>
|
||||
<tbody>
|
||||
{% for d in data %}
|
||||
<tr>
|
||||
<td>{{ d.o.order_no }}</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>{% for b, q in d.lines %}<div>{{ b.code }}{% if b.name %} · {{ b.name }}{% endif %} × {{ '%.2f'|format(q) }}</div>{% else %}-{% endfor %}</td>
|
||||
<td class="num">{{ '%.2f'|format(d.total) }}</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>
|
||||
<td>{{ d.o.operator or '-' }}</td>
|
||||
|
||||
Reference in New Issue
Block a user