feat: 社保公积金独立配置+版本化缴费记录+月度增减员+补偿金批次
- Schema: 拆分社保/公积金配置,新增EmployeeSocialInsRecord/EmployeeHousingFundRecord/DepartmentRecord模型,扩展SalaryChangeRecord,增加SEVERANCE批次类型 - 后端: createEmployee/rehireEmployee接收社保公积金字段并创建缴费记录版本;createTermination/createResignation接收截止年月并关闭缴费记录;调薪/调部门API+版本记录;月度增减员API;公积金独立CRUD/计算/调基;SEVERANCE批次calcBatchEntry - 前端: AddEmployeeModal/RehireModal增加社保公积金输入;ResignModal/Termination增加截止年月+日期不一致提醒;花名册增加调薪/调部门弹窗;SocialInsurance.tsx Tab拆分(社保/公积金/月度增减员)+CSV导出;Money.tsx增加补偿金批次类型 - 修复: seed.ts移除housingOrg/housingEmp;risk.service.ts从HousingFundConfig获取公积金费率
This commit is contained in:
@@ -0,0 +1,150 @@
|
||||
/**
|
||||
* 一次性迁移脚本:为现有员工创建初始版本记录
|
||||
* 运行方式: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()
|
||||
})
|
||||
Reference in New Issue
Block a user