- 订单列表页「导出 Excel」(/orders/export): 导出全部生产订单(订单号/BOM/接单量/工厂/状态/经手人/下单时间/备注) - 订单详情页「导出 Excel」(/order/<id>/export): 导出派工单, 含「订单信息」+「领料明细」双 sheet - 订单详情页「打印」按钮: window.print() + @media print 样式(隐藏导航/按钮, 输入框转纯文本, 隐藏提示), 输出干净派工单
136 lines
7.4 KiB
HTML
136 lines
7.4 KiB
HTML
{% extends "base.html" %}
|
||
{% block title %}订单 {{ o.order_no }}{% endblock %}
|
||
{% block content %}
|
||
<style>
|
||
/* 打印(派工单): 仅保留订单信息与明细表, 隐藏导航/按钮, 输入框显示为纯文本 */
|
||
@media print {
|
||
header.topbar { display: none !important; }
|
||
.toolbar .btn, .toolbar a.btn, .toolbar button { display: none !important; }
|
||
.toolbar .badge { display: inline-block !important; }
|
||
.row-actions { display: none !important; }
|
||
.print-hide { display: none !important; }
|
||
input[name^="alloc_"] {
|
||
border: none !important; background: none !important; padding: 0 !important;
|
||
width: auto !important; -webkit-appearance: none; appearance: none;
|
||
font: inherit; color: inherit;
|
||
}
|
||
form { display: block; }
|
||
@page { margin: 12mm; }
|
||
body { background: #fff !important; }
|
||
}
|
||
</style>
|
||
<div class="toolbar">
|
||
<h3 style="margin:0">生产订单 {{ o.order_no }}</h3>
|
||
<span class="badge {{ 'ok' if o.status=='picked' else 'warn' }}">{{ '已领料' if o.status=='picked' else '待领料' }}</span>
|
||
<a class="btn" href="{{ url_for('order_edit', oid=o.id) }}">编辑订单</a>
|
||
<a class="btn" href="{{ url_for('order_export', oid=o.id) }}">导出 Excel</a>
|
||
<button class="btn" type="button" onclick="window.print()">打印</button>
|
||
</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>
|
||
{% 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>
|
||
<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) }}">
|
||
<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>
|
||
<tbody>
|
||
{% for r in reqs %}
|
||
<tr class="{{ 'row-danger' if r.short else ('row-warn' if r.have < (r.m.safety_stock or 0)) }}">
|
||
<td><a href="{{ url_for('material_detail', mid=r.m.id) }}" title="进入工厂物料">{{ r.m.part_number }}</a></td>
|
||
<td>{{ r.m.manufacturer or '-' }}</td>
|
||
<td class="num">{{ '%.2f'|format(r.have) }}</td>
|
||
<td class="num">{{ '%.2f'|format(r.need) }}</td>
|
||
<td class="num">{{ '%.2f'|format(r.gap) }}</td>
|
||
<td class="num">
|
||
{% if r.posted %}
|
||
<div style="margin-bottom:3px"><span class="badge ok" title="已生成入库流水, 可直接修改后保存/确认">已调入</span></div>
|
||
<input name="alloc_{{ r.m.id }}" type="text" inputmode="decimal"
|
||
value="{{ '%.2f'|format(r.alloc) }}" style="width:96px">
|
||
{% else %}
|
||
<input name="alloc_{{ r.m.id }}" type="text" inputmode="decimal"
|
||
value="{{ '%.2f'|format(r.alloc) if r.alloc else '' }}"
|
||
placeholder="{{ '%.2f'|format(r.need) }}" style="width:96px">
|
||
{% endif %}
|
||
</td>
|
||
<td><span class="badge {{ 'danger' if r.short else 'ok' }}">{{ '缺料' if r.short else '充足' }}</span></td>
|
||
<td class="num">{{ '%.2f'|format(r.gap + r.alloc) }}</td>
|
||
<td class="small">
|
||
{% if r.subs %}
|
||
{% for s in r.subs %}
|
||
<span class="{{ 'badge ok' if s.available else 'badge danger' }}">{{ s.pn }}·{{ s.stock }}{% if s.available %}✓{% else %}✗{% endif %}</span>
|
||
{% endfor %}
|
||
{% else %}<span class="muted">—</span>{% endif %}
|
||
</td>
|
||
</tr>
|
||
{% else %}
|
||
<tr><td colspan="8" class="muted">该 BOM 没有组成物料</td></tr>
|
||
{% endfor %}
|
||
</tbody>
|
||
</table>
|
||
|
||
<div class="row-actions" style="flex-wrap:wrap; align-items:flex-end">
|
||
<label style="display:flex; flex-direction:column; gap:4px; font-size:13px">
|
||
调入来源工厂
|
||
<select name="from_factory_id" style="padding:6px 8px; min-width:160px">
|
||
{% for f in factories %}
|
||
{% if f.id != (o.factory_id or 0) %}
|
||
<option value="{{ f.id }}" {{ 'selected' if f.code=='SELF' else '' }}>{{ f.code }}({{ f.name or '—' }})</option>
|
||
{% endif %}
|
||
{% endfor %}
|
||
</select>
|
||
</label>
|
||
<button class="primary" type="submit">保存调入数量</button>
|
||
<button class="secondary" type="submit"
|
||
formaction="{{ url_for('order_alloc_post', oid=o.id) }}"
|
||
onclick="return confirm('确认将待调入数量从上方所选来源工厂调拨到生产工厂? 总库存不变, 仅厂间转移。');">确认调入(厂间调拨)</button>
|
||
</div>
|
||
<p class="small muted print-hide">「确认调入」是一次厂间调拨:从来源工厂出库减、生产工厂入库加,系统总库存不变。</p>
|
||
</form>
|
||
|
||
<div class="card" style="margin:14px 0">
|
||
<b>调入总览:</b> 已调入 <b>{{ posted_count }}</b> 项,合计 <b>{{ '%.2f'|format(posted_total) }}</b> 件;
|
||
计划调入合计 <b>{{ '%.2f'|format(planned_total) }}</b> 件。
|
||
</div>
|
||
|
||
{% if o.status != 'picked' %}
|
||
<form method="post" action="{{ url_for('order_pick', oid=o.id) }}" onsubmit="return confirm('确认按 BOM 生成领料出库? 库存不足将记为负并在看板预警。');">
|
||
<div class="row-actions"><button class="primary">确认领料出库</button></div>
|
||
</form>
|
||
{% else %}
|
||
<p class="flash ok">该订单已领料出库。</p>
|
||
{% endif %}
|
||
|
||
<script>
|
||
(function(){
|
||
// 调入数量输入框:上下方向键在「上一行/下一行」的同列输入框之间移动焦点
|
||
// (左右方向键保持默认行为:在文本内移动光标)
|
||
function allocInputs(){
|
||
return Array.prototype.slice.call(document.querySelectorAll('input[name^="alloc_"]'));
|
||
}
|
||
document.addEventListener('keydown', function(e){
|
||
if(e.key !== 'ArrowUp' && e.key !== 'ArrowDown') return;
|
||
var t = e.target;
|
||
if(!t || t.tagName !== 'INPUT' || t.name.indexOf('alloc_') !== 0) return;
|
||
var inputs = allocInputs();
|
||
var idx = inputs.indexOf(t);
|
||
if(idx < 0) return;
|
||
var next;
|
||
if(e.key === 'ArrowUp'){ if(idx <= 0) return; next = inputs[idx - 1]; }
|
||
else { if(idx >= inputs.length - 1) return; next = inputs[idx + 1]; }
|
||
e.preventDefault();
|
||
next.focus();
|
||
try { var v = next.value; next.setSelectionRange(v.length, v.length); } catch(_){}
|
||
});
|
||
})();
|
||
</script>
|
||
{% endblock %}
|