/** * 社保公积金账户化迁移脚本 * 将旧 SocialInsuranceConfig / HousingFundConfig 迁移到 SocialAccount + SocialYearStandard * 将员工参保记录的 city 关联到 accountId * * 用法: npx tsx scripts/migrate-social-accounts.ts */ import prisma from '../src/lib/prisma' async function main() { console.log('========== 社保公积金账户化迁移 ==========') // ========== 1. 迁移社保配置 → 社保账户 + 年度标准 ========== console.log('[1/6] 迁移社保配置...') // 按 (orgId, city) 分组创建社保账户 const socialConfigs = await prisma.socialInsuranceConfig.findMany() const socialAccountMap = new Map() // key: orgId:city → accountId for (const config of socialConfigs) { const key = `${config.orgId}:${config.city}` if (socialAccountMap.has(key)) continue const account = await prisma.socialAccount.create({ data: { orgId: config.orgId, type: 'SOCIAL', name: `${config.city}社保账户`, city: config.city, isDefault: true, status: 'ACTIVE', createdBy: 'migration', }, }) socialAccountMap.set(key, account.id) console.log(` 创建社保账户: ${config.city} → ${account.id}`) } // 迁移每条 Config → YearStandard for (const config of socialConfigs) { const key = `${config.orgId}:${config.city}` const accountId = socialAccountMap.get(key)! await prisma.socialYearStandard.create({ data: { orgId: config.orgId, accountId, pensionOrg: config.pensionOrg, pensionEmp: config.pensionEmp, medicalOrg: config.medicalOrg, medicalEmp: config.medicalEmp, unemploymentOrg: config.unemploymentOrg, unemploymentEmp: config.unemploymentEmp, injuryOrg: config.injuryOrg, maternityOrg: config.maternityOrg, baseMin: config.baseMin, baseMax: config.baseMax, medicalBaseMin: config.medicalBaseMin, medicalBaseMax: config.medicalBaseMax, extraInsurances: config.extraInsurances as any, effectiveFrom: config.effectiveFrom, effectiveTo: config.effectiveTo, isCurrent: config.isCurrent, adjustmentDone: config.adjustmentDone, createdBy: config.createdBy, }, }) } console.log(` 迁移 ${socialConfigs.length} 条社保年度标准`) // ========== 2. 迁移公积金配置 → 公积金账户 + 年度标准 ========== console.log('[2/6] 迁移公积金配置...') const housingConfigs = await prisma.housingFundConfig.findMany() const housingAccountMap = new Map() // key: orgId:city:accountType → accountId for (const config of housingConfigs) { const accountType = config.accountType || 'BASIC' const key = `${config.orgId}:${config.city}:${accountType}` if (housingAccountMap.has(key)) continue const account = await prisma.socialAccount.create({ data: { orgId: config.orgId, type: 'HOUSING', name: `${config.city}${accountType === 'SUPPLEMENTARY' ? '补充' : ''}公积金账户`, city: config.city, accountType, isDefault: accountType === 'BASIC', status: 'ACTIVE', createdBy: 'migration', }, }) housingAccountMap.set(key, account.id) console.log(` 创建公积金账户: ${config.city} ${accountType} → ${account.id}`) } for (const config of housingConfigs) { const accountType = config.accountType || 'BASIC' const key = `${config.orgId}:${config.city}:${accountType}` const accountId = housingAccountMap.get(key)! await prisma.socialYearStandard.create({ data: { orgId: config.orgId, accountId, housingOrg: config.housingOrg, housingEmp: config.housingEmp, baseMin: config.baseMin, baseMax: config.baseMax, effectiveFrom: config.effectiveFrom, effectiveTo: config.effectiveTo, isCurrent: config.isCurrent, adjustmentDone: config.adjustmentDone, createdBy: config.createdBy, }, }) } console.log(` 迁移 ${housingConfigs.length} 条公积金年度标准`) // ========== 3. 迁移员工社保参保记录 accountId ========== console.log('[3/6] 迁移员工社保参保记录 accountId...') const socialRecords = await prisma.employeeSocialInsRecord.findMany({ where: { accountId: null } }) let socialUpdated = 0 for (const record of socialRecords) { const key = `${record.orgId}:${record.city}` const accountId = socialAccountMap.get(key) if (accountId) { await prisma.employeeSocialInsRecord.update({ where: { id: record.id }, data: { accountId } }) socialUpdated++ } } console.log(` 更新 ${socialUpdated}/${socialRecords.length} 条社保参保记录`) // ========== 4. 迁移员工公积金参保记录 accountId ========== console.log('[4/6] 迁移员工公积金参保记录 accountId...') const housingRecords = await prisma.employeeHousingFundRecord.findMany({ where: { accountId: null } }) let housingUpdated = 0 for (const record of housingRecords) { // 公积金默认用 BASIC 类型账户 const key = `${record.orgId}:${record.city}:BASIC` const accountId = housingAccountMap.get(key) if (accountId) { await prisma.employeeHousingFundRecord.update({ where: { id: record.id }, data: { accountId } }) housingUpdated++ } } console.log(` 更新 ${housingUpdated}/${housingRecords.length} 条公积金参保记录`) // ========== 5. 迁移月度办理记录 accountId ========== console.log('[5/6] 迁移月度办理记录 accountId...') const monthlyProcesses = await prisma.socialMonthlyProcess.findMany({ where: { accountId: null } }) let monthlyUpdated = 0 for (const proc of monthlyProcesses) { const snapshot = proc.snapshot as any const city = snapshot?.city || snapshot?.configCity if (city) { const accountMap = proc.type === 'SOCIAL' ? socialAccountMap : housingAccountMap // 社保用 orgId:city,公积金用 orgId:city:BASIC const key = proc.type === 'SOCIAL' ? `${proc.orgId}:${city}` : `${proc.orgId}:${city}:BASIC` const accountId = accountMap.get(key) if (accountId) { await prisma.socialMonthlyProcess.update({ where: { id: proc.id }, data: { accountId } }) monthlyUpdated++ } } } console.log(` 更新 ${monthlyUpdated}/${monthlyProcesses.length} 条月度办理记录`) // ========== 6. 验证数据完整性 ========== console.log('[6/6] 验证数据完整性...') const accounts = await prisma.socialAccount.count() const standards = await prisma.socialYearStandard.count() const socialWithoutAccount = await prisma.employeeSocialInsRecord.count({ where: { accountId: null } }) const housingWithoutAccount = await prisma.employeeHousingFundRecord.count({ where: { accountId: null } }) console.log(` 账户总数: ${accounts}`) console.log(` 年度标准总数: ${standards}`) console.log(` 社保参保记录无 accountId: ${socialWithoutAccount}`) console.log(` 公积金参保记录无 accountId: ${housingWithoutAccount}`) if (socialWithoutAccount > 0 || housingWithoutAccount > 0) { console.log(' ⚠️ 部分参保记录未关联账户(可能城市无对应配置),需手动处理') } console.log('') console.log('========== 迁移完成 ==========') } main() .catch((e) => { console.error('迁移失败:', e) process.exit(1) }) .finally(async () => { await prisma.$disconnect() })