feat: 社保公积金账户化重构
新增 SocialAccount(账户)+ SocialYearStandard(年度标准)两层实体, 替代原 SocialInsuranceConfig/HousingFundConfig 按城市管理的方式。 - DB: 新增 SocialAccount、SocialYearStandard 表,Department 加账户关联 - 迁移: 旧 Config 表数据迁移到 Account + YearStandard - 后端: 新增账户 CRUD + 年度标准 API,薪资计算适配 accountId - 前端: 设置页新增账户管理 Tab,组织架构提示 level=0 可关联账户 - 前端: SocialInsurance.tsx 城市选择器改为账户选择器 - 兼容: 旧 Config 表保留,薪资计算回退旧表 Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
@@ -1,5 +1,69 @@
|
||||
import prisma from '../lib/prisma'
|
||||
|
||||
// ========== 社保公积金账户辅助函数 ==========
|
||||
|
||||
/**
|
||||
* 通过员工获取适用的社保/公积金账户
|
||||
* 优先从员工所属根部门(level=0)关联的账户继承,回退到公司默认账户
|
||||
*/
|
||||
async function getEmployeeAccounts(orgId: string, employeeId: string) {
|
||||
const emp = await prisma.employee.findFirst({
|
||||
where: { id: employeeId, orgId },
|
||||
include: { dept: true },
|
||||
})
|
||||
if (!emp) return { socialAccount: null, housingAccount: null }
|
||||
|
||||
// 向上找到 level=0 的根部门
|
||||
let currentDept: any = emp.dept
|
||||
while (currentDept && currentDept.level > 0 && currentDept.parentId) {
|
||||
currentDept = await prisma.department.findUnique({ where: { id: currentDept.parentId } })
|
||||
}
|
||||
const rootDeptId = currentDept?.id || null
|
||||
|
||||
let socialAccount: any = null
|
||||
let housingAccount: any = null
|
||||
if (rootDeptId) {
|
||||
const rootDept = await prisma.department.findUnique({
|
||||
where: { id: rootDeptId },
|
||||
include: { socialAccount: true, housingAccount: true },
|
||||
})
|
||||
socialAccount = rootDept?.socialAccount || null
|
||||
housingAccount = rootDept?.housingAccount || null
|
||||
}
|
||||
|
||||
// 回退到公司默认账户
|
||||
if (!socialAccount) {
|
||||
socialAccount = await prisma.socialAccount.findFirst({ where: { orgId, type: 'SOCIAL', isDefault: true } })
|
||||
}
|
||||
if (!housingAccount) {
|
||||
housingAccount = await prisma.socialAccount.findFirst({ where: { orgId, type: 'HOUSING', isDefault: true } })
|
||||
}
|
||||
|
||||
return { socialAccount, housingAccount }
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过账户获取指定月份的年度标准
|
||||
*/
|
||||
async function getStandardByAccountAndMonth(accountId: string, month: string) {
|
||||
const standard = await prisma.socialYearStandard.findFirst({
|
||||
where: {
|
||||
accountId,
|
||||
effectiveFrom: { lte: month },
|
||||
OR: [{ effectiveTo: null }, { effectiveTo: { gte: month } }],
|
||||
},
|
||||
orderBy: { effectiveFrom: 'desc' },
|
||||
})
|
||||
if (!standard) {
|
||||
// 回退到当前生效标准
|
||||
return prisma.socialYearStandard.findFirst({
|
||||
where: { accountId, isCurrent: true },
|
||||
orderBy: { effectiveFrom: 'desc' },
|
||||
})
|
||||
}
|
||||
return standard
|
||||
}
|
||||
|
||||
// ========== 薪酬模版 ==========
|
||||
|
||||
const DEFAULT_ITEMS: { name: string; code: string; type: 'INPUT' | 'CALCULATED'; formula: string | null; order: number; isDefault: boolean; isEditable: boolean }[] = [
|
||||
@@ -142,25 +206,32 @@ export async function calcBatchEntry(
|
||||
const employee = await prisma.employee.findFirst({ where: { id: employeeId, orgId } })
|
||||
if (!employee) throw { code: 'NOT_FOUND', message: '员工不存在' }
|
||||
|
||||
// 通过员工账户查年度标准(新逻辑),回退到旧配置(兼容)
|
||||
const { socialAccount, housingAccount } = await getEmployeeAccounts(orgId, employeeId)
|
||||
let socialConfig: any = null
|
||||
let housingConfig: any = null
|
||||
|
||||
if (socialAccount) {
|
||||
socialConfig = await getStandardByAccountAndMonth(socialAccount.id, month)
|
||||
}
|
||||
if (housingAccount) {
|
||||
housingConfig = await getStandardByAccountAndMonth(housingAccount.id, month)
|
||||
}
|
||||
|
||||
// 回退到旧配置表(兼容未迁移数据)
|
||||
const cityWhere = employee.city ? { orgId, city: employee.city } : { orgId }
|
||||
const [socialConfig, housingConfig] = await Promise.all([
|
||||
prisma.socialInsuranceConfig.findFirst({
|
||||
where: {
|
||||
...cityWhere,
|
||||
effectiveFrom: { lte: month },
|
||||
OR: [{ effectiveTo: null }, { effectiveTo: { gte: month } }],
|
||||
},
|
||||
if (!socialConfig) {
|
||||
socialConfig = await prisma.socialInsuranceConfig.findFirst({
|
||||
where: { ...cityWhere, effectiveFrom: { lte: month }, OR: [{ effectiveTo: null }, { effectiveTo: { gte: month } }] },
|
||||
orderBy: { effectiveFrom: 'desc' },
|
||||
}),
|
||||
prisma.housingFundConfig.findFirst({
|
||||
where: {
|
||||
...cityWhere,
|
||||
effectiveFrom: { lte: month },
|
||||
OR: [{ effectiveTo: null }, { effectiveTo: { gte: month } }],
|
||||
},
|
||||
})
|
||||
}
|
||||
if (!housingConfig) {
|
||||
housingConfig = await prisma.housingFundConfig.findFirst({
|
||||
where: { ...cityWhere, effectiveFrom: { lte: month }, OR: [{ effectiveTo: null }, { effectiveTo: { gte: month } }] },
|
||||
orderBy: { effectiveFrom: 'desc' },
|
||||
}),
|
||||
])
|
||||
})
|
||||
}
|
||||
|
||||
// 社保基数:优先用员工核定基数,否则用基本工资
|
||||
const socialBase = employee.socialInsBase || inputs.baseSalary
|
||||
|
||||
Reference in New Issue
Block a user