- 多工厂(factory 表) + 顶栏工厂下拉过滤, 库存按厂聚合 - 厂间调拨(transfer): 源厂减+目标厂加, 总库存不变, 双向流水对账 - 替代料关系(substitute): 出库/BOM领料/生产领料缺料时提示可替代料 - 流水台账(ledger): 入出按日期合并折叠, 按日期导出同一 Excel - 生产订单增强: 编辑/删除(级联清关联流水)/调入数量/确认调入=厂间调拨 - 入库新增送货日期字段; 出库物料编码扫码定位 - 重写 README 补全全部模块; .gitignore 排除 dist_pkg/build_pkg/*.db - 停止跟踪 instance/app.db(运行时自动生成)
105 lines
5.1 KiB
HTML
105 lines
5.1 KiB
HTML
{% 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 %}
|