aps-agent/aps-frontend/js/data.js

404 lines
16 KiB
JavaScript
Raw Permalink Normal View History

2026-07-21 11:05:57 +08:00
/**
* APS 测试数据与状态持久化
*/
const apsData = (function () {
const STORAGE_KEY = 'aps_frontend_data_v1';
// ---------- ID 生成 ----------
let idCounters = {};
function nextId(type) {
idCounters[type] = (idCounters[type] || 0) + 1;
return idCounters[type];
}
function resetCounters(data) {
idCounters = {};
const scan = (arr, type) => {
if (!Array.isArray(arr)) return;
arr.forEach(item => {
if (item && item.id) idCounters[type] = Math.max(idCounters[type] || 0, Number(item.id) || 0);
});
};
scan(data.factories, 'factory');
scan(data.workshops, 'workshop');
scan(data.lines, 'line');
scan(data.workstations, 'workstation');
scan(data.operations, 'operation');
scan(data.routings, 'routing');
scan(data.materials, 'material');
scan(data.boms, 'bom');
scan(data.equipment, 'equipment');
scan(data.teams, 'team');
scan(data.shifts, 'shift');
scan(data.salesOrders, 'salesOrder');
scan(data.productionOrders, 'productionOrder');
scan(data.workOrders, 'workOrder');
scan(data.scheduleVersions, 'scheduleVersion');
scan(data.conflicts, 'conflict');
scan(data.logs, 'log');
}
// ---------- 日期工具 ----------
function fmtDate(d) {
if (!d) return '';
const dt = new Date(d);
const y = dt.getFullYear();
const m = String(dt.getMonth() + 1).padStart(2, '0');
const day = String(dt.getDate()).padStart(2, '0');
return `${y}-${m}-${day}`;
}
function fmtTime(d) {
if (!d) return '';
const dt = new Date(d);
return String(dt.getHours()).padStart(2, '0') + ':' + String(dt.getMinutes()).padStart(2, '0');
}
function fmtDateTime(d) {
if (!d) return '';
return fmtDate(d) + ' ' + fmtTime(d);
}
function parseDate(s) {
if (!s) return null;
if (s instanceof Date) return s;
return new Date(s.replace(' ', 'T'));
}
function addMinutes(d, minutes) {
return new Date(new Date(d).getTime() + minutes * 60000);
}
function today() {
const d = new Date();
d.setHours(0, 0, 0, 0);
return d;
}
function dayStart(d) {
const dt = new Date(d);
dt.setHours(0, 0, 0, 0);
return dt;
}
// ---------- 种子数据 ----------
function seed() {
const data = {
factories: [],
workshops: [],
lines: [],
workstations: [],
operations: [],
routings: [],
routingSteps: [],
materials: [],
boms: [],
bomItems: [],
lineProducts: [],
workstationOperations: [],
equipment: [],
teams: [],
shifts: [],
shiftCalendar: [],
maintenance: [],
salesOrders: [],
productionOrders: [],
workOrders: [],
scheduleVersions: [],
conflicts: [],
logs: [],
scheduleParams: {
defaultEngine: 'HYBRID',
planningHorizonDays: 30,
timeGranularityMinutes: 15,
deliveryBufferRatio: 0.95,
weights: { tardiness: 0.4, cost: 0.3, utilization: 0.2, balance: 0.1 },
constraints: {
materialKit: true,
equipment: true,
personnel: true,
changeover: true
},
rushStrategy: 'STRATEGY_SHIFT',
freezeWindowHours: 24
}
};
// 工厂
data.factories.push({ id: 1, code: 'F001', name: '青岛工厂', timezone: 'Asia/Shanghai', address: '青岛市高新区', status: 'ACTIVE' });
// 车间
data.workshops.push({ id: 1, factoryId: 1, code: 'WS01', name: '一车间', status: 'ACTIVE' });
data.workshops.push({ id: 2, factoryId: 1, code: 'WS02', name: '二车间', status: 'ACTIVE' });
// 产线
data.lines.push({ id: 1, workshopId: 1, code: 'L001', name: '装配产线A', capacityPerDay: 1000, taktTime: 0.5, efficiencyFactor: 1.0, status: 'ACTIVE', alternativeLineIds: [3] });
data.lines.push({ id: 2, workshopId: 1, code: 'L002', name: '装配产线B', capacityPerDay: 800, taktTime: 0.6, efficiencyFactor: 0.95, status: 'ACTIVE', alternativeLineIds: [1] });
data.lines.push({ id: 3, workshopId: 2, code: 'L003', name: '装配产线C', capacityPerDay: 600, taktTime: 0.8, efficiencyFactor: 0.9, status: 'ACTIVE', alternativeLineIds: [1] });
// 工位
[
[1, 'WS001', 'SMT-01', 1],
[1, 'WS002', 'DIP-01', 2],
[1, 'WS003', 'ASSY-01', 3],
[1, 'WS004', 'TEST-01', 4],
[1, 'WS005', 'PKG-01', 5],
[2, 'WS006', 'SMT-02', 1],
[2, 'WS007', 'ASSY-02', 2],
[2, 'WS008', 'TEST-02', 3],
[2, 'WS009', 'PKG-02', 4],
[3, 'WS010', 'SMT-03', 1],
[3, 'WS011', 'DIP-03', 2],
[3, 'WS012', 'TEST-03', 3],
[3, 'WS013', 'PKG-03', 4]
].forEach((x, i) => {
data.workstations.push({ id: i + 1, lineId: x[0], code: x[1], name: x[2], sequenceNo: x[3], status: 'ACTIVE' });
});
// 工序
data.operations.push(
{ id: 1, code: 'OP10', name: 'SMT贴片', type: 'INTERNAL', standardTime: 0.5 },
{ id: 2, code: 'OP20', name: 'DIP插件', type: 'INTERNAL', standardTime: 0.8 },
{ id: 3, code: 'OP30', name: '组装', type: 'INTERNAL', standardTime: 1.0 },
{ id: 4, code: 'OP40', name: '测试', type: 'INTERNAL', standardTime: 0.6 },
{ id: 5, code: 'OP50', name: '包装', type: 'INTERNAL', standardTime: 0.3 }
);
// 工艺路线
data.routings.push({ id: 1, productId: 1, version: 'V1.0', versionName: '控制器A工艺V1.0', isDefault: true, status: 'ACTIVE' });
data.routings.push({ id: 2, productId: 2, version: 'V1.0', versionName: '控制器B工艺V1.0', isDefault: true, status: 'ACTIVE' });
data.routings.push({ id: 3, productId: 3, version: 'V1.0', versionName: '控制器C工艺V1.0', isDefault: true, status: 'ACTIVE' });
const steps = [
[1, 1, 1, null, 30, 0.5, 0, 10, false],
[1, 2, 2, 1, 30, 0.8, 0, 10, false],
[1, 3, 3, 2, 20, 1.0, 0, 10, false],
[1, 4, 4, 3, 20, 0.6, 0, 10, false],
[1, 5, 5, 4, 10, 0.3, 0, 10, false],
[2, 1, 1, null, 30, 0.4, 0, 10, false],
[2, 2, 3, 1, 20, 0.9, 0, 10, false],
[2, 3, 4, 2, 20, 0.5, 0, 10, false],
[2, 4, 5, 3, 10, 0.3, 0, 10, false],
[3, 1, 1, null, 30, 0.6, 0, 10, false],
[3, 2, 2, 1, 30, 1.0, 0, 10, false],
[3, 3, 4, 2, 20, 0.7, 0, 10, false],
[3, 4, 5, 3, 10, 0.3, 0, 10, false]
];
steps.forEach((x, i) => {
data.routingSteps.push({
id: i + 1,
routingId: x[0],
operationId: x[1],
sequenceNo: x[2],
prevStepId: x[3],
setupTime: x[4],
runTimePerUnit: x[5],
waitTime: x[6],
transferTime: x[7],
isExternal: x[8]
});
});
// 物料
data.materials.push(
{ id: 1, code: 'CTRL-A', name: '智能控制器A型', spec: '标准版', type: 'FINISHED_PRODUCT', unit: '件', safetyStock: 50, procurementLeadTime: 0, stock: 20, inTransit: 0 },
{ id: 2, code: 'CTRL-B', name: '智能控制器B型', spec: '经济版', type: 'FINISHED_PRODUCT', unit: '件', safetyStock: 50, procurementLeadTime: 0, stock: 30, inTransit: 0 },
{ id: 3, code: 'CTRL-C', name: '智能控制器C型', spec: '高频版', type: 'FINISHED_PRODUCT', unit: '件', safetyStock: 30, procurementLeadTime: 0, stock: 10, inTransit: 0 },
{ id: 10, code: 'PCB-001', name: '标准PCB板', spec: 'FR-4', type: 'RAW_MATERIAL', unit: '块', safetyStock: 500, procurementLeadTime: 7, stock: 1800, inTransit: 500 },
{ id: 11, code: 'RC-001', name: '电阻电容包', spec: '混合包', type: 'RAW_MATERIAL', unit: '包', safetyStock: 300, procurementLeadTime: 5, stock: 900, inTransit: 300 },
{ id: 12, code: 'CASE-001', name: '塑料外壳', spec: '白色', type: 'RAW_MATERIAL', unit: '个', safetyStock: 400, procurementLeadTime: 10, stock: 1200, inTransit: 0 },
{ id: 13, code: 'SCR-001', name: '螺丝包', spec: 'M3', type: 'RAW_MATERIAL', unit: '包', safetyStock: 200, procurementLeadTime: 3, stock: 800, inTransit: 0 },
{ id: 14, code: 'WIRE-001', name: '线材', spec: '22AWG', type: 'RAW_MATERIAL', unit: '米', safetyStock: 1000, procurementLeadTime: 4, stock: 3500, inTransit: 0 },
{ id: 15, code: 'LABEL-001', name: '标签', spec: '防伪', type: 'RAW_MATERIAL', unit: '张', safetyStock: 500, procurementLeadTime: 2, stock: 2500, inTransit: 0 },
{ id: 16, code: 'PCB-002', name: '高频PCB板', spec: '高频', type: 'RAW_MATERIAL', unit: '块', safetyStock: 200, procurementLeadTime: 10, stock: 350, inTransit: 200 }
);
// BOM
data.boms.push({ id: 1, productId: 1, version: 'V2.1', versionName: 'A型BOM V2.1', isDefault: true, status: 'ACTIVE' });
data.boms.push({ id: 2, productId: 2, version: 'V1.0', versionName: 'B型BOM V1.0', isDefault: true, status: 'ACTIVE' });
data.boms.push({ id: 3, productId: 3, version: 'V1.0', versionName: 'C型BOM V1.0', isDefault: true, status: 'ACTIVE' });
const bomItems = [
[1, 10, 1, 1, false], [1, 11, 1, 1, false], [1, 12, 1, 3, false], [1, 13, 1, 3, false], [1, 14, 0.5, 3, false], [1, 15, 1, 5, false],
[2, 10, 1, 1, false], [2, 11, 1.2, 1, false], [2, 12, 1, 3, false], [2, 13, 1, 3, false], [2, 14, 0.6, 3, false], [2, 15, 1, 5, false],
[3, 16, 1, 1, true], [3, 11, 1.5, 1, false], [3, 12, 1, 3, false], [3, 13, 1, 3, false], [3, 14, 0.4, 3, false], [3, 15, 1, 5, false]
];
bomItems.forEach((x, i) => {
data.bomItems.push({ id: i + 1, bomId: x[0], materialId: x[1], quantity: x[2], operationId: x[3], isKeyMaterial: x[4] });
});
// 产线产品配置
data.lineProducts.push(
{ id: 1, lineId: 1, productId: 1, standardCapacity: 1000, priority: 1, setupTime: 30 },
{ id: 2, lineId: 1, productId: 3, standardCapacity: 600, priority: 2, setupTime: 40 },
{ id: 3, lineId: 2, productId: 2, standardCapacity: 800, priority: 1, setupTime: 30 },
{ id: 4, lineId: 2, productId: 1, standardCapacity: 700, priority: 2, setupTime: 40 },
{ id: 5, lineId: 3, productId: 1, standardCapacity: 500, priority: 3, setupTime: 45 },
{ id: 6, lineId: 3, productId: 3, standardCapacity: 600, priority: 1, setupTime: 30 }
);
// 工位工序配置
const wsOps = [
[1, 1], [2, 2], [3, 3], [4, 4], [5, 5],
[6, 1], [7, 3], [8, 4], [9, 5],
[10, 1], [11, 2], [12, 4], [13, 5]
];
wsOps.forEach((x, i) => {
data.workstationOperations.push({ id: i + 1, workstationId: x[0], operationId: x[1], setupTime: 20, runTimePerUnit: 0.5, isPrimary: true });
});
// 设备
data.workstations.forEach((ws, i) => {
data.equipment.push({
id: i + 1,
code: 'EQ' + String(i + 1).padStart(3, '0'),
name: ws.name + '设备',
model: 'MDL-' + ws.code,
workstationId: ws.id,
capacityPerHour: 120,
efficiencyFactor: 0.95 + Math.random() * 0.05,
availabilityRate: 0.92 + Math.random() * 0.07,
status: Math.random() > 0.9 ? 'MAINTENANCE' : 'RUNNING'
});
});
// 班组
data.teams.push(
{ id: 1, code: 'T001', name: '贴片班组A', workshopId: 1, memberCount: 12, skillLevel: 'L3', status: 'ACTIVE' },
{ id: 2, code: 'T002', name: '插件班组A', workshopId: 1, memberCount: 8, skillLevel: 'L2', status: 'ACTIVE' },
{ id: 3, code: 'T003', name: '组装班组A', workshopId: 1, memberCount: 15, skillLevel: 'L3', status: 'ACTIVE' },
{ id: 4, code: 'T004', name: '测试班组A', workshopId: 1, memberCount: 10, skillLevel: 'L4', status: 'ACTIVE' },
{ id: 5, code: 'T005', name: '包装班组A', workshopId: 1, memberCount: 8, skillLevel: 'L2', status: 'ACTIVE' },
{ id: 6, code: 'T006', name: '综合班组B', workshopId: 1, memberCount: 20, skillLevel: 'L3', status: 'ACTIVE' }
);
// 班次
data.shifts.push(
{ id: 1, code: 'D', name: '早班', startTime: '08:00', endTime: '16:00', breakPeriods: [{ start: '12:00', end: '13:00' }], isOvertime: false, status: 'ACTIVE' },
{ id: 2, code: 'N', name: '中班', startTime: '16:00', endTime: '24:00', breakPeriods: [{ start: '18:00', end: '18:30' }], isOvertime: false, status: 'ACTIVE' }
);
// 班次日历 (未来14天)
const base = today();
for (let i = 0; i < 14; i++) {
const date = new Date(base);
date.setDate(date.getDate() + i);
const dateStr = fmtDate(date);
const isWeekend = date.getDay() === 0 || date.getDay() === 6;
data.lines.forEach(line => {
data.shifts.forEach(shift => {
data.shiftCalendar.push({
id: data.shiftCalendar.length + 1,
lineId: line.id,
date: dateStr,
shiftId: shift.id,
isWorking: !isWeekend,
teamId: line.id === 2 ? 6 : (shift.id === 1 ? [1, 2, 3, 4, 5][line.id - 1] || 1 : 6),
maxWorkers: 10
});
});
});
}
// 维保计划
data.maintenance.push({
id: 1,
equipmentId: 3,
type: 'MAINTENANCE',
plannedStart: fmtDate(addMinutes(base, 2 * 24 * 60 + 8 * 60)) + ' 09:00',
plannedEnd: fmtDate(addMinutes(base, 2 * 24 * 60 + 8 * 60)) + ' 12:00',
status: 'PLANNED',
description: 'ASSY-01 季度保养'
});
// 销售订单
const orderSeed = [
['SO20260714001', '青岛科技', 'A', '2026-07-14', '2026-07-25', 3, 'CONFIRMED', false, 1, 500],
['SO20260714002', '海尔智家', 'VIP', '2026-07-13', '2026-07-22', 1, 'CONFIRMED', false, 2, 300],
['SO20260714003', '海信电子', 'B', '2026-07-14', '2026-07-24', 5, 'CONFIRMED', false, 1, 800],
['SO20260714004', '中车集团', 'A', '2026-07-12', '2026-07-28', 4, 'CONFIRMED', false, 3, 400],
['SO20260714005', '北汽新能源', 'C', '2026-07-15', '2026-07-30', 8, 'SUBMITTED', false, 2, 600],
['SO20260714006', '比亚迪', 'VIP', '2026-07-15', '2026-07-20', 1, 'CONFIRMED', true, 3, 200],
['SO20260714007', '青岛科技', 'A', '2026-07-16', '2026-07-26', 3, 'CONFIRMED', false, 2, 500]
];
orderSeed.forEach((x, i) => {
const pid = x[8];
const product = data.materials.find(m => m.id === pid);
data.salesOrders.push({
id: i + 1,
orderNo: x[0],
customerId: 'CUST' + String(i + 1).padStart(3, '0'),
customerName: x[1],
customerLevel: x[2],
orderDate: x[3],
deliveryDate: x[4],
priority: x[5],
manualPriority: null,
status: x[6],
source: i % 2 === 0 ? 'MANUAL' : 'API',
specialRequirements: i === 0 ? '需使用环保材料包装' : '',
totalAmount: x[9] * (100 + Math.floor(Math.random() * 200)),
isRush: x[7],
rushStrategy: x[7] ? 'STRATEGY_SHIFT' : null,
changes: [],
createdBy: 'admin',
createdAt: x[3] + ' 09:00',
updatedAt: x[3] + ' 09:00',
items: [{
id: i * 10 + 1,
orderId: i + 1,
lineNo: 1,
productId: pid,
productName: product.name,
productCode: product.code,
quantity: x[9],
unit: '件',
bomVersion: 'V2.1',
routingVersion: 'V1.0',
status: 'PENDING',
note: ''
}]
});
});
// 日志
data.logs.push(
{ id: 1, type: 'INFO', category: 'SCHEDULE', operationType: 'SYSTEM', targetType: 'SCHEDULE', targetId: 1, action: '系统初始化', description: '测试数据加载完成', operator: 'system', createdAt: fmtDateTime(new Date()) },
{ id: 2, type: 'INFO', category: 'ORDER', operationType: 'CREATE', targetType: 'SALES_ORDER', targetId: 1, action: '创建销售订单', description: '订单 SO20260714001 已创建', operator: 'admin', createdAt: '2026-07-14 09:05' }
);
resetCounters(data);
return data;
}
// ---------- localStorage 持久化 ----------
function load() {
try {
const raw = localStorage.getItem(STORAGE_KEY);
if (raw) return JSON.parse(raw);
} catch (e) {}
return null;
}
function save(data) {
try {
localStorage.setItem(STORAGE_KEY, JSON.stringify(data));
} catch (e) {}
}
let data = load();
if (!data) {
data = seed();
save(data);
}
resetCounters(data);
// 兼容旧数据:补全变更历史字段
(data.salesOrders || []).forEach(o => { if (!o.changes) o.changes = []; });
// 暴露工具方法
data.$utils = {
nextId,
fmtDate,
fmtTime,
fmtDateTime,
parseDate,
addMinutes,
today,
dayStart,
save
};
return data;
})();