142 lines
9.1 KiB
HTML
142 lines
9.1 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="row">
|
|
<div class="col-5">
|
|
<div class="card">
|
|
<div class="card-header">紧急订单信息</div>
|
|
<div class="card-body">
|
|
<div class="form-row"><div class="form-group"><label>客户名称 *</label><input id="r_customer" value="宁德时代"></div><div class="form-group"><label>客户等级</label><select id="r_level"><option>VIP</option><option>A</option><option>B</option><option>C</option></select></div></div>
|
|
<div class="form-row"><div class="form-group"><label>产品 *</label><select id="r_product"></select></div><div class="form-group"><label>数量 *</label><input id="r_qty" type="number" value="300"></div></div>
|
|
<div class="form-row"><div class="form-group"><label>要求交期 *</label><input id="r_delivery" type="date"></div><div class="form-group"><label>优先级</label><input id="r_priority" type="number" value="1" min="1" max="10"></div></div>
|
|
<div class="form-row"><div class="form-group"><label>插单策略 *</label><select id="r_strategy">
|
|
<option value="STRATEGY_SHIFT">顺延(后续订单整体后移)</option>
|
|
<option value="STRATEGY_SPLIT">拆分(拆出空闲产能插单)</option>
|
|
<option value="STRATEGY_ALTERNATIVE">替代(启用替代产线)</option>
|
|
<option value="STRATEGY_OVERTIME">加班(启用加班班次)</option>
|
|
<option value="STRATEGY_OUTSOURCE">外协(部分工序外协)</option>
|
|
<option value="STRATEGY_COMPRESSION">压缩(压缩非关键路径)</option>
|
|
</select></div></div>
|
|
<div class="form-row"><div class="form-group"><label>影响范围</label><select id="r_range"><option value="AFFECTED_ONLY">仅影响范围内</option><option value="FULL">全量重排</option></select></div><div class="form-group"><label>冻结已下发工单</label><select id="r_freeze"><option value="true">是</option><option value="false">否</option></select></div></div>
|
|
<div class="form-row"><div class="form-group"><label>特殊要求</label><textarea id="r_note" rows="2">客户加急,务必按期交付</textarea></div></div>
|
|
<button class="btn btn-primary w-full" onclick="submitRushOrder()">提交插单</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-7">
|
|
<div class="card" id="impactCard" style="display:none">
|
|
<div class="card-header">影响分析</div>
|
|
<div class="card-body">
|
|
<div class="row">
|
|
<div class="col-4"><div class="kpi-value" id="i_orders">0</div><div class="kpi-label">受影响订单</div></div>
|
|
<div class="col-4"><div class="kpi-value" id="i_delay">0h</div><div class="kpi-label">最大延迟</div></div>
|
|
<div class="col-4"><div class="kpi-value" id="i_conflicts">0</div><div class="kpi-label">新增冲突</div></div>
|
|
</div>
|
|
<div class="mt-3 table-wrap">
|
|
<table id="impactTable"><thead><tr><th>订单/工单</th><th>影响类型</th><th>描述</th><th>建议</th></tr></thead><tbody></tbody></table>
|
|
</div>
|
|
</div>
|
|
<div class="card-footer">
|
|
<button class="btn" onclick="location.href='calendar.html'">查看日历</button>
|
|
<button class="btn" onclick="location.href='production-orders.html'">查看生产订单</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</main>
|
|
</div>
|
|
</div>
|
|
<script src="../js/data.js"></script>
|
|
<script src="../js/common.js"></script>
|
|
<script>
|
|
initPage('rush-order');
|
|
const products = apsData.materials.filter(m => m.type === 'FINISHED_PRODUCT');
|
|
document.getElementById('r_product').innerHTML = products.map(p => `<option value="${p.id}">${p.name} (${p.code})</option>`).join('');
|
|
document.getElementById('r_delivery').value = apsData.$utils.fmtDate(apsData.$utils.addMinutes(apsData.$utils.today(), 3 * 24 * 60));
|
|
|
|
function submitRushOrder() {
|
|
const customer = document.getElementById('r_customer').value.trim();
|
|
const qty = Number(document.getElementById('r_qty').value);
|
|
const delivery = document.getElementById('r_delivery').value;
|
|
const priority = Number(document.getElementById('r_priority').value);
|
|
const productId = Number(document.getElementById('r_product').value);
|
|
const strategy = document.getElementById('r_strategy').value;
|
|
if (!customer || !qty || !delivery || priority < 1 || priority > 10) return showToast('校验失败', '请填写完整信息', 'error');
|
|
|
|
const product = apsData.materials.find(m => m.id == productId);
|
|
const id = apsData.$utils.nextId('salesOrder');
|
|
const orderNo = 'SO' + delivery.replace(/-/g, '') + String(id).padStart(3, '0');
|
|
apsData.salesOrders.push({
|
|
id, orderNo, customerId: 'CUST' + String(id).padStart(3, '0'), customerName: customer,
|
|
customerLevel: document.getElementById('r_level').value, orderDate: apsData.$utils.fmtDate(apsData.$utils.today()),
|
|
deliveryDate: delivery, priority, manualPriority: priority, status: 'CONFIRMED', source: 'MANUAL',
|
|
specialRequirements: document.getElementById('r_note').value, totalAmount: qty * 200, isRush: true, rushStrategy: strategy,
|
|
changes: [], createdBy: 'admin', createdAt: apsData.$utils.fmtDateTime(new Date()), updatedAt: apsData.$utils.fmtDateTime(new Date()),
|
|
items: [{ id: apsData.$utils.nextId('salesOrderItem'), orderId: id, lineNo: 1, productId: product.id, productName: product.name, productCode: product.code, quantity: qty, unit: '件', bomVersion: 'V2.1', routingVersion: 'V1.0', status: 'PENDING', note: '' }]
|
|
});
|
|
apsData.logs.unshift({ id: apsData.$utils.nextId('log'), type: 'WARNING', category: 'ORDER', operationType: 'RUSH_ORDER', targetType: 'SALES_ORDER', targetId: id, action: '紧急插单', description: `紧急订单 ${orderNo} 创建,策略 ${strategy}`, operator: 'admin', createdAt: apsData.$utils.fmtDateTime(new Date()) });
|
|
|
|
// 执行局部排产
|
|
const orderIds = document.getElementById('r_range').value === 'FULL' ? [] : [id];
|
|
const version = runScheduling({
|
|
orderIds,
|
|
engineType: 'RULE',
|
|
strategyTemplate: 'DELIVERY_FIRST',
|
|
planningHorizonDays: 14,
|
|
startDate: apsData.$utils.fmtDate(apsData.$utils.today()),
|
|
constraints: apsData.scheduleParams.constraints,
|
|
triggerType: 'RUSH_ORDER',
|
|
name: '紧急插单 ' + orderNo
|
|
});
|
|
|
|
// 标记策略
|
|
apsData.productionOrders.filter(po => po.salesOrderId == id).forEach(po => po.rushStrategy = strategy);
|
|
apsData.$utils.save(apsData);
|
|
|
|
// 影响分析
|
|
const conflicts = apsData.conflicts.filter(c => c.versionId == version.id);
|
|
const affectedPoIds = new Set();
|
|
const affectedSoIds = new Set();
|
|
let maxDelay = 0;
|
|
conflicts.forEach(c => {
|
|
if (c.productionOrderId) affectedPoIds.add(c.productionOrderId);
|
|
if (c.workOrderId) {
|
|
const wo = findById(apsData.workOrders, c.workOrderId);
|
|
if (wo) affectedPoIds.add(wo.productionOrderId);
|
|
}
|
|
if (c.conflictType === 'DELAY' && c.description) {
|
|
const m = c.description.match(/([\d.]+)\s*小时/); if (m) maxDelay = Math.max(maxDelay, Number(m[1]));
|
|
}
|
|
});
|
|
affectedPoIds.forEach(pid => {
|
|
const po = findById(apsData.productionOrders, pid);
|
|
if (po && po.salesOrderId != id) affectedSoIds.add(po.salesOrderId);
|
|
});
|
|
document.getElementById('impactCard').style.display = 'block';
|
|
document.getElementById('i_orders').textContent = affectedSoIds.size;
|
|
document.getElementById('i_delay').textContent = (maxDelay ? maxDelay.toFixed(1) : '0') + 'h';
|
|
document.getElementById('i_conflicts').textContent = conflicts.length;
|
|
const tbody = document.querySelector('#impactTable tbody');
|
|
tbody.innerHTML = conflicts.slice(0, 20).map(c => {
|
|
const target = c.workOrderId ? findById(apsData.workOrders, c.workOrderId)?.orderNo : (c.productionOrderId ? findById(apsData.productionOrders, c.productionOrderId)?.orderNo : '-');
|
|
return `<tr><td>${target}</td><td>${c.conflictType}</td><td>${c.description}</td><td>${c.suggestedSolution}</td></tr>`;
|
|
}).join('') || '<tr><td colspan="4" class="text-center text-muted">无冲突</td></tr>';
|
|
showToast('插单完成', `已生成紧急订单 ${orderNo},影响 ${affectedSoIds.size} 个订单`);
|
|
}
|
|
</script>
|
|
</body>
|
|
</html>
|