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:
freedakgmail
2026-07-23 20:02:59 +08:00
parent ad6800b63c
commit 4f125d309b
15 changed files with 2227 additions and 335 deletions
+184 -2
View File
@@ -6,6 +6,18 @@ function daysBetween(a: Date, b: Date): number {
return Math.floor((a.getTime() - b.getTime()) / (1000 * 60 * 60 * 24))
}
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')}`
}
export function getContractStatus(contract: {
signDate: Date | null
startDate: Date
@@ -155,12 +167,20 @@ export async function getEmployeeDetail(orgId: string, id: string) {
}
export async function createEmployee(orgId: string, userId: string, data: any) {
const hireDate = new Date(data.hireDate)
const hireMonth = dateToMonth(hireDate)
const salaryNum = Number(data.monthlySalary) || 0
const socialInsBase = data.socialInsBase != null ? Number(data.socialInsBase) : salaryNum
const housingFundBase = data.housingFundBase != null ? Number(data.housingFundBase) : salaryNum
const socialInsStartMonth = data.socialInsStartMonth || hireMonth
const housingFundStartMonth = data.housingFundStartMonth || hireMonth
const employee = await prisma.employee.create({
data: {
orgId,
name: data.name,
department: data.department,
hireDate: new Date(data.hireDate),
hireDate,
monthlySalary: encrypt(data.monthlySalary),
gender: data.gender,
phone: data.phone,
@@ -168,6 +188,65 @@ export async function createEmployee(orgId: string, userId: string, data: any) {
isPregnant: data.isPregnant || false,
isInMedicalPeriod: data.isInMedicalPeriod || false,
isWorkInjured: data.isWorkInjured || false,
socialInsBase,
housingFundBase,
socialInsStartMonth,
housingFundStartMonth,
createdBy: userId,
},
})
// 创建社保缴费记录
await prisma.employeeSocialInsRecord.create({
data: {
orgId,
employeeId: employee.id,
startMonth: socialInsStartMonth,
endMonth: null,
base: socialInsBase,
changeType: 'ONBOARDING',
createdBy: userId,
},
})
// 创建公积金缴费记录
await prisma.employeeHousingFundRecord.create({
data: {
orgId,
employeeId: employee.id,
startMonth: housingFundStartMonth,
endMonth: null,
base: housingFundBase,
changeType: 'ONBOARDING',
createdBy: userId,
},
})
// 创建初始薪资变更记录
await prisma.salaryChangeRecord.create({
data: {
orgId,
employeeId: employee.id,
oldSalary: 0,
newSalary: salaryNum,
effectiveDate: hireDate,
effectiveMonth: hireMonth,
endMonth: null,
changeType: 'ONBOARDING',
createdBy: userId,
},
})
// 创建初始部门记录
await prisma.employeeDepartmentRecord.create({
data: {
orgId,
employeeId: employee.id,
oldDepartment: '',
newDepartment: data.department,
effectiveMonth: hireMonth,
endMonth: null,
changeType: 'ONBOARDING',
createdBy: userId,
},
})
@@ -227,6 +306,38 @@ export async function rehireEmployee(orgId: string, userId: string, id: string,
throw { code: 'VALIDATION_ERROR', message: '新入职日期必须晚于上次离职/解聘日期' }
}
const newHireMonth = dateToMonth(newHireDate)
const salaryNum = Number(decrypt(employee.monthlySalary)) || 0
const socialInsBase = data.socialInsBase != null ? Number(data.socialInsBase) : salaryNum
const housingFundBase = data.housingFundBase != null ? Number(data.housingFundBase) : salaryNum
const socialInsStartMonth = data.socialInsStartMonth || newHireMonth
const housingFundStartMonth = data.housingFundStartMonth || newHireMonth
const prevHireMonth = prevMonth(newHireMonth)
// 关闭旧社保缴费记录
await prisma.employeeSocialInsRecord.updateMany({
where: { employeeId: id, endMonth: null },
data: { endMonth: prevHireMonth },
})
// 关闭旧公积金缴费记录
await prisma.employeeHousingFundRecord.updateMany({
where: { employeeId: id, endMonth: null },
data: { endMonth: prevHireMonth },
})
// 关闭旧薪资记录
await prisma.salaryChangeRecord.updateMany({
where: { employeeId: id, endMonth: null },
data: { endMonth: prevHireMonth },
})
// 关闭旧部门记录
await prisma.employeeDepartmentRecord.updateMany({
where: { employeeId: id, endMonth: null },
data: { endMonth: prevHireMonth },
})
await prisma.employee.update({
where: { id },
data: {
@@ -236,6 +347,67 @@ export async function rehireEmployee(orgId: string, userId: string, id: string,
isPregnant: false,
isInMedicalPeriod: false,
isWorkInjured: false,
socialInsBase,
housingFundBase,
socialInsStartMonth,
socialInsEndMonth: null,
housingFundStartMonth,
housingFundEndMonth: null,
},
})
// 创建新社保缴费记录
await prisma.employeeSocialInsRecord.create({
data: {
orgId,
employeeId: id,
startMonth: socialInsStartMonth,
endMonth: null,
base: socialInsBase,
changeType: 'REHIRE',
createdBy: userId,
},
})
// 创建新公积金缴费记录
await prisma.employeeHousingFundRecord.create({
data: {
orgId,
employeeId: id,
startMonth: housingFundStartMonth,
endMonth: null,
base: housingFundBase,
changeType: 'REHIRE',
createdBy: userId,
},
})
// 创建新薪资记录
await prisma.salaryChangeRecord.create({
data: {
orgId,
employeeId: id,
oldSalary: salaryNum,
newSalary: salaryNum,
effectiveDate: newHireDate,
effectiveMonth: newHireMonth,
endMonth: null,
changeType: 'REHIRE',
createdBy: userId,
},
})
// 创建新部门记录
await prisma.employeeDepartmentRecord.create({
data: {
orgId,
employeeId: id,
oldDepartment: employee.department,
newDepartment: data.department || employee.department,
effectiveMonth: newHireMonth,
endMonth: null,
changeType: 'REHIRE',
createdBy: userId,
},
})
@@ -287,13 +459,23 @@ export async function updateEmployee(orgId: string, id: string, data: any) {
updateData.monthlySalary = encrypt(data.monthlySalary)
// 记录薪资变更
if (oldSalary !== newSalary) {
const now = new Date()
const nowMonth = dateToMonth(now)
// 关闭之前有效记录
await prisma.salaryChangeRecord.updateMany({
where: { employeeId: id, endMonth: null },
data: { endMonth: prevMonth(nowMonth) },
})
await prisma.salaryChangeRecord.create({
data: {
orgId,
employeeId: id,
oldSalary,
newSalary,
effectiveDate: new Date(),
effectiveDate: now,
effectiveMonth: nowMonth,
endMonth: null,
changeType: 'SALARY_CHANGE',
reason: data.salaryChangeReason || '手动调整',
createdBy: '',
},