117 lines
8.8 KiB
HTML
117 lines
8.8 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="zh-CN">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>订单审核与优先级 - 智能排产系统</title>
|
||
<link rel="stylesheet" href="../css/common.css">
|
||
</head>
|
||
<body>
|
||
<div id="app">
|
||
<header class="header" id="header"></header>
|
||
<div class="body">
|
||
<aside class="sidebar" id="sidebar"></aside>
|
||
<main class="main">
|
||
<h1 class="page-title">订单审核与优先级</h1>
|
||
<p class="page-desc">对待审核订单进行审批,系统根据客户等级、交期、金额和手工优先级自动计算优先级评分。</p>
|
||
<div class="toolbar">
|
||
<button class="btn" onclick="priorityConfigModal()">优先级权重</button>
|
||
<div class="search-box"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg><input id="searchInput" placeholder="搜索" oninput="render()"></div>
|
||
</div>
|
||
<div class="card">
|
||
<div class="card-body table-wrap">
|
||
<table id="auditTable">
|
||
<thead><tr><th>订单号</th><th>客户/等级</th><th>产品/数量</th><th>交期</th><th>金额</th><th>优先级评分</th><th>操作</th></tr></thead>
|
||
<tbody></tbody></table>
|
||
</div>
|
||
</div>
|
||
</main>
|
||
</div>
|
||
</div>
|
||
<script src="../js/data.js"></script>
|
||
<script src="../js/common.js"></script>
|
||
<script>
|
||
initPage('order-audit');
|
||
function levelScore(level) { return { VIP: 10, A: 8, B: 5, C: 3 }[level] || 3; }
|
||
function urgencyScore(deliveryDate) {
|
||
const days = Math.ceil((apsData.$utils.parseDate(deliveryDate + ' 00:00') - apsData.$utils.today()) / (24 * 60 * 60 * 1000));
|
||
if (days <= 3) return 10; if (days <= 7) return 7; if (days <= 14) return 5; return 3;
|
||
}
|
||
function amountScore(amount) { return Math.min(10, Math.round(amount / 50000)); }
|
||
function computeScore(o, manual) {
|
||
const w = apsData.scheduleParams.priorityWeights || { customer_level_weight: 0.30, urgency_weight: 0.35, amount_weight: 0.15, manual_weight: 0.20 };
|
||
return (w.customer_level_weight * levelScore(o.customerLevel) + w.urgency_weight * urgencyScore(o.deliveryDate) + w.amount_weight * amountScore(o.totalAmount) + w.manual_weight * (manual || o.manualPriority || 5)).toFixed(2);
|
||
}
|
||
function render() {
|
||
const kw = document.getElementById('searchInput').value.toLowerCase();
|
||
const rows = apsData.salesOrders.filter(o => o.status === 'SUBMITTED').filter(o => {
|
||
return !kw || (o.orderNo + ' ' + o.customerName + ' ' + o.items[0].productName).toLowerCase().includes(kw);
|
||
}).sort((a, b) => a.priority - b.priority);
|
||
const tbody = document.querySelector('#auditTable tbody');
|
||
tbody.innerHTML = rows.map(o => {
|
||
const item = o.items[0];
|
||
return `<tr>
|
||
<td>${o.orderNo}</td>
|
||
<td>${o.customerName} <span class="badge badge-${o.customerLevel==='VIP'?'danger':(o.customerLevel==='A'?'primary':'default')}">${o.customerLevel}</span></td>
|
||
<td>${item.productName} × ${item.quantity}</td>
|
||
<td>${o.deliveryDate}</td>
|
||
<td>¥${o.totalAmount.toLocaleString()}</td>
|
||
<td class="text-primary"><strong>${computeScore(o)}</strong></td>
|
||
<td class="actions"><button class="btn btn-sm btn-success" onclick="auditOrder(${o.id})">审核</button></td>
|
||
</tr>`;
|
||
}).join('') || '<tr><td colspan="7" class="text-center text-muted">暂无待审核订单</td></tr>';
|
||
}
|
||
function auditOrder(id) {
|
||
const o = findById(apsData.salesOrders, id);
|
||
openModal('订单审核', `
|
||
<div class="form-row"><div class="form-group"><label>订单号</label><input value="${o.orderNo}" readonly></div><div class="form-group"><label>客户等级</label><input value="${o.customerLevel}" readonly></div></div>
|
||
<div class="form-row"><div class="form-group"><label>产品/数量</label><input value="${o.items[0].productName} × ${o.items[0].quantity}" readonly></div><div class="form-group"><label>订单金额</label><input value="${o.totalAmount.toLocaleString()}" readonly></div></div>
|
||
<div class="form-row"><div class="form-group"><label>交期</label><input value="${o.deliveryDate}" readonly></div><div class="form-group"><label>优先级 (1-10)</label><input id="a_priority" type="number" min="1" max="10" value="${o.priority}" oninput="updateScore(${id})"></div></div>
|
||
<div class="card mt-2" style="padding:12px">
|
||
<div class="flex justify-between"><span>客户等级得分</span><strong>${levelScore(o.customerLevel)}</strong></div>
|
||
<div class="flex justify-between"><span>交期紧迫度得分</span><strong>${urgencyScore(o.deliveryDate)}</strong></div>
|
||
<div class="flex justify-between"><span>订单金额得分</span><strong>${amountScore(o.totalAmount)}</strong></div>
|
||
<div class="flex justify-between mt-2 text-primary"><span>综合优先级评分</span><strong id="a_score">${computeScore(o)}</strong></div>
|
||
</div>
|
||
<div class="form-row mt-2"><div class="form-group"><label>审核意见 / 退回原因</label><textarea id="a_reason" rows="2"></textarea></div></div>
|
||
`, `<button class="btn" onclick="closeModal()">取消</button><button class="btn btn-danger" onclick="rejectOrder(${id})">退回</button><button class="btn btn-success" onclick="approveOrder(${id})">通过</button>`);
|
||
}
|
||
function updateScore(id) {
|
||
const o = findById(apsData.salesOrders, id);
|
||
document.getElementById('a_score').textContent = computeScore(o, Number(document.getElementById('a_priority').value));
|
||
}
|
||
function approveOrder(id) {
|
||
const o = findById(apsData.salesOrders, id);
|
||
o.priority = Number(document.getElementById('a_priority').value);
|
||
o.manualPriority = o.priority;
|
||
o.status = 'CONFIRMED';
|
||
o.updatedAt = apsData.$utils.fmtDateTime(new Date());
|
||
o.changes.push({ id: apsData.$utils.nextId('orderChange'), changeType: 'AUDIT', oldValue: 'SUBMITTED', newValue: 'CONFIRMED', reason: '审核通过', operator: 'admin', createdAt: apsData.$utils.fmtDateTime(new Date()) });
|
||
apsData.logs.unshift({ id: apsData.$utils.nextId('log'), type: 'INFO', category: 'ORDER', operationType: 'AUDIT', targetType: 'SALES_ORDER', targetId: id, action: '审核通过', description: `订单 ${o.orderNo} 审核通过`, operator: 'admin', createdAt: apsData.$utils.fmtDateTime(new Date()) });
|
||
apsData.$utils.save(apsData); closeModal(); showToast('审核通过'); render();
|
||
}
|
||
function rejectOrder(id) {
|
||
const o = findById(apsData.salesOrders, id);
|
||
const reason = document.getElementById('a_reason').value || '审核不通过';
|
||
o.status = 'REJECTED';
|
||
o.updatedAt = apsData.$utils.fmtDateTime(new Date());
|
||
o.changes.push({ id: apsData.$utils.nextId('orderChange'), changeType: 'AUDIT', oldValue: 'SUBMITTED', newValue: 'REJECTED', reason, operator: 'admin', createdAt: apsData.$utils.fmtDateTime(new Date()) });
|
||
apsData.logs.unshift({ id: apsData.$utils.nextId('log'), type: 'WARNING', category: 'ORDER', operationType: 'AUDIT', targetType: 'SALES_ORDER', targetId: id, action: '审核退回', description: `订单 ${o.orderNo} 被退回:${reason}`, operator: 'admin', createdAt: apsData.$utils.fmtDateTime(new Date()) });
|
||
apsData.$utils.save(apsData); closeModal(); showToast('已退回', reason); render();
|
||
}
|
||
function priorityConfigModal() {
|
||
const w = apsData.scheduleParams.priorityWeights || { customer_level_weight: 0.30, urgency_weight: 0.35, amount_weight: 0.15, manual_weight: 0.20 };
|
||
openModal('优先级权重配置', `
|
||
<div class="form-row"><div class="form-group"><label>客户等级权重</label><input id="w_cust" type="number" step="0.05" value="${w.customer_level_weight}"></div><div class="form-group"><label>交期紧迫度权重</label><input id="w_urg" type="number" step="0.05" value="${w.urgency_weight}"></div></div>
|
||
<div class="form-row"><div class="form-group"><label>订单金额权重</label><input id="w_amt" type="number" step="0.05" value="${w.amount_weight}"></div><div class="form-group"><label>手工优先级权重</label><input id="w_man" type="number" step="0.05" value="${w.manual_weight}"></div></div>
|
||
`, `<button class="btn" onclick="closeModal()">取消</button><button class="btn btn-primary" onclick="saveWeights()">保存</button>`);
|
||
}
|
||
function saveWeights() {
|
||
apsData.scheduleParams.priorityWeights = { customer_level_weight: Number(document.getElementById('w_cust').value), urgency_weight: Number(document.getElementById('w_urg').value), amount_weight: Number(document.getElementById('w_amt').value), manual_weight: Number(document.getElementById('w_man').value) };
|
||
apsData.$utils.save(apsData); closeModal(); showToast('权重已更新'); render();
|
||
}
|
||
render();
|
||
</script>
|
||
</body>
|
||
</html>
|