/** * 一次性迁移脚本:为现有员工创建初始版本记录 * 运行方式:npx tsx scripts/migrate-records.ts */ import prisma from '../src/lib/prisma.js' import { decrypt } from '../src/lib/crypto.js' function dateToMonth(date: Date): string { const y = date.getFullYear() const m = String(date.getMonth() + 1).padStart(2, '0') return `${y}-${m}` } function prevMonth(month: string): string { const [y, m] = month.split('-').map(Number) const d = new Date(y, m - 2, 1) return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}` } async function main() { const employees = await prisma.employee.findMany({ include: { terminations: { orderBy: { terminationDate: 'desc' }, take: 1 }, salaryChanges: { orderBy: { createdAt: 'desc' }, take: 1 }, socialInsRecords: { take: 1 }, housingFundRecords: { take: 1 }, departmentRecords: { take: 1 }, }, }) console.log(`Found ${employees.length} employees to migrate`) for (const emp of employees) { const hireMonth = dateToMonth(emp.hireDate) const termination = emp.terminations[0] const endMonth = termination ? dateToMonth(termination.terminationDate) : null // 解密月薪获取数值 let salaryNum = 0 try { salaryNum = parseFloat(decrypt(emp.monthlySalary)) || 0 } catch { salaryNum = parseFloat(emp.monthlySalary) || 0 } const socialInsBase = emp.socialInsBase ?? salaryNum const housingFundBase = emp.housingFundBase ?? salaryNum // 1. 社保缴费记录(仅当尚无记录时创建) if (emp.socialInsRecords.length === 0) { await prisma.employeeSocialInsRecord.create({ data: { orgId: emp.orgId, employeeId: emp.id, startMonth: emp.socialInsStartMonth || hireMonth, endMonth: endMonth || emp.socialInsEndMonth || null, base: socialInsBase, changeType: 'ONBOARDING', createdBy: emp.createdBy, }, }) } // 2. 公积金缴费记录 if (emp.housingFundRecords.length === 0) { await prisma.employeeHousingFundRecord.create({ data: { orgId: emp.orgId, employeeId: emp.id, startMonth: emp.housingFundStartMonth || hireMonth, endMonth: endMonth || emp.housingFundEndMonth || null, base: housingFundBase, changeType: 'ONBOARDING', createdBy: emp.createdBy, }, }) } // 3. 薪资变更记录(仅当尚无记录时创建) if (emp.salaryChanges.length === 0) { await prisma.salaryChangeRecord.create({ data: { orgId: emp.orgId, employeeId: emp.id, oldSalary: 0, newSalary: salaryNum, effectiveDate: emp.hireDate, effectiveMonth: hireMonth, endMonth: null, changeType: 'ONBOARDING', createdBy: emp.createdBy, }, }) } else { // 已有记录但缺少 effectiveMonth/endMonth/changeType,补充 const latest = emp.salaryChanges[0] if (!latest.effectiveMonth || !latest.changeType) { await prisma.salaryChangeRecord.update({ where: { id: latest.id }, data: { effectiveMonth: dateToMonth(latest.effectiveDate), changeType: latest.changeType || 'SALARY_CHANGE', }, }) } } // 4. 部门变更记录 if (emp.departmentRecords.length === 0) { await prisma.employeeDepartmentRecord.create({ data: { orgId: emp.orgId, employeeId: emp.id, oldDepartment: '', newDepartment: emp.department, effectiveMonth: hireMonth, endMonth: null, changeType: 'ONBOARDING', createdBy: emp.createdBy, }, }) } // 5. 同步 Employee 便捷字段 await prisma.employee.update({ where: { id: emp.id }, data: { socialInsStartMonth: emp.socialInsStartMonth || hireMonth, socialInsEndMonth: endMonth || emp.socialInsEndMonth || null, socialInsBase, housingFundStartMonth: emp.housingFundStartMonth || hireMonth, housingFundEndMonth: endMonth || emp.housingFundEndMonth || null, housingFundBase, }, }) console.log(` ✓ ${emp.name} (${emp.department}) — records created/synced`) } console.log('\nMigration complete!') } main() .catch((e) => { console.error('Migration failed:', e) process.exit(1) }) .finally(async () => { await prisma.$disconnect() })