let PrismaClient try { ;({ PrismaClient } = require('@prisma/client')) } catch { ;({ PrismaClient } = require('/app/node_modules/@prisma/client')) } const prisma = new PrismaClient() const TEST_TAG = '[测试数据]' const locationSeeds = [ { key: 'hq', name: '总部机房', code: 'HQ01', description: `${TEST_TAG} 总部核心设备区` }, { key: 'office', name: '行政办公室', code: 'OF01', description: `${TEST_TAG} 行政办公区` }, { key: 'service', name: '客服办公区', code: 'CS01', description: `${TEST_TAG} 客服与运营办公区` }, { key: 'warehouse', name: '仓储维修间', code: 'WH01', description: `${TEST_TAG} 仓储和维修测试区` } ] const departmentNameMap = { management: '总经理室', opsA: '运营中心A', service: '客服中心', warehouse: '仓储中心', finance: '财务部' } const personDeptKeywordMap = { manager: '总经理室', ops: '运营中心A', service: '客服中心', warehouse: '仓储中心', finance: '财务' } const dayMs = 24 * 60 * 60 * 1000 const dateOffset = (days) => new Date(Date.now() + days * dayMs) const normalizeShortName = (value) => { const normalized = String(value || '').trim().toUpperCase() return normalized.replace(/[^A-Z0-9]/g, '') } const buildEquipmentCode = (shortName, locationId, sequence) => `${normalizeShortName(shortName)}-${String(locationId).padStart(4, '0')}-${String(sequence).padStart(5, '0')}` const calculateValues = (unitPrice, totalQuantity, remainingQuantity) => ({ total_value: Number(unitPrice) * Number(totalQuantity), borrowed_value: Number(unitPrice) * (Number(totalQuantity) - Number(remainingQuantity)), available_value: Number(unitPrice) * Number(remainingQuantity) }) const ensureLocation = async (seed) => { const existing = (await prisma.storageLocation.findFirst({ where: { code: seed.code } })) || (await prisma.storageLocation.findFirst({ where: { name: seed.name } })) if (existing) { return prisma.storageLocation.update({ where: { id: existing.id }, data: { name: seed.name, code: seed.code, status: 'active', description: seed.description } }) } return prisma.storageLocation.create({ data: { name: seed.name, code: seed.code, status: 'active', description: seed.description } }) } const ensureEquipment = async (seed) => { const values = calculateValues(seed.unit_price, seed.total_quantity, seed.remaining_quantity) const existing = await prisma.equipment.findUnique({ where: { equipment_code: seed.equipment_code } }) const data = { equipment_code: seed.equipment_code, equipment_name: seed.equipment_name, equipment_model: seed.equipment_model, equipment_type: seed.equipment_type, code_generation_mode: 'manual', status: seed.status, total_quantity: seed.total_quantity, remaining_quantity: seed.remaining_quantity, unit_price: seed.unit_price, total_value: values.total_value, borrowed_value: values.borrowed_value, available_value: values.available_value, total_value_with_children: seed.total_value_with_children || values.total_value, description: seed.description, purchase_date: seed.purchase_date, supplier: seed.supplier, storage_location_id: seed.storage_location_id, department_id: seed.department_id, asset_type_id: seed.asset_type_id, parent_id: seed.parent_id || null, mount_status: seed.mount_status || 'none' } if (existing) { return prisma.equipment.update({ where: { id: existing.id }, data }) } return prisma.equipment.create({ data }) } const ensureBorrowRecord = async (seed) => { const existing = await prisma.borrowRecord.findFirst({ where: { equipment_code: seed.equipment_code, person_id: seed.person_id, status: seed.status, notes: { contains: TEST_TAG } } }) if (existing) { return existing } return prisma.borrowRecord.create({ data: seed }) } const ensureMaintenanceRecord = async (seed) => { const existing = await prisma.maintenanceRecord.findFirst({ where: { equipment_code: seed.equipment_code, issue_description: seed.issue_description } }) if (existing) { return prisma.maintenanceRecord.update({ where: { id: existing.id }, data: seed }) } return prisma.maintenanceRecord.create({ data: seed }) } const ensureDisposalRecord = async (seed) => { const existing = await prisma.disposalRecord.findFirst({ where: { equipment_code: seed.equipment_code, disposal_reason: seed.disposal_reason } }) if (existing) { return prisma.disposalRecord.update({ where: { id: existing.id }, data: seed }) } return prisma.disposalRecord.create({ data: seed }) } const ensureMountHistory = async (seed) => { const existing = await prisma.mountHistory.findFirst({ where: { child_id: seed.child_id, parent_id: seed.parent_id, operation_type: seed.operation_type, notes: seed.notes } }) if (existing) { return existing } return prisma.mountHistory.create({ data: seed }) } const ensureOperationLog = async (seed) => { const existing = await prisma.operationLog.findFirst({ where: { operation_type: seed.operation_type, equipment_code: seed.equipment_code || null, operation_details: seed.operation_details } }) if (existing) { return existing } return prisma.operationLog.create({ data: seed }) } async function main() { const admin = await prisma.user.findFirst({ where: { username: 'admin' }, select: { id: true, username: true, name: true } }) if (!admin) { throw new Error('未找到 admin 用户,无法写入测试数据') } const assetTypeNormalization = [ { id: 1, name: '主机', short_name: 'PC' }, { id: 2, name: 'cpu', short_name: 'CPU' }, { id: 3, name: '其他外设', short_name: 'QT' }, { id: 4, name: '内存条', short_name: 'NCT' }, { id: 5, name: '显卡', short_name: 'XK' }, { id: 6, name: '显示器', short_name: 'XSQ' }, { id: 7, name: '硬盘', short_name: 'YP' }, { id: 8, name: '笔记本', short_name: 'PC' } ] for (const item of assetTypeNormalization) { await prisma.assetType.update({ where: { id: item.id }, data: { name: item.name, short_name: item.short_name, status: 'active' } }) } const assetTypes = await prisma.assetType.findMany({ select: { id: true, name: true, short_name: true, can_be_parent: true, can_be_child: true } }) const assetTypeMap = new Map(assetTypes.map((item) => [item.name, item])) const requiredTypes = ['主机', 'cpu', '其他外设', '内存条', '显卡', '显示器', '硬盘', '笔记本'] for (const typeName of requiredTypes) { if (!assetTypeMap.has(typeName)) { throw new Error(`缺少资产类型:${typeName}`) } } await prisma.assetType.updateMany({ where: { name: { in: ['主机', '笔记本'] } }, data: { can_be_parent: true } }) await prisma.assetType.updateMany({ where: { name: { in: ['cpu', '其他外设', '内存条', '显卡', '硬盘'] } }, data: { can_be_child: true } }) const locationMap = new Map() for (const seed of locationSeeds) { const location = await ensureLocation(seed) locationMap.set(seed.key, location) } const departmentMap = new Map() for (const [key, name] of Object.entries(departmentNameMap)) { const department = await prisma.department.findFirst({ where: { status: 'active', name } }) if (!department) { throw new Error(`未找到启用部门:${name}`) } departmentMap.set(key, department) } const personMap = new Map() for (const [key, keyword] of Object.entries(personDeptKeywordMap)) { const person = await prisma.person.findFirst({ where: { status: 'active', department: { contains: keyword } }, orderBy: { id: 'asc' } }) if (!person) { throw new Error(`未找到可用测试人员,部门关键字:${keyword}`) } personMap.set(key, person) } const siteConfigCount = await prisma.siteConfig.count() if (siteConfigCount === 0) { await prisma.siteConfig.create({ data: { site_name: '简尚固定资产管理系统', theme_color: '#2563eb', updated_by: admin.id } }) } const code = (typeName, locationKey, sequence) => buildEquipmentCode( assetTypeMap.get(typeName).short_name, locationMap.get(locationKey).id, sequence ) const equipmentSeeds = [ { equipment_code: code('主机', 'hq', 1), equipment_name: '研发部测试主机', equipment_model: 'ThinkCentre M900', equipment_type: '主机', status: '可用', total_quantity: 1, remaining_quantity: 1, unit_price: 5899, description: `${TEST_TAG} 用于二维码打印、挂载测试的主机`, purchase_date: new Date('2025-01-15'), supplier: '联想授权渠道', storage_location_id: locationMap.get('hq').id, department_id: departmentMap.get('opsA').id, asset_type_id: assetTypeMap.get('主机').id }, { equipment_code: code('主机', 'service', 1), equipment_name: '客服前台主机', equipment_model: 'Dell OptiPlex 7090', equipment_type: '主机', status: '已借出', total_quantity: 1, remaining_quantity: 0, unit_price: 4680, description: `${TEST_TAG} 当前借出状态演示`, purchase_date: new Date('2025-02-20'), supplier: '戴尔企业采购', storage_location_id: locationMap.get('service').id, department_id: departmentMap.get('service').id, asset_type_id: assetTypeMap.get('主机').id }, { equipment_code: code('笔记本', 'office', 1), equipment_name: '管理层备用笔记本', equipment_model: 'ThinkPad T14', equipment_type: '笔记本', status: '已借出', total_quantity: 1, remaining_quantity: 0, unit_price: 6999, description: `${TEST_TAG} 长期借用演示`, purchase_date: new Date('2025-03-18'), supplier: '联想官方商城', storage_location_id: locationMap.get('office').id, department_id: departmentMap.get('management').id, asset_type_id: assetTypeMap.get('笔记本').id }, { equipment_code: code('笔记本', 'office', 2), equipment_name: '财务外勤笔记本', equipment_model: 'HP ProBook 440', equipment_type: '笔记本', status: '可用', total_quantity: 1, remaining_quantity: 1, unit_price: 5299, description: `${TEST_TAG} 可借用笔记本`, purchase_date: new Date('2024-12-01'), supplier: '惠普商用渠道', storage_location_id: locationMap.get('office').id, department_id: departmentMap.get('finance').id, asset_type_id: assetTypeMap.get('笔记本').id }, { equipment_code: code('显示器', 'service', 1), equipment_name: '客服工位显示器', equipment_model: 'AOC 24B2XH', equipment_type: '显示器', status: '维护中', total_quantity: 1, remaining_quantity: 1, unit_price: 899, description: `${TEST_TAG} 维修流程演示`, purchase_date: new Date('2024-11-06'), supplier: 'AOC 官方旗舰店', storage_location_id: locationMap.get('service').id, department_id: departmentMap.get('service').id, asset_type_id: assetTypeMap.get('显示器').id }, { equipment_code: code('显示器', 'office', 1), equipment_name: '财务双屏显示器', equipment_model: 'Dell P2422H', equipment_type: '显示器', status: '可用', total_quantity: 1, remaining_quantity: 1, unit_price: 1199, description: `${TEST_TAG} 正常在库显示器`, purchase_date: new Date('2025-01-08'), supplier: '戴尔企业采购', storage_location_id: locationMap.get('office').id, department_id: departmentMap.get('finance').id, asset_type_id: assetTypeMap.get('显示器').id }, { equipment_code: code('其他外设', 'warehouse', 1), equipment_name: '标签打印机', equipment_model: 'Xprinter XP-H500E', equipment_type: '其他外设', status: '待报废', total_quantity: 1, remaining_quantity: 1, unit_price: 899, description: `${TEST_TAG} 报废流程演示设备`, purchase_date: new Date('2023-08-05'), supplier: '芯烨官方店', storage_location_id: locationMap.get('warehouse').id, department_id: departmentMap.get('warehouse').id, asset_type_id: assetTypeMap.get('其他外设').id }, { equipment_code: code('其他外设', 'office', 1), equipment_name: '财务扫描枪', equipment_model: 'Honeywell 1470g', equipment_type: '其他外设', status: '可用', total_quantity: 1, remaining_quantity: 1, unit_price: 560, description: `${TEST_TAG} 已完成维修历史演示`, purchase_date: new Date('2024-06-11'), supplier: '霍尼韦尔代理商', storage_location_id: locationMap.get('office').id, department_id: departmentMap.get('finance').id, asset_type_id: assetTypeMap.get('其他外设').id }, { equipment_code: code('硬盘', 'warehouse', 1), equipment_name: '淘汰机械硬盘', equipment_model: 'Seagate 1TB HDD', equipment_type: '硬盘', status: '已报废', total_quantity: 1, remaining_quantity: 1, unit_price: 260, description: `${TEST_TAG} 已完成报废演示设备`, purchase_date: new Date('2022-02-10'), supplier: '希捷经销商', storage_location_id: locationMap.get('warehouse').id, department_id: departmentMap.get('warehouse').id, asset_type_id: assetTypeMap.get('硬盘').id }, { equipment_code: code('cpu', 'hq', 1), equipment_name: '主机CPU', equipment_model: 'Intel i5-12400', equipment_type: 'cpu', status: '可用', total_quantity: 1, remaining_quantity: 1, unit_price: 1380, description: `${TEST_TAG} 已挂载到研发部测试主机`, purchase_date: new Date('2025-01-15'), supplier: '英特尔代理', storage_location_id: locationMap.get('hq').id, department_id: departmentMap.get('opsA').id, asset_type_id: assetTypeMap.get('cpu').id, mount_status: 'mounted' }, { equipment_code: code('内存条', 'hq', 1), equipment_name: '主机内存', equipment_model: 'Kingston DDR4 16GB', equipment_type: '内存条', status: '可用', total_quantity: 1, remaining_quantity: 1, unit_price: 320, description: `${TEST_TAG} 已挂载到研发部测试主机`, purchase_date: new Date('2025-01-15'), supplier: '金士顿经销商', storage_location_id: locationMap.get('hq').id, department_id: departmentMap.get('opsA').id, asset_type_id: assetTypeMap.get('内存条').id, mount_status: 'mounted' }, { equipment_code: code('显卡', 'hq', 1), equipment_name: '主机显卡', equipment_model: 'NVIDIA T400', equipment_type: '显卡', status: '可用', total_quantity: 1, remaining_quantity: 1, unit_price: 980, description: `${TEST_TAG} 已挂载到研发部测试主机`, purchase_date: new Date('2025-01-15'), supplier: '英伟达代理', storage_location_id: locationMap.get('hq').id, department_id: departmentMap.get('opsA').id, asset_type_id: assetTypeMap.get('显卡').id, mount_status: 'mounted' }, { equipment_code: code('硬盘', 'hq', 1), equipment_name: '主机固态硬盘', equipment_model: 'Samsung PM9A1 1TB', equipment_type: '硬盘', status: '可用', total_quantity: 1, remaining_quantity: 1, unit_price: 699, description: `${TEST_TAG} 已挂载到研发部测试主机`, purchase_date: new Date('2025-01-15'), supplier: '三星渠道', storage_location_id: locationMap.get('hq').id, department_id: departmentMap.get('opsA').id, asset_type_id: assetTypeMap.get('硬盘').id, mount_status: 'mounted' }, { equipment_code: code('其他外设', 'service', 2), equipment_name: '客服键鼠套装', equipment_model: 'Logitech MK270', equipment_type: '其他外设', status: '可用', total_quantity: 1, remaining_quantity: 1, unit_price: 149, description: `${TEST_TAG} 已完成借还历史演示`, purchase_date: new Date('2025-02-01'), supplier: '罗技旗舰店', storage_location_id: locationMap.get('service').id, department_id: departmentMap.get('service').id, asset_type_id: assetTypeMap.get('其他外设').id }, { equipment_code: code('其他外设', 'office', 2), equipment_name: '会议摄像头', equipment_model: 'Logitech C930e', equipment_type: '其他外设', status: '可用', total_quantity: 1, remaining_quantity: 1, unit_price: 799, description: `${TEST_TAG} 正常在库会议设备`, purchase_date: new Date('2024-10-09'), supplier: '罗技企业采购', storage_location_id: locationMap.get('office').id, department_id: departmentMap.get('management').id, asset_type_id: assetTypeMap.get('其他外设').id } ] const equipmentMap = new Map() for (const seed of equipmentSeeds) { const equipment = await ensureEquipment(seed) equipmentMap.set(seed.equipment_code, equipment) } const parentHost = equipmentMap.get(code('主机', 'hq', 1)) const mountedChildren = [ equipmentMap.get(code('cpu', 'hq', 1)), equipmentMap.get(code('内存条', 'hq', 1)), equipmentMap.get(code('显卡', 'hq', 1)), equipmentMap.get(code('硬盘', 'hq', 1)) ] for (const child of mountedChildren) { await prisma.equipment.update({ where: { id: child.id }, data: { parent_id: parentHost.id, mount_status: 'mounted' } }) await ensureMountHistory({ child_id: child.id, parent_id: parentHost.id, operation_type: 'mount', operated_by: admin.id, notes: `${TEST_TAG} 初始化挂载` }) } const childValue = mountedChildren.reduce((sum, item) => sum + Number(item.unit_price), 0) await prisma.equipment.update({ where: { id: parentHost.id }, data: { total_value_with_children: Number(parentHost.unit_price) + childValue } }) const borrowedHost = equipmentMap.get(code('主机', 'service', 1)) const borrowedLaptop = equipmentMap.get(code('笔记本', 'office', 1)) const historicalPeripheral = equipmentMap.get(code('其他外设', 'service', 2)) const maintenanceMonitor = equipmentMap.get(code('显示器', 'service', 1)) const disposedDisk = equipmentMap.get(code('硬盘', 'warehouse', 1)) const disposalPrinter = equipmentMap.get(code('其他外设', 'warehouse', 1)) const maintainedScanner = equipmentMap.get(code('其他外设', 'office', 1)) await ensureBorrowRecord({ equipment_id: borrowedHost.id, equipment_code: borrowedHost.equipment_code, person_id: personMap.get('service').id, person_name: personMap.get('service').real_name, person_nickname: personMap.get('service').nickname, person_identifier: personMap.get('service').id_number, borrow_time: dateOffset(-12), expected_return_time: dateOffset(3), is_long_term: false, return_time: null, status: '已借出', return_condition: '正常', notes: `${TEST_TAG} 客服工位借用中` }) await ensureBorrowRecord({ equipment_id: borrowedLaptop.id, equipment_code: borrowedLaptop.equipment_code, person_id: personMap.get('manager').id, person_name: personMap.get('manager').real_name, person_nickname: personMap.get('manager').nickname, person_identifier: personMap.get('manager').id_number, borrow_time: dateOffset(-25), expected_return_time: null, is_long_term: true, return_time: null, status: '已借出', return_condition: '正常', notes: `${TEST_TAG} 管理层长期领用` }) await ensureBorrowRecord({ equipment_id: historicalPeripheral.id, equipment_code: historicalPeripheral.equipment_code, person_id: personMap.get('ops').id, person_name: personMap.get('ops').real_name, person_nickname: personMap.get('ops').nickname, person_identifier: personMap.get('ops').id_number, borrow_time: dateOffset(-40), expected_return_time: dateOffset(-33), is_long_term: false, return_time: dateOffset(-31), status: '已归还', return_condition: '正常', notes: `${TEST_TAG} 已归还历史记录` }) await ensureMaintenanceRecord({ equipment_id: maintenanceMonitor.id, equipment_code: maintenanceMonitor.equipment_code, issue_description: `${TEST_TAG} 屏幕闪烁,间歇性黑屏`, repair_vendor: '常州本地维保点', estimated_cost: 180, actual_cost: null, start_date: dateOffset(-2), expected_end_date: dateOffset(5), actual_end_date: null, status: 'in_progress', repair_result: null, created_by: admin.id }) await ensureMaintenanceRecord({ equipment_id: maintainedScanner.id, equipment_code: maintainedScanner.equipment_code, issue_description: `${TEST_TAG} 扫描头识别异常`, repair_vendor: '苏州售后中心', estimated_cost: 120, actual_cost: 100, start_date: dateOffset(-18), expected_end_date: dateOffset(-12), actual_end_date: dateOffset(-13), status: 'completed', repair_result: '更换扫描头后恢复正常', created_by: admin.id }) await ensureDisposalRecord({ equipment_id: disposalPrinter.id, equipment_code: disposalPrinter.equipment_code, disposal_reason: `${TEST_TAG} 主板老化,维修成本高于残值`, disposal_method: '报废', estimated_value: 80, actual_value: null, status: 'pending', applied_by: admin.id, applied_at: dateOffset(-1), approved_by: null, approved_at: null, completed_at: null, notes: `${TEST_TAG} 待审批报废单` }) await ensureDisposalRecord({ equipment_id: disposedDisk.id, equipment_code: disposedDisk.equipment_code, disposal_reason: `${TEST_TAG} 盘体异响且坏道严重`, disposal_method: '报废', estimated_value: 20, actual_value: 0, status: 'completed', applied_by: admin.id, applied_at: dateOffset(-30), approved_by: admin.id, approved_at: dateOffset(-28), completed_at: dateOffset(-25), notes: `${TEST_TAG} 已完成报废处置` }) await ensureOperationLog({ user_id: admin.id, username: admin.username, operation_type: '初始化测试数据', equipment_code: null, equipment_id: null, operation_details: `${TEST_TAG} 写入演示资产、借用、维修、报废数据`, ip_address: null }) await ensureOperationLog({ user_id: admin.id, username: admin.username, operation_type: '初始化挂载关系', equipment_code: parentHost.equipment_code, equipment_id: parentHost.id, operation_details: `${TEST_TAG} 为主机挂载 CPU/内存/显卡/硬盘`, ip_address: null }) const summary = { storage_locations: await prisma.storageLocation.count(), equipments: await prisma.equipment.count(), borrow_records: await prisma.borrowRecord.count(), maintenance_records: await prisma.maintenanceRecord.count(), disposal_records: await prisma.disposalRecord.count(), mounted_children: await prisma.equipment.count({ where: { parent_id: parentHost.id } }) } console.log(JSON.stringify(summary, null, 2)) } main() .catch((error) => { console.error(error) process.exitCode = 1 }) .finally(async () => { await prisma.$disconnect() })