feat: BOM批量导入型号/名称可编辑 + 生产订单查看按下单前库存快照 + 编辑冲销回下单状态
This commit is contained in:
+39
-33
@@ -1,58 +1,58 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}BOM 清单{% endblock %}
|
||||
{% block content %}
|
||||
<div class="toolbar">
|
||||
<h3 style="margin:0">BOM 清单 ({{ data|length }})</h3>
|
||||
<div>
|
||||
<a class="btn" href="{{ url_for('download_template', kind='bom') }}">⬇ 下载模板</a>
|
||||
<button class="primary" type="button" onclick="openBatch()">⊞ 批量导入窗口</button>
|
||||
<div class="toolbar" data-page-node-id="vZTw3kPadHZliSPinOK7Z1">
|
||||
<h3 style="margin:0" data-page-node-id="4wsrdtS5FO7LVSlfo7CSNG">BOM 清单 ({{ data|length }})</h3>
|
||||
<div data-page-node-id="wqIH8X3KYDPieHFmuHJk1U">
|
||||
<a class="btn" href="{{ url_for('download_template', kind='bom') }}" data-page-node-id="1uWZtIhFdzaXWtcVfxDuOg">⬇ 下载模板</a>
|
||||
<button class="primary" type="button" onclick="openBatch()" data-page-node-id="gzZTAfVl4A7uSSGZbnvUiB">⊞ 批量导入窗口</button>
|
||||
</div>
|
||||
</div>
|
||||
<table class="tbl">
|
||||
<thead><tr><th>BOM 型号</th><th>名称</th><th>组成项数</th><th>操作</th></tr></thead>
|
||||
<tbody>
|
||||
<table class="tbl" data-page-node-id="b0iXNI8PpWQuniQYav5O0I">
|
||||
<thead data-page-node-id="iYtImpRt9uTnLT1EuDXgCb"><tr data-page-node-id="9EW4NPakt3ZhDfzYevNQb6"><th data-page-node-id="sp7aqyu2wPcBXC14JySMsV">BOM 型号</th><th data-page-node-id="ON7POYFoxBWVPwDTJ5YFVh">名称</th><th data-page-node-id="H6pAPeWhIGXasJf7is1PlR">组成项数</th><th data-page-node-id="IQPsDz71IQrUtVnzjSvTqE">操作</th></tr></thead>
|
||||
<tbody data-page-node-id="GlcDs3bYGt9ktLuIMCnQ4S">
|
||||
{% for d in data %}
|
||||
<tr>
|
||||
<td><a href="{{ url_for('bom_detail', bid=d.b.id) }}">{{ d.b.code }}</a></td>
|
||||
<td>{{ d.b.name or '-' }}</td>
|
||||
<td class="num"><a href="{{ url_for('bom_detail', bid=d.b.id) }}">{{ d.count }}</a></td>
|
||||
<td>
|
||||
<a href="{{ url_for('bom_detail', bid=d.b.id) }}">查看</a>
|
||||
<a href="{{ url_for('bom_edit', bid=d.b.id) }}">编辑</a>
|
||||
<form method="post" action="{{ url_for('bom_delete', bid=d.b.id) }}" style="display:inline" onsubmit="return confirm('确认删除该 BOM?');">
|
||||
<button class="link">删除</button>
|
||||
<tr data-page-node-id="oIa73ogUxPXPW7hd6mSQqR">
|
||||
<td data-page-node-id="txkFINu0hqDcunfToMsIHJ"><a href="{{ url_for('bom_detail', bid=d.b.id) }}" data-page-node-id="wZa7pJWwB3UVob7w2rMPYi">{{ d.b.code }}</a></td>
|
||||
<td data-page-node-id="4ZgT9JXaM292L2DxjmPWdn">{{ d.b.name or '-' }}</td>
|
||||
<td class="num" data-page-node-id="81E2RDtrSvIDDtmXamJaKy"><a href="{{ url_for('bom_detail', bid=d.b.id) }}" data-page-node-id="xibV2oxiCVTyWQxHQ3GBvC">{{ d.count }}</a></td>
|
||||
<td data-page-node-id="z8hNVcUqGGY3LogEDYFO6M">
|
||||
<a href="{{ url_for('bom_detail', bid=d.b.id) }}" data-page-node-id="y99zemTyOQfPCzgAuGfoHX">查看</a>
|
||||
<a href="{{ url_for('bom_edit', bid=d.b.id) }}" data-page-node-id="Xlkt2Bi0z38orXYfmovRTF">编辑</a>
|
||||
<form method="post" action="{{ url_for('bom_delete', bid=d.b.id) }}" style="display:inline" onsubmit="return confirm('确认删除该 BOM?');" data-page-node-id="GRCffEreUmFcM3CoH1FEUC">
|
||||
<button class="link" data-page-node-id="fk4q7Q2tIJdpd368XDifLE">删除</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
{% else %}
|
||||
<tr><td colspan="4" class="muted">还没有 BOM, 点右上角新增</td></tr>
|
||||
<tr data-page-node-id="8dATczjWBQGQA7MAsGu9f8"><td colspan="4" class="muted" data-page-node-id="7Zbca69N7opSKetW07LkHr">还没有 BOM, 点右上角新增</td></tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<!-- 批量导入窗口(模态) -->
|
||||
<div class="modal-mask" id="batchMask" style="display:none">
|
||||
<div class="modal">
|
||||
<div class="modal-head">
|
||||
<span>批量导入 BOM · 多 BOM 清单</span>
|
||||
<button class="x" onclick="closeBatch()">✕</button>
|
||||
<div class="modal-mask" id="batchMask" style="display:none" data-page-node-id="na2dwI5Srtijy3wkeG1h5s">
|
||||
<div class="modal" data-page-node-id="tEI8MSJjVrQXENBtLlS1dA">
|
||||
<div class="modal-head" data-page-node-id="EHG4VZ8LF2jK6OQuZpZ1ij">
|
||||
<span data-page-node-id="xHsrz06nBQLPcxe4wbnUF5">批量导入 BOM · 多 BOM 清单</span>
|
||||
<button class="x" onclick="closeBatch()" data-page-node-id="aoDJmcOoqnCSk7ji1ol2al">✕</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p class="small muted">支持两种录入:① 直接上传 <b>Altium / 嘉立创 BOM</b>(每个 worksheet 即一个 BOM,<b>Part Number</b> 关联物料、<b>Quantity</b> 列即单套用量,下载模板即此格式);② 粘贴文本,每行一个 BOM,以 <b>#BOM <型号> <名称></b> 开头,其下为 <b>物料编号 单套用量</b>,空行分隔。物料不存在时导入会自动新建主数据。</p>
|
||||
<div class="modal-body" data-page-node-id="v1ZVsZhJV9kTmwR8H3UTll">
|
||||
<p class="small muted" data-page-node-id="uBFCxuwHOoYf4HDB7GHjqB">支持两种录入:① 直接上传 <b data-page-node-id="qPxNhhrFxrTrmWD4tIcNEf">Altium / 嘉立创 BOM</b>(每个 worksheet 即一个 BOM,<b data-page-node-id="6TXeirBIr5BJkAkpI9CMod">Part Number</b> 关联物料、<b data-page-node-id="NYIE9aPWd3xD4Tr0hLOqI3">Quantity</b> 列即单套用量,下载模板即此格式);② 粘贴文本,每行一个 BOM,以 <b data-page-node-id="jy8nTX15GDqKSV6wYSm78A">#BOM <型号> <名称></b> 开头,其下为 <b data-page-node-id="Lgr6U8MBrInBJO29ZLmnN3">物料编号 单套用量</b>,空行分隔。物料不存在时导入会自动新建主数据。</p>
|
||||
<textarea id="batchText" rows="11" placeholder="#BOM M4021 示例产品A
|
||||
P0001 2
|
||||
P0002 0.5
|
||||
|
||||
#BOM M4022 示例产品B
|
||||
P0003 4"></textarea>
|
||||
<div class="row-actions">
|
||||
<button type="button" onclick="parsePaste()">解析预览</button>
|
||||
<label class="btn" style="cursor:pointer">📁 上传文件
|
||||
<input type="file" id="batchFile" accept=".xlsx,.csv" style="display:none" onchange="parseFile()">
|
||||
<div class="row-actions" data-page-node-id="Un0xhQE1iuii1GXRoLOGEy">
|
||||
<button type="button" onclick="parsePaste()" data-page-node-id="2nie414a2o4ULz4FNMbFVC">解析预览</button>
|
||||
<label class="btn" style="cursor:pointer" data-page-node-id="s3iZeq1kL8Etsahvv2hGKO">📁 上传文件
|
||||
<input type="file" id="batchFile" accept=".xlsx,.csv" style="display:none" onchange="parseFile()" data-page-node-id="sj1omvbTTh7dteUj3QePnl">
|
||||
</label>
|
||||
<button type="button" class="primary" id="commitBtn" onclick="commitBatch()" disabled>确认导入</button>
|
||||
<button type="button" class="primary" id="commitBtn" onclick="commitBatch()" disabled data-page-node-id="LV1nhOjsxrIhBszlCoxA2s">确认导入</button>
|
||||
</div>
|
||||
<div id="batchResult" style="margin-top:12px"></div>
|
||||
<div id="batchResult" style="margin-top:12px" data-page-node-id="zdtCCsWu38gEMMgkruNxOI"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -83,14 +83,20 @@ function postPreview(payload){
|
||||
body:JSON.stringify(payload)
|
||||
}).then(r=>r.json()).then(renderPreview).catch(e=>alert('解析失败: '+e));
|
||||
}
|
||||
function esc(s){
|
||||
return String(s==null?'':s).replace(/[&<>"]/g, c=>({'&':'&','<':'<','>':'>','"':'"'}[c]));
|
||||
}
|
||||
function renderPreview(data){
|
||||
lastBoms = data.boms || [];
|
||||
const valid = data.valid, invalid = data.invalid;
|
||||
let html = `<div class="flash ${invalid? 'error':'ok'}">共 ${lastBoms.length} 个 BOM · 可导入 <b>${valid}</b> 个 · 异常 <b>${invalid}</b> 个</div>`;
|
||||
lastBoms.forEach(b=>{
|
||||
lastBoms.forEach((b, i)=>{
|
||||
const btag = b.ok? '<span class="badge ok">OK</span>' : `<span class="badge danger">${b.error||'错误'}</span>`;
|
||||
html += `<div class="bom-card ${b.ok?'':'card-danger'}">`;
|
||||
html += `<div class="bom-head"><b>${b.code||''}</b> ${b.name||''} ${btag} <span class="muted small">成功项 ${b.valid_items}/${b.items.length}</span></div>`;
|
||||
html += `<div class="bom-head">`;
|
||||
html += `<label class="bh-lbl">型号</label><input class="bom-code-inp" value="${esc(b.code)}" oninput="lastBoms[${i}].code=this.value" title="可编辑 BOM 型号">`;
|
||||
html += `<label class="bh-lbl">名称</label><input class="bom-name-inp" value="${esc(b.name)}" oninput="lastBoms[${i}].name=this.value" title="可编辑 BOM 名称(全部显示,可修改)">`;
|
||||
html += `${btag} <span class="muted small">成功项 ${b.valid_items}/${b.items.length}</span></div>`;
|
||||
html += '<table class="tbl"><thead><tr><th>物料编号</th><th>型号/规格</th><th>单套用量</th><th>校验</th></tr></thead><tbody>';
|
||||
b.items.forEach(it=>{
|
||||
const cls = it.ok? '' : 'row-danger';
|
||||
|
||||
@@ -7,17 +7,20 @@
|
||||
<a class="btn" href="{{ url_for('order_edit', oid=o.id) }}">编辑订单</a>
|
||||
</div>
|
||||
<div class="card" style="margin-bottom:16px">
|
||||
<p>BOM: <b>{{ bom.code if bom else '-' }}</b> ({{ bom.name if bom else '' }}) | 接单量: <b>{{ '%.2f'|format(o.qty) }}</b> | 生产工厂: <b>{{ factory.code if factory else '未指定(按汇总库存)' }}</b> | 经手人: {{ o.operator or '-' }}</p>
|
||||
<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>{{ factory.code if factory else '未指定(按汇总库存)' }}</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>
|
||||
|
||||
<h3>领料需求与调入 (单套用量 × 接单量){% if factory %} | 按 {{ factory.code }} 工厂库存核算{% endif %}</h3>
|
||||
<p class="small muted" style="margin-top:-6px">库存口径:下方「下单前库存」为 <b>该订单下单那一刻</b>的库存快照(已自动剔除本单后续调入/领料及下单之后的出入库),用于还原下单时的可供料情况,与实时库存可能不同。{% if as_of %}快照时点:{{ as_of.strftime('%Y-%m-%d %H:%M') }}。{% endif %}</p>
|
||||
|
||||
<form method="post" action="{{ url_for('order_alloc_save', oid=o.id) }}">
|
||||
<table class="tbl">
|
||||
<thead><tr>
|
||||
<th>物料编号</th><th>型号</th><th>需求数量</th>
|
||||
<th>调入数量</th><th>当前库存</th><th>缺口</th><th>状态</th><th>可替代料</th>
|
||||
<th>调入数量</th><th>下单前库存</th><th>缺口</th><th>状态</th><th>可替代料</th>
|
||||
</tr></thead>
|
||||
<tbody>
|
||||
{% for r in reqs %}
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
{% block title %}{{ '编辑生产订单' if o else '新建生产订单' }}{% endblock %}
|
||||
{% block content %}
|
||||
<form method="post" class="card form-card" style="max-width:520px"
|
||||
action="{{ url_for('order_edit', oid=o.id) if o else url_for('order_new') }}">
|
||||
action="{{ url_for('order_edit', oid=o.id) if o else url_for('order_new') }}"
|
||||
{% if o %}onsubmit="return confirm('保存将把该订单冲销回「刚下单」状态(撤销已调入/领料流水、清空调入计划),确认继续?');"{% 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">
|
||||
|
||||
Reference in New Issue
Block a user