Files
order_system/po_app/templates/order_edit.html
T

238 lines
13 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 body %}
<h1 class="page">编辑订单 · {{ o.order_no }}({{ o.doc_type_text }})</h1>
<div class="card">
<h2>单据信息</h2>
<form method="post" id="orderForm">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<input type="hidden" name="row_version" value="{{ o.row_version }}">
<div class="form-grid">
<div><label>采购单号</label><input type="text" value="{{ o.order_no }}" disabled></div>
<div><label>采购日期</label><input type="date" name="order_date" value="{{ o.order_date or '' }}"></div>
<div><label>税点</label><input type="text" name="tax_rate" value="{{ o.tax_rate or '' }}" placeholder="如 13%"></div>
</div>
<h3>供方(供应商)</h3>
<div class="form-grid">
<div>
<label>供应商</label>
<input type="text" name="supplier_name" id="supplier_name" value="{{ o.supplier_name or '' }}">
<input type="search" id="supplierSearch" placeholder="搜索供应商名称、简称或编号" aria-label="搜索供应商" style="margin-top:4px;">
<select id="supplierPick" style="margin-top:4px;">
<option value="">-- 从供应商库带入 --</option>
{% for s in suppliers %}<option value="{{ s.id }}" data-search="{{ s.short_name or '' }} {{ s.code or '' }}">{{ s.name }}{{ '(' ~ s.short_name ~ ')' if s.short_name else '' }}</option>{% endfor %}
</select>
</div>
<div><label>联系人</label><input type="text" name="supplier_contact" id="supplier_contact" value="{{ o.supplier_contact or '' }}"></div>
<div><label>电话</label><input type="text" name="supplier_phone" id="supplier_phone" value="{{ o.supplier_phone or '' }}"></div>
<div><label>付款方式</label><input type="text" name="payment_terms" id="payment_terms" value="{{ o.payment_terms or '' }}"></div>
<div><label>税号</label><input type="text" name="supplier_tax_no" id="supplier_tax_no" value="{{ o.supplier_tax_no or '' }}"></div>
<div><label>银行帐号</label><input type="text" name="supplier_bank_account" id="supplier_bank_account" value="{{ o.supplier_bank_account or '' }}"></div>
<div><label>开户银行</label><input type="text" name="supplier_bank_name" id="supplier_bank_name" value="{{ o.supplier_bank_name or '' }}"></div>
</div>
<h3>需方(本公司)</h3>
<div class="form-grid">
<div><label>需方名称</label><input type="text" name="our_company" value="{{ o.our_company or '' }}"></div>
<div><label>联系人</label><input type="text" name="our_contact" value="{{ o.our_contact or '' }}"></div>
<div><label>电话</label><input type="text" name="our_phone" value="{{ o.our_phone or '' }}"></div>
<div><label>税号</label><input type="text" name="our_tax_no" value="{{ o.our_tax_no or '' }}"></div>
<div style="grid-column: span 2;">
<label>交货地点</label>
<input type="text" name="delivery_place" value="{{ o.delivery_place or '' }}">
</div>
</div>
<h3>物料明细</h3>
<table class="grid" id="itemTable">
<tr>
<th style="width:38px;">序</th>
<th style="width:150px;">物料代码</th>
<th>物料规格</th>
<th style="width:62px;">单位</th>
<th style="width:78px;">数量</th>
<th style="width:92px;">含税单价</th>
<th style="width:96px;">金额</th>
<th style="width:118px;">交期</th>
<th style="width:130px;">备注</th>
<th style="width:44px;">删</th>
</tr>
{% for it in o.items %}
<tr>
<td class="center seq">{{ loop.index }}</td>
<td><input type="text" name="item_code" value="{{ it.material_code or '' }}">
<input type="search" class="matsearch" placeholder="搜索代码或规格" aria-label="搜索物料" style="margin-top:3px;">
<select class="matpick" style="margin-top:3px;">
<option value="">-- 物料库 --</option>
{% for m in materials %}<option value="{{ m.id }}" data-code="{{ m.code }}" data-spec="{{ m.spec }}" data-unit="{{ m.unit }}" data-price="{{ m.price }}">{{ m.code }} {{ m.spec }}</option>{% endfor %}
</select>
</td>
<td><input type="text" name="item_spec" value="{{ it.material_spec or '' }}"></td>
<td><input type="text" name="item_unit" value="{{ it.unit or '' }}"></td>
<td><input type="number" min="0" max="999999999" step="0.0001" name="item_qty" class="qty" value="{{ it.qty or 0 }}"></td>
<td><input type="number" min="0" max="999999999" step="0.0001" name="item_price" class="price" value="{{ it.price or 0 }}"></td>
<td><input type="text" class="amt" value="{{ '%.2f'|format(it.amount or 0) }}" disabled></td>
<td><input type="date" name="item_date" value="{{ it.delivery_date or '' }}"></td>
<td><input type="text" name="item_remark" value="{{ it.remark or '' }}"></td>
<td class="center"><button type="button" class="btn sm danger" onclick="delRow(this)">×</button></td>
</tr>
{% endfor %}
</table>
<template id="rowTpl">
<tr>
<td class="center seq">1</td>
<td><input type="text" name="item_code" value="">
<input type="search" class="matsearch" placeholder="搜索代码或规格" aria-label="搜索物料" style="margin-top:3px;">
<select class="matpick" style="margin-top:3px;">
<option value="">-- 物料库 --</option>
{% for m in materials %}<option value="{{ m.id }}" data-code="{{ m.code }}" data-spec="{{ m.spec }}" data-unit="{{ m.unit }}" data-price="{{ m.price }}">{{ m.code }} {{ m.spec }}</option>{% endfor %}
</select>
</td>
<td><input type="text" name="item_spec" value=""></td>
<td><input type="text" name="item_unit" value=""></td>
<td><input type="number" min="0" max="999999999" step="0.0001" name="item_qty" class="qty" value="0"></td>
<td><input type="number" min="0" max="999999999" step="0.0001" name="item_price" class="price" value="0"></td>
<td><input type="text" class="amt" value="0.00" disabled></td>
<td><input type="date" name="item_date" value=""></td>
<td><input type="text" name="item_remark" value=""></td>
<td class="center"><button type="button" class="btn sm danger" onclick="delRow(this)">×</button></td>
</tr>
</template>
<div class="toolbar" style="margin-top:10px;">
<button type="button" class="btn" onclick="addRow()">+ 添加一行</button>
<span class="grow"></span>
<span class="total-bar">合计:<span class="amt" id="totalAmt">{{ '%.2f'|format(o.total_amount) }}</span> 元
<span class="muted">逐行四舍五入到分后合计</span></span>
</div>
<h3>补充说明</h3>
<textarea name="note" placeholder="选填,会打印在 PDF 备注区">{{ o.note or '' }}</textarea>
<div style="margin-top:16px;">
<button class="btn primary" type="submit" name="intent" value="save">保存草稿</button>
<input name="comment" placeholder="提交说明(选填)" style="width:220px;">
<button class="btn ok" type="submit" name="intent" value="submit">保存并提交审批</button>
<a class="btn" href="{{ url_for('order_view', oid=o.id) }}">查看订单</a>
<a class="btn" href="{{ url_for('order_pdf', oid=o.id) }}" target="_blank">预览 PDF</a>
</div>
</form>
</div>
<div class="card">
<h2>从 Excel 导入明细</h2>
<div class="hint">
支持 .xlsx / .xls。系统自动识别表头(物料代码、物料规格、单位、数量、含税单价、交期、备注),
没有表头则按前 7 列顺序读取。行数超出模板也没关系,PDF 会自动分页。
<a href="{{ url_for('import_template') }}">下载导入模板</a>
</div>
<form method="post" action="{{ url_for('import_excel', oid=o.id) }}" enctype="multipart/form-data" class="toolbar">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<input type="hidden" name="row_version" value="{{ o.row_version }}">
<input type="file" name="file" accept=".xls,.xlsx" required style="width:280px;">
<select name="mode" style="width:140px;">
<option value="append">追加到现有明细</option>
<option value="replace">替换全部明细</option>
</select>
<button class="btn primary">导入</button>
</form>
</div>
<script src="{{ url_for('static', filename='money.js') }}"></script>
<script src="{{ url_for('static', filename='search_select.js') }}"></script>
<script>
const MATERIALS = {{ materials|map(attribute='id')|list|tojson }};
let supplierPrices = {{ supplier_prices|tojson }};
POFilterSelect.bind(document.getElementById('supplierSearch'), document.getElementById('supplierPick'));
async function updateSupplierPrices(){
const name = document.getElementById('supplier_name').value.trim();
const tax = document.getElementById('supplier_tax_no').value.trim();
if (!name) { supplierPrices = {}; return; }
try {
const query = new URLSearchParams({supplier_name: name, supplier_tax_no: tax});
const response = await fetch('{{ url_for('api_material_prices') }}?' + query.toString());
if (response.ok) supplierPrices = await response.json();
} catch (error) { /* The catalog default remains usable offline. */ }
}
function bindRow(tr){
tr.querySelectorAll('.qty, .price').forEach(i => i.addEventListener('input', calc));
const pick = tr.querySelector('.matpick');
POFilterSelect.bind(tr.querySelector('.matsearch'), pick);
if (pick) pick.addEventListener('change', function(){
const opt = this.selectedOptions[0];
if (!opt || !opt.value) return;
tr.querySelector('[name=item_code]').value = opt.dataset.code || '';
tr.querySelector('[name=item_spec]').value = opt.dataset.spec || '';
tr.querySelector('[name=item_unit]').value = opt.dataset.unit || '';
tr.querySelector('.price').value = supplierPrices[opt.dataset.code] || opt.dataset.price || 0;
calc();
});
}
function calc(){
let total = 0n, valid = true;
document.querySelectorAll('#itemTable tr').forEach((tr, idx) => {
const qty = tr.querySelector('.qty'), price = tr.querySelector('.price');
if (!qty || !price) return;
try {
const cents = POMoney.lineCents(qty.value, price.value);
tr.querySelector('.amt').value = POMoney.format(cents);
total += cents;
} catch (e) { tr.querySelector('.amt').value = '请检查'; valid = false; }
tr.querySelector('.seq').textContent = idx;
});
document.getElementById('totalAmt').textContent = valid ? POMoney.format(total) : '请检查数量和单价';
}
function addRow(){
dirty = true;
// 从 <template> 克隆,避免"明细为空时没有可复制的行"导致点击无反应
const t = document.getElementById('itemTable');
const tr = document.getElementById('rowTpl').content.firstElementChild.cloneNode(true);
tr.querySelectorAll('input').forEach(i => { if (i.type !== 'date') i.value = ''; });
const q = tr.querySelector('.qty'); if (q) q.value = 0;
const p = tr.querySelector('.price'); if (p) p.value = 0;
const a = tr.querySelector('.amt'); if (a) a.value = '0.00';
const sel = tr.querySelector('.matpick'); if (sel) sel.selectedIndex = 0;
t.appendChild(tr);
bindRow(tr); calc();
}
function delRow(btn){
dirty = true;
btn.closest('tr').remove(); calc();
}
document.querySelectorAll('#itemTable tr').forEach(bindRow);
document.getElementById('supplier_name').addEventListener('change', updateSupplierPrices);
document.getElementById('supplier_tax_no').addEventListener('change', updateSupplierPrices);
calc();
document.getElementById('supplierPick').addEventListener('change', async function(){
if (!this.value) return;
const r = await fetch('/api/suppliers').then(r => r.json());
const s = r.find(x => String(x.id) === this.value);
if (!s) return;
document.getElementById('supplier_name').value = s.name || '';
document.getElementById('supplier_contact').value = s.contact || '';
document.getElementById('supplier_phone').value = s.phone || '';
document.getElementById('supplier_tax_no').value = s.tax_no || '';
document.getElementById('supplier_bank_name').value = s.bank_name || '';
document.getElementById('supplier_bank_account').value = s.bank_account || '';
document.getElementById('payment_terms').value = s.payment_terms || '';
await updateSupplierPrices();
});
let dirty = false;
const orderForm = document.getElementById('orderForm');
orderForm.addEventListener('input', () => { dirty = true; });
orderForm.addEventListener('change', () => { dirty = true; });
orderForm.addEventListener('submit', () => { dirty = false; });
document.querySelector('[action$="/import_excel"]').addEventListener('submit', e => {
if (dirty) { e.preventDefault(); alert('请先保存当前修改,再导入明细。'); }
});
window.addEventListener('beforeunload', e => { if (dirty) { e.preventDefault(); e.returnValue = ''; } });
document.querySelectorAll('a[target="_blank"]').forEach(a => a.addEventListener('click', e => {
if (dirty) { e.preventDefault(); alert('请先保存当前修改,再预览 PDF。'); }
}));
</script>
{% endblock %}