feat: 多工厂/厂间调拨/替代料/流水台账折叠 + 重写 README
- 多工厂(factory 表) + 顶栏工厂下拉过滤, 库存按厂聚合 - 厂间调拨(transfer): 源厂减+目标厂加, 总库存不变, 双向流水对账 - 替代料关系(substitute): 出库/BOM领料/生产领料缺料时提示可替代料 - 流水台账(ledger): 入出按日期合并折叠, 按日期导出同一 Excel - 生产订单增强: 编辑/删除(级联清关联流水)/调入数量/确认调入=厂间调拨 - 入库新增送货日期字段; 出库物料编码扫码定位 - 重写 README 补全全部模块; .gitignore 排除 dist_pkg/build_pkg/*.db - 停止跟踪 instance/app.db(运行时自动生成)
This commit is contained in:
@@ -9,16 +9,29 @@
|
||||
<body>
|
||||
<header class="topbar">
|
||||
<div class="brand">📦 物料入库系统 <span class="ver">M1+M2+M3</span></div>
|
||||
<form class="factory-switch" method="post" action="{{ url_for('set_factory') }}">
|
||||
<label for="facSel">工厂</label>
|
||||
<select id="facSel" name="factory_id" onchange="this.form.submit()">
|
||||
<option value="all">全部工厂(汇总)</option>
|
||||
{% for f in factories %}
|
||||
<option value="{{ f.id }}" {% if current_factory_id == f.id %}selected{% endif %}>{{ f.code }} · {{ f.name }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</form>
|
||||
<nav>
|
||||
<a href="{{ url_for('dashboard') }}">库存看板</a>
|
||||
<a href="{{ url_for('materials') }}">物料</a>
|
||||
<a href="{{ url_for('inbound') }}">入库</a>
|
||||
<a href="{{ url_for('outbound') }}">出库</a>
|
||||
<a href="{{ url_for('ledger') }}">流水</a>
|
||||
{% if g.user and g.user.role in ('admin','manager') %}
|
||||
<a href="{{ url_for('boms') }}">BOM</a>
|
||||
<a href="{{ url_for('orders') }}">生产订单</a>
|
||||
<a href="{{ url_for('report') }}">报表</a>
|
||||
<a href="{{ url_for('logs') }}">日志</a>
|
||||
<a href="{{ url_for('factories') }}">工厂</a>
|
||||
<a href="{{ url_for('transfers') }}">调拨</a>
|
||||
<a href="{{ url_for('substitutes') }}">替代料</a>
|
||||
{% endif %}
|
||||
{% if g.user and g.user.role == 'admin' %}
|
||||
<a href="{{ url_for('users') }}">用户</a>
|
||||
|
||||
@@ -7,6 +7,10 @@
|
||||
<div class="stat danger"><div class="num">{{ negative }}</div><div class="lbl">库存为负(缺料)</div></div>
|
||||
</div>
|
||||
|
||||
<div class="scope-banner">
|
||||
{% if current_factory %}当前范围:<b>{{ current_factory.code }} · {{ current_factory.name }}</b> · 仅统计该工厂库存{% else %}当前范围:<b>全部工厂(汇总)</b> · 顶栏可切换具体工厂{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="tabs">
|
||||
<button class="tab active" type="button" onclick="showTab('mat', this)">📦 库存预警</button>
|
||||
{% if can_bom %}<button class="tab" type="button" onclick="showTab('bom', this)">🔧 BOM 物料齐套</button>{% endif %}
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}工厂 / 代工厂管理{% endblock %}
|
||||
{% block content %}
|
||||
|
||||
<div class="toolbar">
|
||||
<h3 style="margin:0">工厂 / 代工厂管理</h3>
|
||||
</div>
|
||||
|
||||
<div class="card" style="margin-bottom:16px">
|
||||
<form method="post" class="form-card" style="display:flex;flex-wrap:wrap;gap:12px;align-items:flex-end">
|
||||
<div>
|
||||
<label>工厂编码 *</label>
|
||||
<input name="code" placeholder="如 SUB01" required style="text-transform:uppercase">
|
||||
</div>
|
||||
<div>
|
||||
<label>工厂名称</label>
|
||||
<input name="name" placeholder="如 东莞代工厂A">
|
||||
</div>
|
||||
<div>
|
||||
<label>类型</label>
|
||||
<select name="type">
|
||||
<option value="self">自厂</option>
|
||||
<option value="subcontract">代工厂</option>
|
||||
</select>
|
||||
</div>
|
||||
<button class="primary" type="submit">+ 添加工厂</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<table class="tbl">
|
||||
<thead>
|
||||
<tr><th>编码</th><th>名称</th><th>类型</th><th>状态</th><th>操作</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for f in fs %}
|
||||
<tr>
|
||||
<td>{{ f.code }}</td>
|
||||
<td>{{ f.name or '-' }}</td>
|
||||
<td>{% if f.type == 'subcontract' %}代工厂{% else %}自厂{% endif %}</td>
|
||||
<td>
|
||||
{% if f.enabled %}<span class="badge ok">启用</span>
|
||||
{% else %}<span class="badge warn">停用</span>{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
<a class="btn" href="{{ url_for('factory_workbench', fid=f.id) }}">进入</a>
|
||||
<form method="post" action="{{ url_for('factory_toggle', fid=f.id) }}" style="display:inline">
|
||||
<button class="btn" type="submit">{% if f.enabled %}停用{% else %}启用{% endif %}</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
{% else %}
|
||||
<tr><td colspan="5" class="muted">暂无工厂, 请先添加</td></tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<p class="small muted" style="margin-top:14px">
|
||||
说明:每笔入库 / 出库都会归属到一个工厂。顶栏"工厂"下拉可切换查看范围(选"全部工厂"为各厂汇总)。
|
||||
停用后的工厂不会出现在下拉选项中,但其历史流水仍保留。
|
||||
</p>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,104 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}工厂工作台 · {{ f.code }}{% endblock %}
|
||||
{% block content %}
|
||||
|
||||
<div class="toolbar">
|
||||
<h3 style="margin:0">工厂工作台:{{ f.code }} · {{ f.name or '' }}
|
||||
<span class="badge {{ 'ok' if f.enabled else 'warn' }}">{{ '启用' if f.enabled else '停用' }}</span>
|
||||
</h3>
|
||||
<a class="btn" href="{{ url_for('factories') }}">← 返回工厂列表</a>
|
||||
</div>
|
||||
|
||||
<div class="tabs">
|
||||
<button class="tab active" onclick="switchTab('kanban')">📊 物料看板</button>
|
||||
<button class="tab" onclick="switchTab('bom')">🧩 BOM 齐套</button>
|
||||
<button class="tab" onclick="switchTab('import')">⊞ 批量导入</button>
|
||||
</div>
|
||||
|
||||
<!-- 物料看板 -->
|
||||
<div id="tab-kanban" class="tab-pane">
|
||||
<div class="stat-row" style="margin:12px 0">
|
||||
<div class="stat"><div class="num">{{ rows|length }}</div><div class="lbl">有库存物料</div></div>
|
||||
<div class="stat warn"><div class="num">{{ low }}</div><div class="lbl">低于安全库存</div></div>
|
||||
<div class="stat danger"><div class="num">{{ negative }}</div><div class="lbl">库存为负</div></div>
|
||||
</div>
|
||||
<table class="tbl">
|
||||
<thead><tr><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.status=='danger' else ('row-warn' if r.status=='warn' else '') }}">
|
||||
<td>{{ r.m.part_number }}</td>
|
||||
<td>{{ r.m.manufacturer or '-' }}</td>
|
||||
<td>{{ r.m.unit or '-' }}</td>
|
||||
<td class="num">{{ r.m.safety_stock or 0 }}</td>
|
||||
<td class="num">{{ '%.2f'|format(r.stock) }}</td>
|
||||
<td><span class="badge {{ 'danger' if r.status=='danger' else ('warn' if r.status=='warn' else 'ok') }}">{{ '负库存' if r.status=='danger' else ('低于安全库存' if r.status=='warn' else '正常') }}</span></td>
|
||||
</tr>
|
||||
{% else %}
|
||||
<tr><td colspan="6" class="muted">该厂暂无库存数据</td></tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- BOM 齐套 -->
|
||||
<div id="tab-bom" class="tab-pane" style="display:none">
|
||||
{% for bd in bom_data %}
|
||||
<div class="card" style="margin-bottom:16px">
|
||||
<h3 style="margin-top:0">{{ bd.b.code }} ({{ bd.b.name or '-' }})
|
||||
<span class="small muted">· 单套用量 × 该厂库存</span></h3>
|
||||
{% if bd.comps %}
|
||||
<table class="tbl">
|
||||
<thead><tr><th>物料编号</th><th>型号/规格</th><th>单套用量</th><th>该厂库存</th><th>状态</th></tr></thead>
|
||||
<tbody>
|
||||
{% for c in bd.comps %}
|
||||
<tr class="{{ 'row-danger' if c.status=='danger' else ('row-warn' if c.status=='warn' else '') }}">
|
||||
<td>{{ c.m.part_number }}</td>
|
||||
<td>{{ c.m.manufacturer or '-' }}</td>
|
||||
<td class="num">{{ '%.3f'|format(c.qty) }}</td>
|
||||
<td class="num">{{ '%.2f'|format(c.stock) }}</td>
|
||||
<td><span class="badge {{ 'danger' if c.status=='danger' else ('warn' if c.status=='warn' else 'ok') }}">{{ '负库存' if c.status=='danger' else ('低于安全库存' if c.status=='warn' else '正常') }}</span></td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% else %}
|
||||
<p class="small muted">该 BOM 没有组成物料</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% else %}
|
||||
<p class="muted">暂无 BOM</p>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
<!-- 批量导入 -->
|
||||
<div id="tab-import" class="tab-pane" style="display:none">
|
||||
<div class="card" style="margin-bottom:16px">
|
||||
<h3 style="margin-top:0">导入入库流水到 {{ f.code }}</h3>
|
||||
<p class="small muted">上传 Excel/CSV(列:物料编号 / 数量 / 批次 / 日期 / 备注),将生成归属 {{ f.code }} 的入库流水。</p>
|
||||
<form method="post" action="{{ url_for('factory_import_inbound', fid=f.id) }}" enctype="multipart/form-data" style="display:flex;gap:10px;align-items:center;flex-wrap:wrap">
|
||||
<input type="file" name="file" accept=".xlsx,.csv" required>
|
||||
<button class="primary" type="submit">上传并导入入库</button>
|
||||
<a class="btn" href="{{ url_for('download_template', kind='transactions') }}">⬇ 下载模板</a>
|
||||
</form>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h3 style="margin-top:0">导入物料主数据(期初库存归属 {{ f.code }})</h3>
|
||||
<p class="small muted">上传物料 Excel(列:物料编号 / 型号 / 单位 / 安全库存 / 期初库存),期初库存将写入 {{ f.code }}。</p>
|
||||
<form method="post" action="{{ url_for('factory_import_materials', fid=f.id) }}" enctype="multipart/form-data" style="display:flex;gap:10px;align-items:center;flex-wrap:wrap">
|
||||
<input type="file" name="file" accept=".xlsx,.csv" required>
|
||||
<button class="primary" type="submit">上传并导入物料</button>
|
||||
<a class="btn" href="{{ url_for('download_template', kind='materials') }}">⬇ 下载模板</a>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function switchTab(name){
|
||||
document.querySelectorAll('.tab').forEach(b=>b.classList.remove('active'));
|
||||
document.querySelectorAll('.tab-pane').forEach(p=>p.style.display='none');
|
||||
event.target.classList.add('active');
|
||||
document.getElementById('tab-'+name).style.display='block';
|
||||
}
|
||||
</script>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,85 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}流水台账{% endblock %}
|
||||
{% block content %}
|
||||
<div class="toolbar">
|
||||
<h3 style="margin:0">流水台账(出库 + 入库 按日期合并)</h3>
|
||||
<span class="badge ok">总入库 {{ '%.2f'|format(total_in) }}</span>
|
||||
<span class="badge danger">总出库 {{ '%.2f'|format(total_out) }}</span>
|
||||
</div>
|
||||
<p class="muted" style="margin:6px 0 0">选择日期区间后点「导出 Excel」,系统会把该区间内的<b>出库与入库合并到同一个文件</b>(文件名自动带上日期)。同一天的出库与入库已按日期折叠在一起,点日期可展开当天的明细。</p>
|
||||
|
||||
<style>
|
||||
details.date-group{margin:14px 0;border:1px solid #e6e9ef;border-radius:8px;overflow:hidden;background:#fff}
|
||||
details.date-group>summary{list-style:none;cursor:pointer;padding:11px 14px;background:#f5f8fc;font-weight:600;display:flex;align-items:center;gap:8px;user-select:none}
|
||||
details.date-group>summary::-webkit-details-marker{display:none}
|
||||
details.date-group>summary::before{content:"\25B8";display:inline-block;transition:transform .15s;color:#8a94a6;font-size:12px}
|
||||
details.date-group[open]>summary::before{content:"\25BE"}
|
||||
details.date-group>table{margin:0;border:none}
|
||||
details.date-group>table tr:first-child td,details.date-group>table tr:first-child th{border-top:none}
|
||||
.fold-btns{margin:10px 0 2px;display:flex;gap:8px}
|
||||
</style>
|
||||
|
||||
<div class="fold-btns">
|
||||
<button type="button" class="btn" onclick="document.querySelectorAll('details.date-group').forEach(d=>d.open=true)">全部展开</button>
|
||||
<button type="button" class="btn" onclick="document.querySelectorAll('details.date-group').forEach(d=>d.open=false)">全部折叠</button>
|
||||
</div>
|
||||
|
||||
{% if factory %}
|
||||
<div class="scope-banner">当前仅显示工厂:<b>{{ factory.code }}</b>(切换顶栏工厂可改变范围)</div>
|
||||
{% else %}
|
||||
<div class="scope-banner">当前显示:<b>全部工厂(汇总)</b></div>
|
||||
{% endif %}
|
||||
|
||||
<form method="get" class="card" style="display:flex; gap:12px; align-items:flex-end; flex-wrap:wrap; margin-bottom:14px">
|
||||
<label style="font-size:13px">起始日期<input type="date" name="from" value="{{ frm }}"></label>
|
||||
<label style="font-size:13px">结束日期<input type="date" name="to" value="{{ to }}"></label>
|
||||
<label style="font-size:13px">类型
|
||||
<select name="type" style="margin-left:4px">
|
||||
<option value="" {% if type=='' %}selected{% endif %}>全部</option>
|
||||
<option value="in" {% if type=='in' %}selected{% endif %}>入库</option>
|
||||
<option value="out" {% if type=='out' %}selected{% endif %}>出库</option>
|
||||
<option value="transfer" {% if type=='transfer' %}selected{% endif %}>厂间调拨</option>
|
||||
<option value="prod" {% if type=='prod' %}selected{% endif %}>生产相关</option>
|
||||
<option value="other" {% if type=='other' %}selected{% endif %}>其他</option>
|
||||
</select>
|
||||
</label>
|
||||
<label style="font-size:13px">调拨/关联单号<input type="text" name="q" value="{{ q }}" placeholder="如 TR... / WO..." style="width:140px"></label>
|
||||
<button class="primary" type="submit">筛选</button>
|
||||
<a class="btn" href="{{ url_for('ledger') }}">清除</a>
|
||||
<a class="btn primary" href="{{ url_for('ledger_export') }}?from={{ frm|urlencode }}&to={{ to|urlencode }}&type={{ type|urlencode }}&q={{ q|urlencode }}" title="按所选日期生成 Excel,出库与入库合并在同一文件">按日期生成 Excel</a>
|
||||
</form>
|
||||
|
||||
{% if not date_groups %}
|
||||
<p class="muted">暂无流水记录。</p>
|
||||
{% endif %}
|
||||
|
||||
{% for d, items in date_groups %}
|
||||
<details class="date-group" {{ 'open' if loop.first else '' }}>
|
||||
<summary>
|
||||
<span class="dg-date">{{ d }}</span>
|
||||
<span class="small muted">({{ items|length }} 笔)</span>
|
||||
</summary>
|
||||
<table class="tbl">
|
||||
<thead><tr>
|
||||
<th style="width:48px">类型</th><th>业务类型</th><th>物料编号</th><th>型号</th>
|
||||
<th class="num">数量</th><th>工厂</th><th>来源 / 用途</th><th>调拨/关联单号</th><th>经手人</th>
|
||||
</tr></thead>
|
||||
<tbody>
|
||||
{% for r in items %}
|
||||
<tr class="{{ 'row-in' if r.type=='入' else 'row-out' }}">
|
||||
<td><span class="badge {{ 'ok' if r.type=='入' else 'danger' }}">{{ r.type }}</span></td>
|
||||
<td>{{ r.cat }}</td>
|
||||
<td>{% if r.m %}<a href="{{ url_for('material_detail', mid=r.m.id) }}">{{ r.m.part_number }}</a>{% else %}-{% endif %}</td>
|
||||
<td>{{ r.m.manufacturer if r.m else '-' }}</td>
|
||||
<td class="num">{{ '%.2f'|format(r.qty) }}</td>
|
||||
<td>{{ r.fac.code if r.fac else '—' }}</td>
|
||||
<td>{{ r.who or '-' }}</td>
|
||||
<td>{{ r.ref or '-' }}</td>
|
||||
<td>{{ r.op or '-' }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</details>
|
||||
{% endfor %}
|
||||
{% endblock %}
|
||||
@@ -29,11 +29,12 @@
|
||||
|
||||
<h3>入库流水(每次入库时间 / 数量)</h3>
|
||||
<table class="tbl">
|
||||
<thead><tr><th>入库日期</th><th>送货日期</th><th>录入时间</th><th>数量</th><th>批次号</th><th>来源 / 供应商</th><th>经手人</th><th>备注</th></tr></thead>
|
||||
<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 inbounds %}
|
||||
<tr>
|
||||
<td>{{ r.occur_date }}</td>
|
||||
<td>{{ fmap.get(r.factory_id, '-') }}</td>
|
||||
<td>{{ r.delivery_date or '-' }}</td>
|
||||
<td class="small muted">{{ r.created_at.strftime('%Y-%m-%d %H:%M') if r.created_at else '-' }}</td>
|
||||
<td class="num">{{ '%.2f'|format(r.qty) }}</td>
|
||||
@@ -43,18 +44,19 @@
|
||||
<td>{{ r.remark or '-' }}</td>
|
||||
</tr>
|
||||
{% else %}
|
||||
<tr><td colspan="8" class="muted">暂无入库记录</td></tr>
|
||||
<tr><td colspan="9" 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></tr></thead>
|
||||
<thead><tr><th>出库日期</th><th>工厂</th><th>录入时间</th><th>数量</th><th>用途</th><th>关联单号</th><th>经手人</th><th>备注</th></tr></thead>
|
||||
<tbody>
|
||||
{% for r in outbounds %}
|
||||
<tr>
|
||||
<td>{{ r.occur_date }}</td>
|
||||
<td>{{ fmap.get(r.factory_id, '-') }}</td>
|
||||
<td class="small muted">{{ r.created_at.strftime('%Y-%m-%d %H:%M') if r.created_at else '-' }}</td>
|
||||
<td class="num">{{ '%.2f'|format(r.qty) }}</td>
|
||||
<td>{{ r.purpose or '-' }}</td>
|
||||
@@ -63,7 +65,7 @@
|
||||
<td>{{ r.remark or '-' }}</td>
|
||||
</tr>
|
||||
{% else %}
|
||||
<tr><td colspan="7" class="muted">暂无出库记录</td></tr>
|
||||
<tr><td colspan="8" class="muted">暂无出库记录</td></tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
+66
-19
@@ -4,29 +4,76 @@
|
||||
<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>
|
||||
</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> | 经手人: {{ o.operator or '-' }}</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 '-' }}</p>
|
||||
{% if o.remark %}<p class="small muted">备注: {{ o.remark }}</p>{% endif %}
|
||||
</div>
|
||||
|
||||
<h3>领料需求 (单套用量 × 接单量)</h3>
|
||||
<table class="tbl">
|
||||
<thead><tr><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>{{ r.m.part_number }}</td>
|
||||
<td>{{ r.m.manufacturer or '-' }}</td>
|
||||
<td class="num">{{ '%.2f'|format(r.need) }}</td>
|
||||
<td class="num">{{ '%.2f'|format(r.have) }}</td>
|
||||
<td class="num">{{ '%.2f'|format(r.gap) }}</td>
|
||||
<td><span class="badge {{ 'danger' if r.short else 'ok' }}">{{ '缺料' if r.short else '充足' }}</span></td>
|
||||
</tr>
|
||||
{% else %}
|
||||
<tr><td colspan="6" class="muted">该 BOM 没有组成物料</td></tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
<h3>领料需求与调入 (单套用量 × 接单量){% if factory %} | 按 {{ factory.code }} 工厂库存核算{% endif %}</h3>
|
||||
|
||||
<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>
|
||||
</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.need) }}</td>
|
||||
<td class="num">
|
||||
{% if r.posted %}
|
||||
<span class="badge ok" title="已生成入库流水">已调入 {{ '%.2f'|format(r.alloc) }}</span>
|
||||
{% else %}
|
||||
<input name="alloc_{{ r.m.id }}" type="number" step="0.01" min="0"
|
||||
value="{{ '%.2f'|format(r.alloc) if r.alloc else '' }}"
|
||||
placeholder="{{ '%.2f'|format(r.need) }}" style="width:96px">
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="num">{{ '%.2f'|format(r.have) }}</td>
|
||||
<td class="num">{{ '%.2f'|format(r.gap) }}</td>
|
||||
<td><span class="badge {{ 'danger' if r.short else 'ok' }}">{{ '缺料' if r.short else '充足' }}</span></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">「确认调入」是一次厂间调拨:从来源工厂出库减、生产工厂入库加,系统总库存不变。</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 生成领料出库? 库存不足将记为负并在看板预警。');">
|
||||
|
||||
@@ -1,20 +1,33 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}新建生产订单{% endblock %}
|
||||
{% block title %}{{ '编辑生产订单' if o else '新建生产订单' }}{% endblock %}
|
||||
{% block content %}
|
||||
<form method="post" class="card form-card" style="max-width:520px">
|
||||
<h3>新建生产订单</h3>
|
||||
<label>订单号 (留空自动生成)</label>
|
||||
<input name="order_no" placeholder="如 WO20260911xxx">
|
||||
<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') }}">
|
||||
<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 }}">{{ b.code }} ({{ b.name or '-' }})</option>
|
||||
<option value="{{ b.id }}" {% if o and o.bom_id == b.id %}selected{% endif %}>{{ b.code }} ({{ b.name or '-' }})</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<label>接单量 (台/套) *</label>
|
||||
<input name="qty" type="number" step="0.01" min="0.01" required>
|
||||
<div class="row-actions"><button class="primary">生成订单</button></div>
|
||||
<p class="small muted">生成后可在订单详情页按 BOM 展开领料需求, 并一键"确认领料"出库。</p>
|
||||
<input name="qty" type="number" step="0.01" min="0.01" required value="{{ '%.2f'|format(o.qty) if o else '' }}">
|
||||
<label>生产工厂</label>
|
||||
<select name="factory_id">
|
||||
<option value="">-- 不指定(汇总库存校验)--</option>
|
||||
{% for f in factories %}
|
||||
<option value="{{ f.id }}" {% if o and o.factory_id == f.id %}selected{% endif %}>{{ f.code }} · {{ f.name }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<label>备注</label>
|
||||
<input name="remark" value="{{ o.remark if o else '' }}" placeholder="选填">
|
||||
<div class="row-actions">
|
||||
<button class="primary">{{ '保存修改' if o else '生成订单' }}</button>
|
||||
<a class="btn" href="{{ url_for('orders') }}">取消</a>
|
||||
</div>
|
||||
<p class="small muted">生成后可在订单详情页按 BOM 展开领料需求, 并手动录入每个物料的「调入数量」。</p>
|
||||
</form>
|
||||
{% endblock %}
|
||||
|
||||
+12
-4
@@ -6,19 +6,27 @@
|
||||
<a class="btn primary" href="{{ url_for('order_new') }}">+ 新建订单</a>
|
||||
</div>
|
||||
<table class="tbl">
|
||||
<thead><tr><th>订单号</th><th>BOM</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 class="{{ 'row-danger' if d.o.status=='picked' and false }}">
|
||||
<tr>
|
||||
<td>{{ d.o.order_no }}</td>
|
||||
<td>{{ d.bom.code if d.bom else '-' }} ({{ d.bom.name if d.bom else '' }})</td>
|
||||
<td class="num">{{ '%.2f'|format(d.o.qty) }}</td>
|
||||
<td>{{ d.factory.code if d.factory else '未指定' }}</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>
|
||||
<td><a href="{{ url_for('order_detail', oid=d.o.id) }}">查看</a></td>
|
||||
<td class="row-actions">
|
||||
<a href="{{ url_for('order_detail', oid=d.o.id) }}">查看</a>
|
||||
<a href="{{ url_for('order_edit', oid=d.o.id) }}">编辑</a>
|
||||
<form method="post" action="{{ url_for('order_delete', oid=d.o.id) }}" style="display:inline"
|
||||
onsubmit="return confirm('确认删除生产订单 {{ d.o.order_no }}? 关联出入库流水将保留。');">
|
||||
<button class="link-danger" type="submit">删除</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
{% else %}
|
||||
<tr><td colspan="6" class="muted">还没有生产订单</td></tr>
|
||||
<tr><td colspan="7" class="muted">还没有生产订单</td></tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}替代料关系{% endblock %}
|
||||
{% block content %}
|
||||
|
||||
<div class="toolbar">
|
||||
<h3 style="margin:0">🔁 替代料关系</h3>
|
||||
<form method="post" action="{{ url_for('substitute_import') }}" enctype="multipart/form-data" style="display:flex;gap:8px;align-items:center">
|
||||
<span class="small muted">从「替代料」Excel 批量导入:</span>
|
||||
<input type="file" name="file" accept=".xlsx,.xls" required>
|
||||
<button class="primary" type="submit">导入</button>
|
||||
</form>
|
||||
</div>
|
||||
<p class="small muted">登记「某主料可由哪些替代料替代」。出库 / BOM 领料时,若主料库存不足,系统会自动提示可用的替代料。当前按{% if factory %}工厂 {{ factory.code }}{% else %}全部工厂汇总{% endif %}库存核算。</p>
|
||||
|
||||
<div class="card form-card" style="max-width:720px;margin-bottom:18px">
|
||||
<h4 style="margin-top:0">+ 新增替代关系</h4>
|
||||
<form method="post" action="{{ url_for('substitutes') }}">
|
||||
<input type="hidden" name="action" value="add">
|
||||
<label>主料 *</label>
|
||||
<select name="main_id" required>
|
||||
<option value="">-- 选择主料 --</option>
|
||||
{% for m in mats %}
|
||||
<option value="{{ m.id }}">{{ m.part_number }} ({{ m.manufacturer or '-' }})</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<label>替代料 *</label>
|
||||
<select name="sub_id" required>
|
||||
<option value="">-- 选择替代料 --</option>
|
||||
{% for m in mats %}
|
||||
<option value="{{ m.id }}">{{ m.part_number }} ({{ m.manufacturer or '-' }})</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<label>备注</label>
|
||||
<input name="note" placeholder="如 替代型号说明">
|
||||
<div class="row-actions"><button class="primary" type="submit">添加</button></div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<h3>已登记关系({{ rows|length }} 条)</h3>
|
||||
<table class="tbl">
|
||||
<thead><tr>
|
||||
<th>主料编号</th><th>主料型号</th><th>替代料编号</th><th>替代料型号</th>
|
||||
<th>替代料当前库存</th><th>备注</th><th>操作</th>
|
||||
</tr></thead>
|
||||
<tbody>
|
||||
{% for r in rows %}
|
||||
<tr>
|
||||
<td><a href="{{ url_for('material_detail', mid=r.main.id) }}">{{ r.main.part_number }}</a></td>
|
||||
<td>{{ r.main.manufacturer or '-' }}</td>
|
||||
<td><a href="{{ url_for('material_detail', mid=r.sub.id) }}">{{ r.sub.part_number }}</a></td>
|
||||
<td>{{ r.sub.manufacturer or '-' }}</td>
|
||||
<td class="num {{ 'row-in' if r.stock>0 else 'row-out' }}">{{ '%.2f'|format(r.stock) }}</td>
|
||||
<td>{{ r.note or '-' }}</td>
|
||||
<td>
|
||||
<form method="post" action="{{ url_for('substitute_delete', sid=r.id) }}" onsubmit="return confirm('确认删除该替代关系?');">
|
||||
<button class="btn danger" type="submit">删除</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
{% else %}
|
||||
<tr><td colspan="7" class="muted">暂无替代关系,可手动新增或从 Excel 导入。</td></tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,77 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}新建厂间调拨{% endblock %}
|
||||
{% block content %}
|
||||
|
||||
<div class="toolbar">
|
||||
<h3 style="margin:0">➡️ 新建厂间调拨</h3>
|
||||
<a class="btn" href="{{ url_for('transfers') }}">← 返回调拨记录</a>
|
||||
</div>
|
||||
|
||||
<div class="card form-card" style="max-width:560px">
|
||||
<p class="small muted">从「调出工厂」扣减库存,同时向「调入工厂」增加库存,两笔流水自动生成、可追溯对账。系统会校验调出厂库存是否充足。</p>
|
||||
<form method="post" id="tfForm">
|
||||
<label>调出工厂(源厂)*</label>
|
||||
<select name="from_factory_id" id="fromFac" onchange="refreshPreview()" required>
|
||||
<option value="">-- 选择调出工厂 --</option>
|
||||
{% for f in factories %}
|
||||
<option value="{{ f.id }}" {% if current_fid == f.id %}selected{% endif %}>{{ f.code }} · {{ f.name }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
|
||||
<label>调入工厂(目标厂)*</label>
|
||||
<select name="to_factory_id" id="toFac" required>
|
||||
<option value="">-- 选择调入工厂 --</option>
|
||||
{% for f in factories %}
|
||||
<option value="{{ f.id }}">{{ f.code }} · {{ f.name }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
|
||||
<label>物料 *</label>
|
||||
<select name="material_id" id="matSel" onchange="refreshPreview()" required>
|
||||
<option value="">-- 选择物料 --</option>
|
||||
{% for m in materials %}
|
||||
<option value="{{ m.id }}">{{ m.part_number }} ({{ m.manufacturer or '-' }})</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
|
||||
<label>数量 *</label>
|
||||
<input name="qty" id="qty" type="number" step="0.01" min="0.01" oninput="refreshPreview()" required>
|
||||
|
||||
<label>经手人</label>
|
||||
<input name="operator">
|
||||
|
||||
<label>业务日期</label>
|
||||
<input name="occur_date" type="date" value="{{ today }}">
|
||||
|
||||
<label>备注</label>
|
||||
<input name="remark">
|
||||
|
||||
<div id="preview" style="margin:10px 0"></div>
|
||||
|
||||
<div class="row-actions">
|
||||
<button class="primary" type="submit">确认调拨</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function refreshPreview(){
|
||||
const mat = document.getElementById('matSel');
|
||||
const from = document.getElementById('fromFac');
|
||||
const qty = document.getElementById('qty');
|
||||
const box = document.getElementById('preview');
|
||||
if(!mat.value || !from.value || !qty.value || parseFloat(qty.value) <= 0){
|
||||
box.innerHTML = ''; return;
|
||||
}
|
||||
const u = `/api/transfer/preview?material_id=${mat.value}&from_factory_id=${from.value}&qty=${encodeURIComponent(qty.value)}`;
|
||||
fetch(u).then(r=>r.json()).then(d=>{
|
||||
const stock = d.stock;
|
||||
if(d.short){
|
||||
box.innerHTML = `<div class="flash error">⚠ 源厂库存不足: 现有 <b>${stock}</b>, 需调拨 <b>${parseFloat(qty.value)}</b>, 提交将被拦截</div>`;
|
||||
} else {
|
||||
box.innerHTML = `<div class="flash ok">源厂当前库存: <b>${stock}</b>, 调拨后剩余 <b>${(stock - parseFloat(qty.value)).toFixed(2)}</b></div>`;
|
||||
}
|
||||
}).catch(()=>{ box.innerHTML=''; });
|
||||
}
|
||||
</script>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,49 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}厂间调拨记录{% endblock %}
|
||||
{% block content %}
|
||||
|
||||
<div class="toolbar">
|
||||
<h3 style="margin:0">➡️ 厂间调拨记录</h3>
|
||||
{% if g.user and g.user.role in ('admin','manager') %}
|
||||
<a class="btn primary" href="{{ url_for('transfer_new') }}">+ 新建调拨</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{% if factory %}
|
||||
<div class="scope-banner">
|
||||
🔎 当前已选中某工厂,仅显示<b>调出或调入涉及该厂</b>的调拨记录。
|
||||
<a href="{{ url_for('set_factory') }}" style="display:none"></a>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if not rows %}
|
||||
<p class="muted">暂无调拨记录。点击右上角「新建调拨」即可在工厂之间调拨物料。</p>
|
||||
{% else %}
|
||||
<table class="tbl">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>调拨单号</th>
|
||||
<th>调出厂 → 调入厂</th>
|
||||
<th>物料</th>
|
||||
<th>数量</th>
|
||||
<th>业务日期</th>
|
||||
<th>经手人</th>
|
||||
<th>备注</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for r in rows %}
|
||||
<tr>
|
||||
<td>{{ r.t.no }}</td>
|
||||
<td>{{ r.ff.code if r.ff else '?' }} → {{ r.tf.code if r.tf else '?' }}</td>
|
||||
<td><a href="{{ url_for('material_detail', mid=r.m.id) }}">{{ r.m.part_number if r.m else r.t.material_id }}</a></td>
|
||||
<td class="num">{{ '%.2f'|format(r.t.qty) }}</td>
|
||||
<td>{{ r.t.occur_date }}</td>
|
||||
<td>{{ r.t.operator or '-' }}</td>
|
||||
<td>{{ r.t.remark or '-' }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
@@ -23,6 +23,13 @@
|
||||
<option value="{{ m.id }}" data-pn="{{ m.part_number }}">{{ m.part_number }} ({{ m.manufacturer or '-' }})</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<div id="subHint" class="sub-hint" style="margin-top:8px"></div>
|
||||
<label>所属工厂 *</label>
|
||||
<select name="factory_id" required>
|
||||
{% for f in factories %}
|
||||
<option value="{{ f.id }}" {% if (current_factory_id == f.id) or (not current_factory_id and loop.first) %}selected{% endif %}>{{ f.code }} · {{ f.name }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<label>批次号</label>
|
||||
<input name="batch_no">
|
||||
<label>数量 *</label>
|
||||
@@ -49,6 +56,43 @@
|
||||
</div>
|
||||
</form>
|
||||
|
||||
{% if kind == 'outbound' %}
|
||||
<div class="card bom-issue-card" style="margin-top:18px">
|
||||
<h3 style="margin-top:0">🧩 按 BOM 批量操作(领料出库 / 调拨入库)</h3>
|
||||
<p class="small muted">选择 BOM 与数量,系统按单套用量自动生成每种组件的出库/入库流水,任意数量均可。</p>
|
||||
<form method="post" action="{{ url_for('bom_issue') }}" class="form-card">
|
||||
<label>操作类型 *</label>
|
||||
<select name="mode" id="bomMode" onchange="onBomModeChange();refreshBomPreview()">
|
||||
<option value="pick">按BOM齐套领料出库(扣本厂库存)</option>
|
||||
<option value="transfer">按BOM调拨入库(调入目标厂)</option>
|
||||
</select>
|
||||
<label>BOM 型号 *</label>
|
||||
<select name="bom_id" id="bomSel" onchange="refreshBomPreview()" required>
|
||||
<option value="">-- 选择 BOM --</option>
|
||||
{% for b in boms %}
|
||||
<option value="{{ b.id }}">{{ b.code }} ({{ b.name or '-' }})</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<label>数量(台/套)*</label>
|
||||
<input name="qty" id="bomQty" type="number" step="0.01" min="0.01" oninput="refreshBomPreview()" required>
|
||||
<label id="facLabel">本厂 *</label>
|
||||
<select name="factory_id" id="bomFac" onchange="refreshBomPreview()">
|
||||
{% for f in factories %}
|
||||
<option value="{{ f.id }}" {% if (current_factory_id == f.id) or (not current_factory_id and loop.first) %}selected{% endif %}>{{ f.code }} · {{ f.name }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<label>经手人</label>
|
||||
<input name="operator">
|
||||
<label>业务日期</label>
|
||||
<input name="occur_date" type="date" value="{{ today }}">
|
||||
<label>备注</label>
|
||||
<input name="remark">
|
||||
<div class="row-actions"><button class="primary" type="submit">生成流水</button></div>
|
||||
</form>
|
||||
<div id="bomPreview" style="margin-top:12px"></div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<h3>最近 {{ recs|length }} 条{{ '入库' if kind=='inbound' else '出库' }}记录</h3>
|
||||
<table class="tbl">
|
||||
<thead>
|
||||
@@ -213,5 +257,71 @@ function commitBatch(){
|
||||
if(d.added>0) location.reload();
|
||||
}).catch(e=>alert('导入失败: '+e));
|
||||
}
|
||||
|
||||
/* ---- 出库页 BOM 卡片: 实时需求预览 ---- */
|
||||
function refreshBomPreview(){
|
||||
const bid = document.getElementById('bomSel');
|
||||
const qty = document.getElementById('bomQty');
|
||||
const fac = document.getElementById('bomFac');
|
||||
const mode = document.getElementById('bomMode');
|
||||
if(!bid || !qty || !fac || !mode) return;
|
||||
const box = document.getElementById('bomPreview');
|
||||
if(!bid.value || !qty.value || parseFloat(qty.value) <= 0){ box.innerHTML=''; return; }
|
||||
const u = `/api/bom/preview?bom_id=${bid.value}&qty=${encodeURIComponent(qty.value)}&factory_id=${fac.value}&mode=${mode.value}`;
|
||||
fetch(u).then(r=>r.json()).then(d=>{
|
||||
if(!d.items.length){ box.innerHTML='<p class="small muted">该 BOM 无组成物料</p>'; return; }
|
||||
let html = '<table class="tbl"><thead><tr><th>物料编号</th><th>型号</th><th>需求数量</th><th>工厂库存</th><th>状态</th></tr></thead><tbody>';
|
||||
d.items.forEach(it=>{
|
||||
const cls = it.short ? 'row-danger' : '';
|
||||
const tag = it.short ? '<span class="badge danger">缺料</span>' : '<span class="badge ok">充足</span>';
|
||||
html += `<tr class="${cls}"><td>${it.pn}</td><td>${it.mf}</td><td class="num">${it.need}</td><td class="num">${it.stock}</td><td>${tag}</td></tr>`;
|
||||
if(it.subs && it.subs.length){
|
||||
const avail = it.subs.filter(s=>s.available);
|
||||
const subTxt = it.subs.map(s=>`${s.pn}(库存${s.stock}${s.available?' 有货':' 无货'})`).join(',');
|
||||
const subCls = avail.length ? 'row-in' : '';
|
||||
html += `<tr class="${subCls}"><td colspan="5" class="small muted">↳ 可替代料: ${subTxt}</td></tr>`;
|
||||
}
|
||||
});
|
||||
html += '</tbody></table>';
|
||||
if(d.any_short && mode.value === 'pick'){
|
||||
html = '<div class="flash error">存在缺料, 提交将被拦截</div>' + html;
|
||||
}
|
||||
box.innerHTML = html;
|
||||
}).catch(e=>{ box.innerHTML=''; });
|
||||
}
|
||||
function onBomModeChange(){
|
||||
const mode = document.getElementById('bomMode');
|
||||
const lbl = document.getElementById('facLabel');
|
||||
if(mode && lbl){ lbl.textContent = (mode.value === 'pick') ? '本厂 *' : '目标厂 *'; }
|
||||
}
|
||||
if(document.getElementById('bomMode')){
|
||||
onBomModeChange();
|
||||
}
|
||||
|
||||
/* ---- 出库单物料: 实时提示可替代料 ---- */
|
||||
function refreshSubHint(){
|
||||
const sel = document.getElementById('materialSelect');
|
||||
const box = document.getElementById('subHint');
|
||||
if(!sel || !box) return;
|
||||
const mid = sel.value;
|
||||
if(!mid){ box.innerHTML=''; return; }
|
||||
fetch(`/api/substitutes/${mid}`).then(r=>r.json()).then(d=>{
|
||||
if(!d.subs || !d.subs.length){
|
||||
box.innerHTML = '<p class="small muted">该物料暂无登记替代料</p>';
|
||||
return;
|
||||
}
|
||||
let html = '<div class="card" style="padding:10px 12px"><b>可替代料(库存提示):</b>'
|
||||
+ '<table class="tbl"><thead><tr><th>替代料编号</th><th>型号</th><th>当前库存</th><th>状态</th></tr></thead><tbody>';
|
||||
d.subs.forEach(s=>{
|
||||
const cls = s.available ? 'row-in' : 'row-out';
|
||||
const tag = s.available ? '<span class="badge ok">有货</span>' : '<span class="badge danger">无货</span>';
|
||||
html += `<tr class="${cls}"><td>${s.pn}</td><td>${s.mf}</td><td class="num">${s.stock}</td><td>${tag}</td></tr>`;
|
||||
});
|
||||
html += '</tbody></table></div>';
|
||||
box.innerHTML = html;
|
||||
}).catch(()=>{ box.innerHTML=''; });
|
||||
}
|
||||
const _subSel = document.getElementById('materialSelect');
|
||||
if(_subSel){ _subSel.addEventListener('change', refreshSubHint); }
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
Reference in New Issue
Block a user