const express = require('express'); const router = express.Router(); const { Op } = require('sequelize'); const { sequelize, dbDialect } = require('../db'); const InventoryPlan = require('../models/InventoryPlan'); const InventoryTask = require('../models/InventoryTask'); const InventoryRecord = require('../models/InventoryRecord'); const Device = require('../models/Device'); const Rack = require('../models/Rack'); const Room = require('../models/Room'); const User = require('../models/User'); const PendingDevice = require('../models/PendingDevice'); const { authMiddleware, authorize } = require('../middleware/auth'); const { PAGINATION } = require('../config'); const { generateId } = require('../utils/idGenerator'); InventoryTask.belongsTo(InventoryPlan, { foreignKey: 'planId', as: 'Plan' }); InventoryPlan.hasMany(InventoryTask, { foreignKey: 'planId', as: 'Tasks' }); InventoryTask.belongsTo(User, { foreignKey: 'assignedTo', as: 'Assignee' }); InventoryRecord.belongsTo(InventoryTask, { foreignKey: 'taskId', as: 'Task' }); InventoryTask.hasMany(InventoryRecord, { foreignKey: 'taskId', as: 'Records' }); InventoryRecord.belongsTo(InventoryPlan, { foreignKey: 'planId', as: 'Plan' }); InventoryPlan.hasMany(InventoryRecord, { foreignKey: 'planId', as: 'Records' }); InventoryRecord.belongsTo(Device, { foreignKey: 'deviceId', as: 'Device' }); InventoryRecord.belongsTo(User, { foreignKey: 'checkedBy', as: 'Checker' }); function generatePlanId() { return generateId({ prefix: 'PLAN' }); } function generateTaskId() { return generateId({ prefix: 'TASK' }); } function generateRecordId() { return generateId({ prefix: 'REC' }); } router.use(authMiddleware); router.get('/plans', async (req, res) => { try { const { status, page = 1, pageSize = PAGINATION.DEFAULT_PAGE_SIZE, keyword } = req.query; const offset = (page - 1) * pageSize; const where = {}; if (status) { where.status = status; } if (keyword) { where[Op.or] = [ { name: { [Op.like]: `%${keyword}%` } }, { description: { [Op.like]: `%${keyword}%` } }, ]; } const { count, rows } = await InventoryPlan.findAndCountAll({ where, include: [ { model: require('../models/User'), as: 'Creator', attributes: ['userId', 'username', 'realName'], }, ], order: [['createdAt', 'DESC']], limit: parseInt(pageSize), offset: parseInt(offset), }); res.json({ plans: rows, total: count, page: parseInt(page), pageSize: parseInt(pageSize), }); } catch (error) { res.status(500).json({ error: error.message }); } }); router.get('/plans/:planId', async (req, res) => { try { const plan = await InventoryPlan.findByPk(req.params.planId, { include: [ { model: require('../models/User'), as: 'Creator', attributes: ['userId', 'username', 'realName'], }, ], }); if (!plan) { return res.status(404).json({ error: '盘点计划不存在' }); } const tasks = await InventoryTask.findAll({ where: { planId: plan.planId }, include: [ { model: require('../models/User'), as: 'Assignee', attributes: ['userId', 'username', 'realName'], }, ], order: [['createdAt', 'ASC']], }); res.json({ plan, tasks }); } catch (error) { res.status(500).json({ error: error.message }); } }); router.post('/plans', async (req, res) => { try { const { name, type, description, scheduledDate, targetRooms, targetRacks } = req.body; const plan = await InventoryPlan.create({ planId: generatePlanId(), name, type: type || 'full', description, scheduledDate: scheduledDate ? new Date(scheduledDate) : null, targetRooms: targetRooms || [], targetRacks: targetRacks || [], status: 'draft', createdBy: req.user?.userId, }); res.status(201).json(plan); } catch (error) { res.status(500).json({ error: error.message }); } }); router.put('/plans/:planId', async (req, res) => { try { const plan = await InventoryPlan.findByPk(req.params.planId); if (!plan) { return res.status(404).json({ error: '盘点计划不存在' }); } const { name, type, description, scheduledDate, targetRooms, targetRacks, status } = req.body; await plan.update({ name: name || plan.name, type: type || plan.type, description: description !== undefined ? description : plan.description, scheduledDate: scheduledDate ? new Date(scheduledDate) : plan.scheduledDate, targetRooms: Array.isArray(targetRooms) ? targetRooms : plan.targetRooms, targetRacks: Array.isArray(targetRacks) ? targetRacks : plan.targetRacks, status: status !== undefined ? status : plan.status, }); res.json(plan); } catch (error) { res.status(500).json({ error: error.message }); } }); router.delete('/plans/:planId', async (req, res) => { try { const plan = await InventoryPlan.findByPk(req.params.planId); if (!plan) { return res.status(404).json({ error: '盘点计划不存在' }); } await InventoryRecord.destroy({ where: { planId: plan.planId } }); await InventoryTask.destroy({ where: { planId: plan.planId } }); await plan.destroy(); res.json({ message: '盘点计划删除成功' }); } catch (error) { res.status(500).json({ error: error.message }); } }); router.post('/plans/:planId/start', async (req, res) => { try { const plan = await InventoryPlan.findByPk(req.params.planId); if (!plan) { return res.status(404).json({ error: '盘点计划不存在' }); } if (plan.status !== 'draft' && plan.status !== 'pending') { return res.status(400).json({ error: '只有草稿或待执行状态的盘点计划可以启动' }); } const targetRooms = plan.targetRooms || []; const targetRacks = plan.targetRacks || []; let allDevices = []; if (targetRacks.length > 0) { allDevices = await Device.findAll({ where: { rackId: { [Op.in]: targetRacks } }, }); } else if (targetRooms.length > 0) { const racksInRooms = await Rack.findAll({ where: { roomId: { [Op.in]: targetRooms } }, attributes: ['rackId'], }); const rackIds = racksInRooms.map(r => r.rackId); allDevices = await Device.findAll({ where: { rackId: { [Op.in]: rackIds } }, }); } else { allDevices = await Device.findAll(); } const tasksToCreate = []; const recordsToCreate = []; const taskId = generateTaskId(); tasksToCreate.push({ taskId, planId: plan.planId, targetType: 'all', targetId: 'all', targetName: '全部设备', status: 'pending', totalDevices: allDevices.length, }); for (let i = 0; i < allDevices.length; i++) { const device = allDevices[i]; recordsToCreate.push({ recordId: generateRecordId(), taskId, planId: plan.planId, deviceId: device.deviceId, deviceName: device.name, deviceType: device.type, serialNumber: device.serialNumber, rackId: device.rackId, position: device.position, status: 'pending', }); } if (tasksToCreate.length > 0) { await InventoryTask.bulkCreate(tasksToCreate, { individualHooks: false }); } if (recordsToCreate.length > 0) { // 使用 Sequelize bulkCreate 替代原始 SQL,自动参数化防止 SQL 注入 await InventoryRecord.bulkCreate(recordsToCreate, { individualHooks: false }); } await plan.update({ status: 'in_progress', totalDevices: allDevices.length, checkedDevices: 0, normalDevices: 0, abnormalDevices: 0, missedDevices: allDevices.length, }); res.json({ message: '盘点任务已启动', taskCount: tasksToCreate.length, deviceCount: allDevices.length, }); } catch (error) { console.error('启动盘点错误:', error); res.status(500).json({ error: error.message }); } }); router.get('/tasks/:taskId', async (req, res) => { try { const task = await InventoryTask.findByPk(req.params.taskId, { include: [ { model: InventoryPlan, as: 'Plan', attributes: ['planId', 'name'] }, { model: require('../models/User'), as: 'Assignee', attributes: ['userId', 'username', 'realName'], }, ], }); if (!task) { return res.status(404).json({ error: '盘点任务不存在' }); } const records = await InventoryRecord.findAll({ where: { taskId: task.taskId }, include: [ { model: Device, as: 'Device', attributes: ['deviceId', 'name', 'type', 'serialNumber', 'rackId', 'position'], }, { model: require('../models/User'), as: 'Checker', attributes: ['userId', 'username', 'realName'], }, ], }); const rackIds = [...new Set(records.map(r => r.rackId).filter(Boolean))]; const racks = await Rack.findAll({ where: { rackId: rackIds }, include: [{ model: Room, as: 'Room' }], }); const rackMap = {}; racks.forEach(r => { rackMap[r.rackId] = r; }); const enrichedRecords = records.map(record => { const rackInfo = rackMap[record.rackId]; const roomName = rackInfo?.Room?.name || ''; const rackName = rackInfo?.name || record.rackId || ''; const position = record.position || ''; return { ...record.toJSON(), displayLocation: roomName ? `${roomName} - ${rackName} - U${position}` : `${rackName} - U${position}`, }; }); res.json({ task, records: enrichedRecords }); } catch (error) { console.error('获取盘点任务记录错误:', error); res.status(500).json({ error: error.message }); } }); router.put('/tasks/:taskId', async (req, res) => { try { const task = await InventoryTask.findByPk(req.params.taskId); if (!task) { return res.status(404).json({ error: '盘点任务不存在' }); } const { assignedTo, status } = req.body; if (assignedTo !== undefined) { await task.update({ assignedTo, assignedAt: assignedTo ? new Date() : task.assignedAt, }); } if (status) { await task.update({ status, completedAt: status === 'completed' ? new Date() : null, }); } res.json(task); } catch (error) { res.status(500).json({ error: error.message }); } }); router.post('/records/:recordId/check', async (req, res) => { try { const record = await InventoryRecord.findByPk(req.params.recordId, { include: [{ model: Device, as: 'Device' }], }); if (!record) { return res.status(404).json({ error: '盘点记录不存在' }); } const { actualSerialNumber, actualRackId, actualPosition, status, remark, photoUrl } = req.body; let abnormalType = null; if (status === 'abnormal') { if (actualSerialNumber && actualSerialNumber !== record.serialNumber) { abnormalType = 'serial_mismatch'; } else if (actualRackId && actualRackId !== record.rackId) { abnormalType = 'position_mismatch'; } } else if (status === 'not_found') { abnormalType = 'device_missing'; } await record.update({ actualSerialNumber: actualSerialNumber || null, actualRackId: actualRackId || null, actualPosition: actualPosition || null, status: status || record.status, abnormalType, checkedBy: req.user?.userId, checkedAt: new Date(), remark: remark || null, photoUrl: photoUrl || null, }); const task = await InventoryTask.findByPk(record.taskId); const plan = await InventoryPlan.findByPk(record.planId); const taskRecords = await InventoryRecord.findAll({ where: { taskId: task.taskId } }); const taskStats = { totalDevices: taskRecords.length, checkedDevices: taskRecords.filter(r => r.status !== 'pending').length, normalDevices: taskRecords.filter(r => r.status === 'normal').length, abnormalDevices: taskRecords.filter(r => r.status === 'abnormal').length, }; await task.update(taskStats); const planRecords = await InventoryRecord.findAll({ where: { planId: plan.planId } }); const planStats = { totalDevices: planRecords.length, checkedDevices: planRecords.filter(r => r.status !== 'pending').length, normalDevices: planRecords.filter(r => r.status === 'normal').length, abnormalDevices: planRecords.filter(r => r.status === 'abnormal').length, missedDevices: planRecords.filter(r => r.status === 'pending').length, }; await plan.update(planStats); res.json({ record, taskStats, planStats }); } catch (error) { res.status(500).json({ error: error.message }); } }); router.get('/records', async (req, res) => { try { const { planId, taskId, status, page = 1, pageSize = 20 } = req.query; const offset = (page - 1) * pageSize; const where = {}; if (planId) { where.planId = planId; } if (taskId) { where.taskId = taskId; } if (status) { where.status = status; } const { count, rows } = await InventoryRecord.findAndCountAll({ where, include: [ { model: Device, as: 'Device', attributes: ['deviceId', 'name', 'type', 'serialNumber', 'rackId', 'position'], }, { model: require('../models/User'), as: 'Checker', attributes: ['userId', 'username', 'realName'], }, ], order: [ ['checkedAt', 'DESC'], ['createdAt', 'DESC'], ], limit: parseInt(pageSize), offset: parseInt(offset), }); res.json({ records: rows, total: count, page: parseInt(page), pageSize: parseInt(pageSize), }); } catch (error) { res.status(500).json({ error: error.message }); } }); router.post('/plans/:planId/complete', async (req, res) => { try { const plan = await InventoryPlan.findByPk(req.params.planId); if (!plan) { return res.status(404).json({ error: '盘点计划不存在' }); } await InventoryRecord.update( { status: 'missed' }, { where: { planId: plan.planId, status: 'pending' } } ); const finalRecords = await InventoryRecord.findAll({ where: { planId: plan.planId } }); await plan.update({ status: 'completed', completedDate: new Date(), checkedDevices: finalRecords.filter(r => r.status !== 'pending').length, normalDevices: finalRecords.filter(r => r.status === 'normal').length, abnormalDevices: finalRecords.filter(r => r.status === 'abnormal').length, missedDevices: finalRecords.filter(r => r.status === 'missed').length, }); await InventoryTask.update( { status: 'completed' }, { where: { planId: plan.planId, status: { [Op.ne]: 'completed' } } } ); res.json({ message: '盘点已完成', plan }); } catch (error) { res.status(500).json({ error: error.message }); } }); router.get('/stats/dashboard', async (req, res) => { try { const totalPlans = await InventoryPlan.count(); const completedPlans = await InventoryPlan.count({ where: { status: 'completed' } }); const inProgressPlans = await InventoryPlan.count({ where: { status: 'in_progress' } }); const totalRecords = await InventoryRecord.count(); const normalRecords = await InventoryRecord.count({ where: { status: 'normal' } }); const abnormalRecords = await InventoryRecord.count({ where: { status: 'abnormal' } }); const pendingRecords = await InventoryRecord.count({ where: { status: 'pending' } }); const recentPlans = await InventoryPlan.findAll({ limit: 5, order: [['createdAt', 'DESC']], include: [ { model: require('../models/User'), as: 'Creator', attributes: ['userId', 'username', 'realName'], }, ], }); res.json({ totalPlans, completedPlans, inProgressPlans, totalRecords, normalRecords, abnormalRecords, pendingRecords, completionRate: totalRecords > 0 ? (((normalRecords + abnormalRecords) / totalRecords) * 100).toFixed(1) : 0, abnormalRate: totalRecords > 0 ? ((abnormalRecords / totalRecords) * 100).toFixed(1) : 0, recentPlans, }); } catch (error) { res.status(500).json({ error: error.message }); } }); router.post('/quick-add-device', async (req, res) => { try { const { taskId, planId, serialNumber, SN, deviceName, name, deviceType, type, roomId, rackId, position, model, brand, height, powerConsumption, ipAddress, purchaseDate, warrantyExpiry, description, remark, ...restFields } = req.body; // 字段名映射:优先使用数据库字段名,其次使用默认字段名 const finalSerialNumber = serialNumber || SN; const finalDeviceName = name || deviceName; const finalDeviceType = type || deviceType; if (!planId || !finalSerialNumber) { return res.status(400).json({ error: '缺少必要参数:planId, serialNumber' }); } const plan = await InventoryPlan.findByPk(planId); if (!plan) { return res.status(404).json({ error: '盘点计划不存在' }); } const existingDevice = await Device.findOne({ where: { serialNumber: finalSerialNumber } }); if (existingDevice) { return res .status(400) .json({ error: '该序列号的设备已存在于设备管理中', deviceId: existingDevice.deviceId }); } const existingPending = await PendingDevice.findOne({ where: { serialNumber: finalSerialNumber, status: 'pending' }, }); if (existingPending) { return res .status(400) .json({ error: '该序列号的设备已在暂存列表中', pendingId: existingPending.pendingId }); } const pendingId = `PEND${Date.now().toString(36).toUpperCase()}${Math.random().toString(36).substr(2, 4).toUpperCase()}`; // 只有当用户没有填写设备名称时,才使用默认名称 const finalName = finalDeviceName && finalDeviceName.trim() !== '' ? finalDeviceName.trim() : `新设备-${finalSerialNumber.slice(-6)}`; const pendingDevice = await PendingDevice.create({ pendingId, serialNumber: finalSerialNumber, deviceName: finalName, deviceType: finalDeviceType || 'other', roomId: roomId || null, rackId: rackId || null, position: position || null, model: model || null, brand: brand || null, height: height || 1, powerConsumption: powerConsumption || 0, ipAddress: ipAddress || null, purchaseDate: purchaseDate ? new Date(purchaseDate) : null, warrantyExpiry: warrantyExpiry ? new Date(warrantyExpiry) : null, description: description || null, customFields: Object.keys(restFields).length > 0 ? restFields : null, planId, taskId: taskId || null, createdBy: req.user?.userId, status: 'pending', remark: remark || '盘点时快速添加', }); res.status(201).json({ message: '设备已暂存,请前往暂存设备页面完善信息后同步', pendingDevice, }); } catch (error) { console.error('快速添加设备错误:', error); res.status(500).json({ error: error.message }); } }); router.get('/pending-devices', async (req, res) => { try { const { status, planId, roomId, keyword, page = 1, pageSize = PAGINATION.DEFAULT_PAGE_SIZE, } = req.query; const offset = (page - 1) * pageSize; const where = {}; if (status) { where.status = status; } if (planId) { where.planId = planId; } if (roomId) { where.roomId = roomId; } if (keyword) { where[Op.or] = [ { serialNumber: { [Op.like]: `%${keyword}%` } }, { deviceName: { [Op.like]: `%${keyword}%` } }, ]; } const { count, rows } = await PendingDevice.findAndCountAll({ where, include: [ { model: User, as: 'Creator', attributes: ['userId', 'username', 'realName'] }, { model: User, as: 'Syncer', attributes: ['userId', 'username', 'realName'] }, { model: InventoryPlan, as: 'Plan', attributes: ['planId', 'name'] }, { model: Room, as: 'Room', attributes: ['roomId', 'name'] }, { model: Rack, as: 'Rack', attributes: ['rackId', 'name'] }, ], order: [['createdAt', 'DESC']], limit: parseInt(pageSize), offset: parseInt(offset), }); res.json({ pendingDevices: rows, total: count, page: parseInt(page), pageSize: parseInt(pageSize), }); } catch (error) { console.error('获取暂存设备列表错误:', error); res.status(500).json({ error: error.message }); } }); router.get('/pending-devices/stats', async (req, res) => { try { const total = await PendingDevice.count(); const pending = await PendingDevice.count({ where: { status: 'pending' } }); const synced = await PendingDevice.count({ where: { status: 'synced' } }); res.json({ total, pending, synced }); } catch (error) { res.status(500).json({ error: error.message }); } }); router.get('/pending-devices/:pendingId', async (req, res) => { try { const pendingDevice = await PendingDevice.findByPk(req.params.pendingId, { include: [ { model: User, as: 'Creator', attributes: ['userId', 'username', 'realName'] }, { model: User, as: 'Syncer', attributes: ['userId', 'username', 'realName'] }, { model: InventoryPlan, as: 'Plan', attributes: ['planId', 'name'] }, { model: Room, as: 'Room', attributes: ['roomId', 'name'] }, { model: Rack, as: 'Rack', attributes: ['rackId', 'name'] }, ], }); if (!pendingDevice) { return res.status(404).json({ error: '暂存设备不存在' }); } res.json(pendingDevice); } catch (error) { res.status(500).json({ error: error.message }); } }); router.put('/pending-devices/:pendingId', async (req, res) => { try { const pendingDevice = await PendingDevice.findByPk(req.params.pendingId); if (!pendingDevice) { return res.status(404).json({ error: '暂存设备不存在' }); } if (pendingDevice.status === 'synced') { return res.status(400).json({ error: '已同步的设备无法修改' }); } const { deviceName, deviceType, roomId, rackId, position, model, brand, height, powerConsumption, ipAddress, purchaseDate, warrantyExpiry, description, remark, ...restFields } = req.body; const updateData = { deviceName: deviceName !== undefined ? deviceName : pendingDevice.deviceName, deviceType: deviceType !== undefined ? deviceType : pendingDevice.deviceType, roomId: roomId !== undefined ? roomId : pendingDevice.roomId, rackId: rackId !== undefined ? rackId : pendingDevice.rackId, position: position !== undefined ? position : pendingDevice.position, model: model !== undefined ? model : pendingDevice.model, brand: brand !== undefined ? brand : pendingDevice.brand, height: height !== undefined ? height : pendingDevice.height, powerConsumption: powerConsumption !== undefined ? powerConsumption : pendingDevice.powerConsumption, ipAddress: ipAddress !== undefined ? ipAddress : pendingDevice.ipAddress, purchaseDate: purchaseDate !== undefined ? purchaseDate ? new Date(purchaseDate) : null : pendingDevice.purchaseDate, warrantyExpiry: warrantyExpiry !== undefined ? warrantyExpiry ? new Date(warrantyExpiry) : null : pendingDevice.warrantyExpiry, description: description !== undefined ? description : pendingDevice.description, remark: remark !== undefined ? remark : pendingDevice.remark, }; if (Object.keys(restFields).length > 0) { const existingCustomFields = pendingDevice.customFields || {}; updateData.customFields = { ...existingCustomFields, ...restFields }; } await pendingDevice.update(updateData); res.json(pendingDevice); } catch (error) { res.status(500).json({ error: error.message }); } }); router.delete('/pending-devices/:pendingId', async (req, res) => { try { const pendingDevice = await PendingDevice.findByPk(req.params.pendingId); if (!pendingDevice) { return res.status(404).json({ error: '暂存设备不存在' }); } await pendingDevice.destroy(); res.json({ message: '删除成功' }); } catch (error) { res.status(500).json({ error: error.message }); } }); router.post('/pending-devices/:pendingId/sync', async (req, res) => { try { const pendingDevice = await PendingDevice.findByPk(req.params.pendingId); if (!pendingDevice) { return res.status(404).json({ error: '暂存设备不存在' }); } if (pendingDevice.status === 'synced') { return res.status(400).json({ error: '该设备已同步' }); } const existingDevice = await Device.findOne({ where: { serialNumber: pendingDevice.serialNumber }, }); if (existingDevice) { return res.status(400).json({ error: '该序列号的设备已存在于设备管理中' }); } const devices = await Device.findAll({ where: { deviceId: { [Op.like]: 'DEV%' } } }); let maxNumber = 0; devices.forEach(device => { const match = device.deviceId.match(/^DEV(\d+)$/); if (match) { const num = parseInt(match[1], 10); if (num > maxNumber) { maxNumber = num; } } }); const deviceId = `DEV${String(maxNumber + 1).padStart(3, '0')}`; const newDevice = await Device.create({ deviceId, name: pendingDevice.deviceName, type: pendingDevice.deviceType, serialNumber: pendingDevice.serialNumber, rackId: pendingDevice.rackId, position: pendingDevice.position, height: pendingDevice.height || 1, powerConsumption: pendingDevice.powerConsumption || 0, model: pendingDevice.model, ipAddress: pendingDevice.ipAddress, description: pendingDevice.description, purchaseDate: pendingDevice.purchaseDate, warrantyExpiry: pendingDevice.warrantyExpiry, customFields: pendingDevice.customFields, status: 'running', }); await pendingDevice.update({ status: 'synced', syncedAt: new Date(), syncedBy: req.user?.userId, syncedDeviceId: newDevice.deviceId, }); res.json({ message: '同步成功', device: newDevice, pendingDevice, }); } catch (error) { console.error('同步设备错误:', error); res.status(500).json({ error: error.message }); } }); router.post('/pending-devices/batch-sync', async (req, res) => { try { const { pendingIds } = req.body; if (!pendingIds || pendingIds.length === 0) { return res.status(400).json({ error: '请选择要同步的设备' }); } const pendingDevices = await PendingDevice.findAll({ where: { pendingId: { [Op.in]: pendingIds }, status: 'pending', }, }); if (pendingDevices.length === 0) { return res.status(400).json({ error: '没有可同步的设备' }); } const devices = await Device.findAll({ where: { deviceId: { [Op.like]: 'DEV%' } } }); let maxNumber = 0; devices.forEach(device => { const match = device.deviceId.match(/^DEV(\d+)$/); if (match) { const num = parseInt(match[1], 10); if (num > maxNumber) { maxNumber = num; } } }); const results = []; const errors = []; for (const pending of pendingDevices) { try { const existingDevice = await Device.findOne({ where: { serialNumber: pending.serialNumber }, }); if (existingDevice) { errors.push({ pendingId: pending.pendingId, serialNumber: pending.serialNumber, error: '序列号已存在', }); continue; } maxNumber++; const deviceId = `DEV${String(maxNumber).padStart(3, '0')}`; const newDevice = await Device.create({ deviceId, name: pending.deviceName, type: pending.deviceType, serialNumber: pending.serialNumber, rackId: pending.rackId, position: pending.position, height: pending.height || 1, powerConsumption: pending.powerConsumption || 0, model: pending.model, ipAddress: pending.ipAddress, description: pending.description, purchaseDate: pending.purchaseDate, warrantyExpiry: pending.warrantyExpiry, customFields: pending.customFields, status: 'running', }); await pending.update({ status: 'synced', syncedAt: new Date(), syncedBy: req.user?.userId, syncedDeviceId: newDevice.deviceId, }); results.push({ pendingId: pending.pendingId, deviceId: newDevice.deviceId }); } catch (err) { errors.push({ pendingId: pending.pendingId, error: err.message }); } } res.json({ message: `成功同步 ${results.length} 台设备`, successCount: results.length, errorCount: errors.length, results, errors, }); } catch (error) { console.error('批量同步设备错误:', error); res.status(500).json({ error: error.message }); } }); module.exports = router;