GuDingZiChan/backend/scripts/seedDemoData.js

756 lines
24 KiB
JavaScript

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()
})