Files
Inventory-Management-System/templates/prepare.html
T
star 5a6f4ea4df feat: 新增备料(采购下单)功能模块,含清空清单
- 独立采购备料清单:选 BOM + 输入套数一键生成计划(计划数量 = 单套用量 × 套数),可手工增删改、导出 Excel

- 备料与生产订单完全解耦,仅记录计划数量、不记账(不产生任何出入库流水)

- 新增「清空清单」功能(/prepare/clear,JS 二次确认 + 操作日志),按当前工厂范围清空

- 配套:PrepPlan 模型、缺口(计划-库存)实时计算、库存随工厂切换过滤
2026-10-19 15:09:20 +08:00

128 lines
6.2 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{% extends "base.html" %}
{% block title %}备料清单{% endblock %}
{% block content %}
<div class="toolbar">
<h3 style="margin:0">备料清单(采购下单用)</h3>
<span class="badge warn">不记账 · 按需用×套数预填</span>
<form method="post" action="{{ url_for('prepare_clear') }}" style="display:inline"
onsubmit="return confirm('确定清空当前范围内的全部备料计划?此操作不可撤销。');">
<button type="submit" class="btn danger">清空清单</button>
</form>
<a class="btn" href="{{ url_for('prepare_export') }}">导出 Excel</a>
</div>
<div class="card" style="margin-bottom:16px">
<p class="small muted" style="margin-top:0">
本页为<b>独立的采购备料清单</b>:选一个 <b>BOM</b> 并输入<b>套数</b>,即按
<b>「计划数量 = BOM单套用量 × 套数」</b> 一键生成(可再手工改);
也可在下方手工添加任意物料并填数量。当前范围:<b>{% if current_factory %}{{ current_factory.code }} · {{ current_factory.name }}{% else %}全部工厂(汇总){% endif %}</b>;
「实际库存」「缺口」随顶栏工厂切换实时计算。
</p>
<p class="small muted" style="margin-bottom:0">
说明:本页与 Excel 导出均<b>只读</b>,不产生任何入库 / 出库流水,仅用于向供应商下单采购。
<b>缺口 = 计划数量 − 实际库存</b>(&gt;0 表示还需采购,&lt;0 表示库存已盈余)。
</p>
</div>
{# 添加物料 #}
<form method="post" action="{{ url_for('prepare_add') }}" class="card" style="margin-bottom:16px;display:flex;gap:12px;align-items:flex-end;flex-wrap:wrap">
<div>
<label class="small muted">物料编号</label><br>
<input list="matlist" name="part_number" placeholder="输入或选择物料编号" style="width:240px" autocomplete="off" required>
<datalist id="matlist">
{% for m in materials %}<option value="{{ m.part_number }}">{{ m.manufacturer or '' }}</option>{% endfor %}
</datalist>
</div>
<div>
<label class="small muted">计划数量</label><br>
<input type="text" inputmode="decimal" name="qty" placeholder="可留空" style="width:120px;text-align:right">
</div>
<button type="submit" class="btn primary">添加物料</button>
<span class="muted small">手工录入单个物料(计划数量可留空,稍后填)</span>
</form>
{# 按 BOM 批量生成计划(计划数量 = 单套用量 × 套数) #}
<form method="post" action="{{ url_for('prepare_add_bom') }}" class="card" style="margin-bottom:16px;display:flex;gap:12px;align-items:flex-end;flex-wrap:wrap">
<div>
<label class="small muted">① 选择 BOM</label><br>
<select id="bomSel" name="bom_id" style="width:300px">
<option value="">-- 选择 BOM --</option>
{% for b in bom_opts %}<option value="{{ b.id }}">{{ b.label }}</option>{% endfor %}
</select>
</div>
<div>
<label class="small muted">② 套数</label><br>
<input type="text" inputmode="decimal" name="sets" value="1" style="width:120px;text-align:right">
</div>
<button type="submit" class="btn primary">生成计划(需用×套数)</button>
<span class="muted small">计划数量 = BOM单套用量 × 套数,生成后可在表中手工调整</span>
</form>
<form method="post" action="{{ url_for('prepare_save') }}">
<div class="toolbar" style="justify-content:flex-end">
<span class="muted small">修改计划数量后点此保存</span>
<button type="submit" class="btn primary">保存计划数量</button>
</div>
<table class="tbl">
<thead><tr>
<th>物料编号</th><th>型号/规格</th><th>单位</th>
<th>计划数量</th><th>实际库存</th><th>缺口</th><th>状态</th><th>操作</th>
</tr></thead>
<tbody>
{% for r in rows %}
<tr class="{{ 'row-danger' if r.diff > 0 else '' }}">
<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>{{ r.m.unit or '个' }}</td>
<td>
<input type="text" inputmode="decimal" name="plan_{{ r.id }}"
class="plan-input" style="width:90px;text-align:right"
value="{{ '%.2f'|format(r.plan) if r.plan else '' }}"
data-stock="{{ '%.2f'|format(r.stock) }}">
</td>
<td class="num">{{ '%.2f'|format(r.stock) }}</td>
<td class="num"><span class="diff" id="diff_{{ r.id }}">{{ '%.2f'|format(r.diff) }}</span></td>
<td><span class="badge {{ 'danger' if r.diff > 0 else 'ok' }}">{{ '需采购' if r.diff > 0 else ('盈余' if r.diff < 0 else '刚好') }}</span></td>
<td>
<form method="post" action="{{ url_for('prepare_delete', pid=r.id) }}" style="display:inline"
onsubmit="return confirm('确定删除该备料物料?');">
<button type="submit" class="btn danger small">删除</button>
</form>
</td>
</tr>
{% else %}
<tr><td colspan="8" class="muted">还没有添加任何备料物料,请选择 BOM 加入或手工录入。</td></tr>
{% endfor %}
</tbody>
</table>
</form>
{% endblock %}
{% block scripts %}
<script>
// 计划数量实时计算缺口 = 计划 - 实际库存
function recalcDiff(input) {
var row = input.closest('tr');
var diffEl = row.querySelector('.diff');
if (!diffEl) return;
var stock = parseFloat(input.getAttribute('data-stock')) || 0;
var val = input.value.trim();
if (val === '') { diffEl.textContent = ''; diffEl.className = 'diff'; return; }
var plan = parseFloat(val);
if (isNaN(plan)) { diffEl.textContent = '—'; diffEl.className = 'diff'; return; }
var d = Math.round((plan - stock) * 100) / 100;
diffEl.textContent = d.toFixed(2);
diffEl.className = 'diff ' + (d < 0 ? 'diff-neg' : (d > 0 ? 'diff-pos' : ''));
var badge = row.querySelector('.badge');
if (badge) {
badge.className = 'badge ' + (d > 0 ? 'danger' : 'ok');
badge.textContent = d > 0 ? '需采购' : (d < 0 ? '盈余' : '刚好');
}
}
document.querySelectorAll('.plan-input').forEach(function (inp) {
inp.addEventListener('input', function () { recalcDiff(this); });
recalcDiff(inp); // 初始(回填值)
});
</script>
{% endblock %}