feat: 社保年度标准完善 — 医疗附加 + 继承旧配置 + 公积金独立上下限

1. 医疗附加金额:SocialYearStandard 和 SocialInsuranceConfig 新增
   medicalOrgExtra/medicalEmpExtra 字段(如北京3元大病医疗)
   - calcSocialInsurance 计算时加上医疗附加
   - 前端年度标准表单加医疗企业/个人附加输入

2. 新建年度标准继承:新增 /inherit-config API
   - 优先用当前年度标准
   - 回退到旧 SocialInsuranceConfig/HousingFundConfig
   - 前端点击"新建年度标准"时自动继承填充

3. 公积金独立上下限:SocialYearStandard 新增 housingBaseMin/housingBaseMax
   - calcHousingFund 优先用公积金专用上下限,为0时回退到社保
   - 前端公积金年度标准表单加公积金基数上下限输入

4. blank_employees/blank_all 模式跳过试用期工资覆盖

5. 劳务协议/实习协议人员 calcBatchEntry 强制社保公积金为0

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
selfrelease
2026-08-16 15:31:56 +08:00
parent 3ac333891c
commit 38075420f4
5 changed files with 98 additions and 8 deletions
+48 -3
View File
@@ -14,6 +14,8 @@ const socialConfigFields = {
pensionEmp: z.number().optional(),
medicalOrg: z.number().optional(),
medicalEmp: z.number().optional(),
medicalOrgExtra: z.number().min(0).optional(),
medicalEmpExtra: z.number().min(0).optional(),
unemploymentOrg: z.number().optional(),
unemploymentEmp: z.number().optional(),
injuryOrg: z.number().optional(),
@@ -166,6 +168,47 @@ router.get('/accounts/:accountId/current-standard', async (req: AuthRequest, res
} catch (err) { next(err) }
})
// 按账户获取继承数据(当前标准 → 旧社保配置 → 默认值),用于新建年度标准初始值
router.get('/accounts/:accountId/inherit-config', async (req: AuthRequest, res: Response, next: NextFunction) => {
try {
const orgId = req.user!.orgId
const { accountId } = req.params
const account = await prisma.socialAccount.findFirst({ where: { id: accountId, orgId } })
if (!account) return res.status(404).json({ success: false, error: { code: 'NOT_FOUND', message: '账户不存在' } })
// 1. 优先用当前年度标准
const currentStandard = await prisma.socialYearStandard.findFirst({
where: { orgId, accountId, isCurrent: true },
orderBy: { effectiveFrom: 'desc' },
})
if (currentStandard) {
return res.json({ success: true, data: { ...currentStandard, source: 'current_standard' } })
}
// 2. 回退到旧社保/公积金配置表(按城市)
if (account.type === 'HOUSING') {
const oldConfig = await prisma.housingFundConfig.findFirst({
where: { orgId, city: account.city },
orderBy: { effectiveFrom: 'desc' },
})
if (oldConfig) {
return res.json({ success: true, data: { ...oldConfig, source: 'old_housing_config' } })
}
} else {
const oldConfig = await prisma.socialInsuranceConfig.findFirst({
where: { orgId, city: account.city },
orderBy: { effectiveFrom: 'desc' },
})
if (oldConfig) {
return res.json({ success: true, data: { ...oldConfig, source: 'old_social_config' } })
}
}
// 3. 都没有,返回 null
res.json({ success: true, data: null })
} catch (err) { next(err) }
})
// 按账户+月份获取适用标准
router.get('/accounts/:accountId/standard-by-month/:month', async (req: AuthRequest, res: Response, next: NextFunction) => {
try {
@@ -194,6 +237,8 @@ const yearStandardSchema = z.object({
pensionEmp: z.number().optional(),
medicalOrg: z.number().optional(),
medicalEmp: z.number().optional(),
medicalOrgExtra: z.number().min(0).optional(),
medicalEmpExtra: z.number().min(0).optional(),
unemploymentOrg: z.number().optional(),
unemploymentEmp: z.number().optional(),
injuryOrg: z.number().optional(),
@@ -760,8 +805,8 @@ router.post('/calculate', async (req: AuthRequest, res: Response, next: NextFunc
const pensionOrg = actualBase * config.pensionOrg / 100
const pensionEmp = actualBase * config.pensionEmp / 100
const medicalOrg = medicalBase * config.medicalOrg / 100
const medicalEmp = medicalBase * config.medicalEmp / 100
const medicalOrg = medicalBase * config.medicalOrg / 100 + (config.medicalOrgExtra || 0)
const medicalEmp = medicalBase * config.medicalEmp / 100 + (config.medicalEmpExtra || 0)
const unemploymentOrg = actualBase * config.unemploymentOrg / 100
const unemploymentEmp = actualBase * config.unemploymentEmp / 100
const injuryOrg = actualBase * config.injuryOrg / 100
@@ -1150,7 +1195,7 @@ function calcSocialDetail(base: number, config: any) {
const medicalBase = Math.min(Math.max(base, medMin), medMax)
const items = [
{ name: '养老', orgRate: config.pensionOrg, empRate: config.pensionEmp, orgAmount: actualBase * config.pensionOrg / 100, empAmount: actualBase * config.pensionEmp / 100 },
{ name: '医疗', orgRate: config.medicalOrg, empRate: config.medicalEmp, orgAmount: medicalBase * config.medicalOrg / 100, empAmount: medicalBase * config.medicalEmp / 100 },
{ name: '医疗', orgRate: config.medicalOrg, empRate: config.medicalEmp, orgAmount: medicalBase * config.medicalOrg / 100 + (config.medicalOrgExtra || 0), empAmount: medicalBase * config.medicalEmp / 100 + (config.medicalEmpExtra || 0) },
{ name: '失业', orgRate: config.unemploymentOrg, empRate: config.unemploymentEmp, orgAmount: actualBase * config.unemploymentOrg / 100, empAmount: actualBase * config.unemploymentEmp / 100 },
{ name: '工伤', orgRate: config.injuryOrg, empRate: 0, orgAmount: actualBase * config.injuryOrg / 100, empAmount: 0 },
{ name: '生育', orgRate: config.maternityOrg, empRate: 0, orgAmount: medicalBase * config.maternityOrg / 100, empAmount: 0 },