93 lines
5.2 KiB
HTML
93 lines
5.2 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>
|
|||
|
|
<div class="toolbar">
|
|||
|
|
<button class="btn btn-primary" onclick="batchIssue()">批量下发已拆分工单</button>
|
|||
|
|
</div>
|
|||
|
|
<div class="card">
|
|||
|
|
<div class="card-body table-wrap">
|
|||
|
|
<table id="poTable">
|
|||
|
|
<thead><tr><th><input type="checkbox" id="checkAll" onclick="toggleAll()"></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('work-order-split');
|
|||
|
|
let selectedIds = [];
|
|||
|
|
function render() {
|
|||
|
|
const rows = apsData.productionOrders.filter(po => po.status !== 'CANCELLED');
|
|||
|
|
const tbody = document.querySelector('#poTable tbody');
|
|||
|
|
tbody.innerHTML = rows.map(po => {
|
|||
|
|
const woCount = apsData.workOrders.filter(wo => wo.productionOrderId == po.id).length;
|
|||
|
|
return `<tr>
|
|||
|
|
<td><input type="checkbox" class="row-check" value="${po.id}" ${selectedIds.includes(po.id)?'checked':''} onchange="toggleRow(${po.id})"></td>
|
|||
|
|
<td>${po.orderNo}</td>
|
|||
|
|
<td>${po.productName} × ${po.quantity}</td>
|
|||
|
|
<td>${po.lineName}</td>
|
|||
|
|
<td>${po.plannedStartDate ? po.plannedStartDate.slice(0,16) : ''} ~ ${po.plannedEndDate ? po.plannedEndDate.slice(0,16) : ''}</td>
|
|||
|
|
<td>${woCount}</td>
|
|||
|
|
<td class="actions">
|
|||
|
|
${woCount === 0 ? `<button class="btn btn-sm btn-primary" onclick="splitWo(${po.id})">拆分工单</button>` : '<span class="text-muted">已拆分</span>'}
|
|||
|
|
${woCount > 0 ? `<button class="btn btn-sm btn-success" onclick="issueAll(${po.id})">下发</button>` : ''}
|
|||
|
|
</td>
|
|||
|
|
</tr>`;
|
|||
|
|
}).join('') || '<tr><td colspan="7" class="text-center text-muted">无数据</td></tr>';
|
|||
|
|
}
|
|||
|
|
function toggleRow(id) { selectedIds = selectedIds.includes(id) ? selectedIds.filter(x=>x!==id) : [...selectedIds, id]; render(); }
|
|||
|
|
function toggleAll() { selectedIds = document.getElementById('checkAll').checked ? apsData.productionOrders.map(po=>po.id) : []; render(); }
|
|||
|
|
function splitWo(poId) {
|
|||
|
|
const po = findById(apsData.productionOrders, poId);
|
|||
|
|
const steps = findRoutingSteps(po.productId);
|
|||
|
|
if (!steps.length) return showToast('无法拆分', '产品缺少工艺路线', 'error');
|
|||
|
|
const start = apsData.$utils.parseDate(po.plannedStartDate || apsData.$utils.fmtDateTime(apsData.$utils.today()));
|
|||
|
|
const end = apsData.$utils.parseDate(po.plannedEndDate || apsData.$utils.addMinutes(start, 24*60));
|
|||
|
|
const span = (end - start) / steps.length;
|
|||
|
|
steps.forEach((step, idx) => {
|
|||
|
|
const op = findById(apsData.operations, step.operationId);
|
|||
|
|
const ws = findWorkstationForOperation(po.lineId, step.operationId);
|
|||
|
|
const s = apsData.$utils.addMinutes(start, idx * span);
|
|||
|
|
const e = apsData.$utils.addMinutes(start, (idx + 1) * span);
|
|||
|
|
apsData.workOrders.push({
|
|||
|
|
id: apsData.$utils.nextId('workOrder'), orderNo: po.orderNo + '-' + String(idx+1).padStart(2,'0'), productionOrderId: po.id, productionOrderNo: po.orderNo,
|
|||
|
|
operationId: op.id, operationName: op.name, sequenceNo: step.sequenceNo, productId: po.productId, productName: po.productName, quantity: po.quantity, unit: po.unit,
|
|||
|
|
completedQuantity: 0, lineId: po.lineId, lineName: po.lineName, workstationId: ws ? ws.id : null, workstationName: ws ? ws.name : '-',
|
|||
|
|
plannedStartTime: apsData.$utils.fmtDateTime(s), plannedEndTime: apsData.$utils.fmtDateTime(e), status: 'PENDING', teamId: null, teamName: null,
|
|||
|
|
requiredMaterials: [], requiredEquipment: [], priority: po.priority, isFrozen: false, kitStatus: 'PASSED', progressPercent: 0, conflictCount: 0, note: ''
|
|||
|
|
});
|
|||
|
|
});
|
|||
|
|
apsData.logs.unshift({ id: apsData.$utils.nextId('log'), type: 'INFO', category: 'WO', operationType: 'SPLIT', targetType: 'PRODUCTION_ORDER', targetId: poId, action: '工单拆分', description: `生产订单 ${po.orderNo} 拆分为 ${steps.length} 个工单`, operator: 'admin', createdAt: apsData.$utils.fmtDateTime(new Date()) });
|
|||
|
|
apsData.$utils.save(apsData); showToast('拆分完成', po.orderNo); render();
|
|||
|
|
}
|
|||
|
|
function issueAll(poId) {
|
|||
|
|
let count = 0;
|
|||
|
|
apsData.workOrders.filter(wo => wo.productionOrderId == poId && wo.status === 'PENDING').forEach(wo => { wo.status = 'ISSUED'; wo.isFrozen = true; count++; });
|
|||
|
|
apsData.$utils.save(apsData); showToast('下发完成', `${count} 个工单已下发`); render();
|
|||
|
|
}
|
|||
|
|
function batchIssue() {
|
|||
|
|
let count = 0;
|
|||
|
|
selectedIds.forEach(id => issueAll(id));
|
|||
|
|
selectedIds = []; render();
|
|||
|
|
}
|
|||
|
|
render();
|
|||
|
|
</script>
|
|||
|
|
</body>
|
|||
|
|
</html>
|