feat: 补充公积金支持(员工级别可选)
上海等城市存在补充公积金账户,同一企业内高管可能有但普通员工 没有。新增员工级别补充公积金账户关联: - Employee 表加 supplementaryHousingAccountId 字段 - BatchEntry 表加 supplementaryHousingEmp/Org 字段 - 薪资计算 calcBatchEntry 支持补充公积金(额外算一笔,纳入 个税扣除、最低工资保护递延逻辑) - 新增员工弹窗加补充公积金账户选择器(可选,仅显示 accountType=SUPPLEMENTARY 的公积金账户) - 员工档案编辑支持补充公积金账户 Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
@@ -263,6 +263,8 @@ model Employee {
|
|||||||
socialInsEndMonth String? // 当前社保截止年月(便捷字段,null=在保)
|
socialInsEndMonth String? // 当前社保截止年月(便捷字段,null=在保)
|
||||||
housingFundStartMonth String? // 当前公积金开始年月(便捷字段)
|
housingFundStartMonth String? // 当前公积金开始年月(便捷字段)
|
||||||
housingFundEndMonth String? // 当前公积金截止年月(便捷字段)
|
housingFundEndMonth String? // 当前公积金截止年月(便捷字段)
|
||||||
|
supplementaryHousingAccountId String? // 补充公积金账户(可选,高管才有)
|
||||||
|
supplementaryHousingAccount SocialAccount? @relation("EmpSupplementaryHousingAccount", fields: [supplementaryHousingAccountId], references: [id], onDelete: SetNull)
|
||||||
specialDeduction Float @default(0) // 专项附加扣除(子女教育、赡养老人等,员工portal端填报)
|
specialDeduction Float @default(0) // 专项附加扣除(子女教育、赡养老人等,员工portal端填报)
|
||||||
city String? // 员工社保参保城市
|
city String? // 员工社保参保城市
|
||||||
birthDate DateTime? // 出生日期(从身份证号提取)
|
birthDate DateTime? // 出生日期(从身份证号提取)
|
||||||
@@ -489,6 +491,8 @@ model SocialAccount {
|
|||||||
// 部门关联(员工通过部门继承账户)
|
// 部门关联(员工通过部门继承账户)
|
||||||
deptSocialAccounts Department[] @relation("DeptSocialAccount")
|
deptSocialAccounts Department[] @relation("DeptSocialAccount")
|
||||||
deptHousingAccounts Department[] @relation("DeptHousingAccount")
|
deptHousingAccounts Department[] @relation("DeptHousingAccount")
|
||||||
|
// 员工补充公积金关联
|
||||||
|
empSupplementaryHousing Employee[] @relation("EmpSupplementaryHousingAccount")
|
||||||
|
|
||||||
@@unique([orgId, type, name])
|
@@unique([orgId, type, name])
|
||||||
@@index([orgId, type, city])
|
@@index([orgId, type, city])
|
||||||
@@ -908,6 +912,8 @@ model BatchEntry {
|
|||||||
socialOrg Float @default(0)
|
socialOrg Float @default(0)
|
||||||
housingEmp Float @default(0)
|
housingEmp Float @default(0)
|
||||||
housingOrg Float @default(0)
|
housingOrg Float @default(0)
|
||||||
|
supplementaryHousingEmp Float @default(0) // 补充公积金个人部分
|
||||||
|
supplementaryHousingOrg Float @default(0) // 补充公积金单位部分
|
||||||
tax Float @default(0)
|
tax Float @default(0)
|
||||||
totalPay Float @default(0) // 应发合计
|
totalPay Float @default(0) // 应发合计
|
||||||
netPay Float @default(0) // 实发工资
|
netPay Float @default(0) // 实发工资
|
||||||
|
|||||||
@@ -467,6 +467,8 @@ router.post('/batches', async (req: AuthRequest, res: Response, next: NextFuncti
|
|||||||
socialOrg: calcResult.socialOrg,
|
socialOrg: calcResult.socialOrg,
|
||||||
housingEmp: calcResult.housingEmp,
|
housingEmp: calcResult.housingEmp,
|
||||||
housingOrg: calcResult.housingOrg,
|
housingOrg: calcResult.housingOrg,
|
||||||
|
supplementaryHousingEmp: calcResult.supplementaryHousingEmp || 0,
|
||||||
|
supplementaryHousingOrg: calcResult.supplementaryHousingOrg || 0,
|
||||||
tax: calcResult.tax,
|
tax: calcResult.tax,
|
||||||
totalPay: calcResult.totalPay,
|
totalPay: calcResult.totalPay,
|
||||||
netPay: calcResult.netPay,
|
netPay: calcResult.netPay,
|
||||||
@@ -578,7 +580,7 @@ router.put('/batches/:batchId/entries/:employeeId', async (req: AuthRequest, res
|
|||||||
// 重新计算
|
// 重新计算
|
||||||
const calcResult = await calcBatchEntry(orgId, employeeId, batch.month, inputs, batch.type, options)
|
const calcResult = await calcBatchEntry(orgId, employeeId, batch.month, inputs, batch.type, options)
|
||||||
|
|
||||||
const { systemSocialEmp, systemSocialOrg, systemHousingEmp, systemHousingOrg, taxBreakdown, ...entryData } = calcResult
|
const { systemSocialEmp, systemSocialOrg, systemHousingEmp, systemHousingOrg, systemSupplementaryHousingEmp, systemSupplementaryHousingOrg, taxBreakdown, ...entryData } = calcResult
|
||||||
const updated = await prisma.batchEntry.update({
|
const updated = await prisma.batchEntry.update({
|
||||||
where: { id: entry.id },
|
where: { id: entry.id },
|
||||||
data: { ...inputs, ...entryData },
|
data: { ...inputs, ...entryData },
|
||||||
@@ -718,10 +720,14 @@ router.get('/batches/:batchId/entries/:employeeId/tax-detail', async (req: AuthR
|
|||||||
taxBreakdown.systemSocialOrg = calcResult.systemSocialOrg
|
taxBreakdown.systemSocialOrg = calcResult.systemSocialOrg
|
||||||
taxBreakdown.systemHousingEmp = calcResult.systemHousingEmp
|
taxBreakdown.systemHousingEmp = calcResult.systemHousingEmp
|
||||||
taxBreakdown.systemHousingOrg = calcResult.systemHousingOrg
|
taxBreakdown.systemHousingOrg = calcResult.systemHousingOrg
|
||||||
|
taxBreakdown.systemSupplementaryHousingEmp = calcResult.systemSupplementaryHousingEmp
|
||||||
|
taxBreakdown.systemSupplementaryHousingOrg = calcResult.systemSupplementaryHousingOrg
|
||||||
taxBreakdown.actualSocialEmp = entry.socialEmp
|
taxBreakdown.actualSocialEmp = entry.socialEmp
|
||||||
taxBreakdown.actualSocialOrg = entry.socialOrg
|
taxBreakdown.actualSocialOrg = entry.socialOrg
|
||||||
taxBreakdown.actualHousingEmp = entry.housingEmp
|
taxBreakdown.actualHousingEmp = entry.housingEmp
|
||||||
taxBreakdown.actualHousingOrg = entry.housingOrg
|
taxBreakdown.actualHousingOrg = entry.housingOrg
|
||||||
|
taxBreakdown.actualSupplementaryHousingEmp = entry.supplementaryHousingEmp || 0
|
||||||
|
taxBreakdown.actualSupplementaryHousingOrg = entry.supplementaryHousingOrg || 0
|
||||||
res.json({ success: true, data: taxBreakdown })
|
res.json({ success: true, data: taxBreakdown })
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
next(err)
|
next(err)
|
||||||
@@ -772,7 +778,7 @@ router.post('/batches/:batchId/employees', async (req: AuthRequest, res: Respons
|
|||||||
const calcResult = await calcBatchEntry(orgId, employeeId, batch.month, { baseSalary, overtimePay, allowance: 0, deduction: 0, bonus: 0 }, batch.type)
|
const calcResult = await calcBatchEntry(orgId, employeeId, batch.month, { baseSalary, overtimePay, allowance: 0, deduction: 0, bonus: 0 }, batch.type)
|
||||||
const riskWarnings = await getPayrollRiskWarnings(orgId, employeeId)
|
const riskWarnings = await getPayrollRiskWarnings(orgId, employeeId)
|
||||||
|
|
||||||
const { systemSocialEmp: _sse, systemSocialOrg: _sso, systemHousingEmp: _she, systemHousingOrg: _sho, taxBreakdown: _tb, ...entryData } = calcResult
|
const { systemSocialEmp: _sse, systemSocialOrg: _sso, systemHousingEmp: _she, systemHousingOrg: _sho, systemSupplementaryHousingEmp: _sshe, systemSupplementaryHousingOrg: _ssho, taxBreakdown: _tb, ...entryData } = calcResult
|
||||||
const entry = await prisma.batchEntry.create({
|
const entry = await prisma.batchEntry.create({
|
||||||
data: {
|
data: {
|
||||||
batchId, orgId, employeeId,
|
batchId, orgId, employeeId,
|
||||||
@@ -915,7 +921,7 @@ router.post('/batches/:batchId/archive', async (req: AuthRequest, res: Response,
|
|||||||
const options: any = { prevDeferred }
|
const options: any = { prevDeferred }
|
||||||
|
|
||||||
const calcResult = await calcBatchEntry(orgId, entry.employeeId, batch.month, inputs, batch.type, options)
|
const calcResult = await calcBatchEntry(orgId, entry.employeeId, batch.month, inputs, batch.type, options)
|
||||||
const { systemSocialEmp: _sse, systemSocialOrg: _sso, systemHousingEmp: _she, systemHousingOrg: _sho, taxBreakdown: _tb, ...entryData } = calcResult
|
const { systemSocialEmp: _sse, systemSocialOrg: _sso, systemHousingEmp: _she, systemHousingOrg: _sho, systemSupplementaryHousingEmp: _sshe, systemSupplementaryHousingOrg: _ssho, taxBreakdown: _tb, ...entryData } = calcResult
|
||||||
await prisma.batchEntry.update({
|
await prisma.batchEntry.update({
|
||||||
where: { id: entry.id },
|
where: { id: entry.id },
|
||||||
data: { ...entryData },
|
data: { ...entryData },
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ export const createEmployeeSchema = z.object({
|
|||||||
city: z.string().max(20).optional(),
|
city: z.string().max(20).optional(),
|
||||||
education: z.string().max(20).optional(),
|
education: z.string().max(20).optional(),
|
||||||
position: z.string().max(50).optional(),
|
position: z.string().max(50).optional(),
|
||||||
|
supplementaryHousingAccountId: z.string().nullable().optional(),
|
||||||
contract: z.object({
|
contract: z.object({
|
||||||
signDate: z.string().datetime().nullable(),
|
signDate: z.string().datetime().nullable(),
|
||||||
startDate: z.string().datetime(),
|
startDate: z.string().datetime(),
|
||||||
@@ -51,6 +52,7 @@ export const updateEmployeeSchema = z.object({
|
|||||||
education: z.string().max(20).optional(),
|
education: z.string().max(20).optional(),
|
||||||
position: z.string().max(50).optional(),
|
position: z.string().max(50).optional(),
|
||||||
status: z.enum(['ACTIVE', 'PENDING_ONBOARD', 'RESIGNED', 'BLACKLISTED']).optional(),
|
status: z.enum(['ACTIVE', 'PENDING_ONBOARD', 'RESIGNED', 'BLACKLISTED']).optional(),
|
||||||
|
supplementaryHousingAccountId: z.string().nullable().optional(),
|
||||||
cityChangeReason: z.string().max(200).optional(),
|
cityChangeReason: z.string().max(200).optional(),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -407,6 +407,7 @@ export async function createEmployee(orgId: string, userId: string, data: any) {
|
|||||||
education: data.education || null,
|
education: data.education || null,
|
||||||
position: data.position || null,
|
position: data.position || null,
|
||||||
status: data.status || 'ACTIVE',
|
status: data.status || 'ACTIVE',
|
||||||
|
supplementaryHousingAccountId: data.supplementaryHousingAccountId || null,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -759,6 +760,7 @@ export async function updateEmployee(orgId: string, id: string, data: any) {
|
|||||||
if (data.education !== undefined) updateData.education = data.education
|
if (data.education !== undefined) updateData.education = data.education
|
||||||
if (data.position !== undefined) updateData.position = data.position
|
if (data.position !== undefined) updateData.position = data.position
|
||||||
if (data.status !== undefined) updateData.status = data.status
|
if (data.status !== undefined) updateData.status = data.status
|
||||||
|
if (data.supplementaryHousingAccountId !== undefined) updateData.supplementaryHousingAccountId = data.supplementaryHousingAccountId || null
|
||||||
|
|
||||||
// 参保城市变更:关闭旧城市在保记录,创建新城市记录
|
// 参保城市变更:关闭旧城市在保记录,创建新城市记录
|
||||||
if (data.city !== undefined && data.city !== employee.city) {
|
if (data.city !== undefined && data.city !== employee.city) {
|
||||||
|
|||||||
@@ -220,6 +220,7 @@ export async function calcBatchEntry(
|
|||||||
const { socialAccount, housingAccount } = await getEmployeeAccounts(orgId, employeeId)
|
const { socialAccount, housingAccount } = await getEmployeeAccounts(orgId, employeeId)
|
||||||
let socialConfig: any = null
|
let socialConfig: any = null
|
||||||
let housingConfig: any = null
|
let housingConfig: any = null
|
||||||
|
let supplementaryHousingConfig: any = null
|
||||||
|
|
||||||
if (socialAccount) {
|
if (socialAccount) {
|
||||||
socialConfig = await getStandardByAccountAndMonth(socialAccount.id, month)
|
socialConfig = await getStandardByAccountAndMonth(socialAccount.id, month)
|
||||||
@@ -227,6 +228,10 @@ export async function calcBatchEntry(
|
|||||||
if (housingAccount) {
|
if (housingAccount) {
|
||||||
housingConfig = await getStandardByAccountAndMonth(housingAccount.id, month)
|
housingConfig = await getStandardByAccountAndMonth(housingAccount.id, month)
|
||||||
}
|
}
|
||||||
|
// 补充公积金:员工单独关联的补充公积金账户
|
||||||
|
if (employee.supplementaryHousingAccountId) {
|
||||||
|
supplementaryHousingConfig = await getStandardByAccountAndMonth(employee.supplementaryHousingAccountId, month)
|
||||||
|
}
|
||||||
|
|
||||||
// 回退到旧配置表(兼容未迁移数据)
|
// 回退到旧配置表(兼容未迁移数据)
|
||||||
const cityWhere = employee.city ? { orgId, city: employee.city } : { orgId }
|
const cityWhere = employee.city ? { orgId, city: employee.city } : { orgId }
|
||||||
@@ -248,12 +253,14 @@ export async function calcBatchEntry(
|
|||||||
const housingBase = isNoSocialContract ? 0 : (employee.housingFundBase != null ? employee.housingFundBase : inputs.baseSalary)
|
const housingBase = isNoSocialContract ? 0 : (employee.housingFundBase != null ? employee.housingFundBase : inputs.baseSalary)
|
||||||
|
|
||||||
let socialEmp = 0, socialOrg = 0, housingEmp = 0, housingOrg = 0
|
let socialEmp = 0, socialOrg = 0, housingEmp = 0, housingOrg = 0
|
||||||
|
let suppHousingEmp = 0, suppHousingOrg = 0
|
||||||
|
|
||||||
// 年终奖/奖金批次、补偿金批次、劳务协议/实习协议人员:不扣社保公积金
|
// 年终奖/奖金批次、补偿金批次、劳务协议/实习协议人员:不扣社保公积金
|
||||||
// 其他批次:计算当月应缴全额,减去已归档批次已扣金额,差额为本批次应扣
|
// 其他批次:计算当月应缴全额,减去已归档批次已扣金额,差额为本批次应扣
|
||||||
if (batchType !== 'BONUS' && batchType !== 'SEVERANCE' && !options?.skipSocial && !isNoSocialContract) {
|
if (batchType !== 'BONUS' && batchType !== 'SEVERANCE' && !options?.skipSocial && !isNoSocialContract) {
|
||||||
// 1. 计算当月应缴社保公积金全额
|
// 1. 计算当月应缴社保公积金全额
|
||||||
let fullSocialEmp = 0, fullSocialOrg = 0, fullHousingEmp = 0, fullHousingOrg = 0
|
let fullSocialEmp = 0, fullSocialOrg = 0, fullHousingEmp = 0, fullHousingOrg = 0
|
||||||
|
let fullSuppHousingEmp = 0, fullSuppHousingOrg = 0
|
||||||
if (socialConfig) {
|
if (socialConfig) {
|
||||||
const social = calcSocialInsurance(socialBase, socialConfig)
|
const social = calcSocialInsurance(socialBase, socialConfig)
|
||||||
fullSocialEmp = social.socialEmp
|
fullSocialEmp = social.socialEmp
|
||||||
@@ -264,6 +271,12 @@ export async function calcBatchEntry(
|
|||||||
fullHousingEmp = housing.housingEmp
|
fullHousingEmp = housing.housingEmp
|
||||||
fullHousingOrg = housing.housingOrg
|
fullHousingOrg = housing.housingOrg
|
||||||
}
|
}
|
||||||
|
// 补充公积金(用同样的公积金基数)
|
||||||
|
if (supplementaryHousingConfig) {
|
||||||
|
const suppHousing = calcHousingFund(housingBase, supplementaryHousingConfig)
|
||||||
|
fullSuppHousingEmp = suppHousing.housingEmp
|
||||||
|
fullSuppHousingOrg = suppHousing.housingOrg
|
||||||
|
}
|
||||||
|
|
||||||
// 2. 查询该员工当月已归档批次中已扣的社保公积金金额
|
// 2. 查询该员工当月已归档批次中已扣的社保公积金金额
|
||||||
const archivedEntries = await prisma.batchEntry.findMany({
|
const archivedEntries = await prisma.batchEntry.findMany({
|
||||||
@@ -272,18 +285,22 @@ export async function calcBatchEntry(
|
|||||||
employeeId,
|
employeeId,
|
||||||
batch: { month, status: 'ARCHIVED', type: { in: ['REGULAR', 'TERMINATION'] } },
|
batch: { month, status: 'ARCHIVED', type: { in: ['REGULAR', 'TERMINATION'] } },
|
||||||
},
|
},
|
||||||
select: { socialEmp: true, socialOrg: true, housingEmp: true, housingOrg: true },
|
select: { socialEmp: true, socialOrg: true, housingEmp: true, housingOrg: true, supplementaryHousingEmp: true, supplementaryHousingOrg: true },
|
||||||
})
|
})
|
||||||
const deductedSocialEmp = archivedEntries.reduce((s, e) => s + e.socialEmp, 0)
|
const deductedSocialEmp = archivedEntries.reduce((s, e) => s + e.socialEmp, 0)
|
||||||
const deductedSocialOrg = archivedEntries.reduce((s, e) => s + e.socialOrg, 0)
|
const deductedSocialOrg = archivedEntries.reduce((s, e) => s + e.socialOrg, 0)
|
||||||
const deductedHousingEmp = archivedEntries.reduce((s, e) => s + e.housingEmp, 0)
|
const deductedHousingEmp = archivedEntries.reduce((s, e) => s + e.housingEmp, 0)
|
||||||
const deductedHousingOrg = archivedEntries.reduce((s, e) => s + e.housingOrg, 0)
|
const deductedHousingOrg = archivedEntries.reduce((s, e) => s + e.housingOrg, 0)
|
||||||
|
const deductedSuppHousingEmp = archivedEntries.reduce((s, e) => s + (e.supplementaryHousingEmp || 0), 0)
|
||||||
|
const deductedSuppHousingOrg = archivedEntries.reduce((s, e) => s + (e.supplementaryHousingOrg || 0), 0)
|
||||||
|
|
||||||
// 3. 本批次应扣 = 应缴全额 - 已扣金额(不足额补扣,足额为0)
|
// 3. 本批次应扣 = 应缴全额 - 已扣金额(不足额补扣,足额为0)
|
||||||
socialEmp = Math.max(0, fullSocialEmp - deductedSocialEmp)
|
socialEmp = Math.max(0, fullSocialEmp - deductedSocialEmp)
|
||||||
socialOrg = Math.max(0, fullSocialOrg - deductedSocialOrg)
|
socialOrg = Math.max(0, fullSocialOrg - deductedSocialOrg)
|
||||||
housingEmp = Math.max(0, fullHousingEmp - deductedHousingEmp)
|
housingEmp = Math.max(0, fullHousingEmp - deductedHousingEmp)
|
||||||
housingOrg = Math.max(0, fullHousingOrg - deductedHousingOrg)
|
housingOrg = Math.max(0, fullHousingOrg - deductedHousingOrg)
|
||||||
|
suppHousingEmp = Math.max(0, fullSuppHousingEmp - deductedSuppHousingEmp)
|
||||||
|
suppHousingOrg = Math.max(0, fullSuppHousingOrg - deductedSuppHousingOrg)
|
||||||
|
|
||||||
// 4. 叠加上月递延的社保/公积金(入职当月未扣完的部分,本月补扣)
|
// 4. 叠加上月递延的社保/公积金(入职当月未扣完的部分,本月补扣)
|
||||||
if (options?.prevDeferred) {
|
if (options?.prevDeferred) {
|
||||||
@@ -297,6 +314,8 @@ export async function calcBatchEntry(
|
|||||||
const systemSocialOrg = socialOrg
|
const systemSocialOrg = socialOrg
|
||||||
const systemHousingEmp = housingEmp
|
const systemHousingEmp = housingEmp
|
||||||
const systemHousingOrg = housingOrg
|
const systemHousingOrg = housingOrg
|
||||||
|
const systemSupplementaryHousingEmp = suppHousingEmp
|
||||||
|
const systemSupplementaryHousingOrg = suppHousingOrg
|
||||||
|
|
||||||
// 手动覆盖社保值
|
// 手动覆盖社保值
|
||||||
if (options?.overrideSocial) {
|
if (options?.overrideSocial) {
|
||||||
@@ -340,11 +359,11 @@ export async function calcBatchEntry(
|
|||||||
status: 'ARCHIVED',
|
status: 'ARCHIVED',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
select: { totalPay: true, socialEmp: true, housingEmp: true, tax: true },
|
select: { totalPay: true, socialEmp: true, housingEmp: true, supplementaryHousingEmp: true, tax: true },
|
||||||
})
|
})
|
||||||
const ytdIncome = archivedEntries.reduce((s, e) => s + e.totalPay, 0) + totalPay
|
const ytdIncome = archivedEntries.reduce((s, e) => s + e.totalPay, 0) + totalPay
|
||||||
const ytdSocialEmp = archivedEntries.reduce((s, e) => s + e.socialEmp, 0) + socialEmp
|
const ytdSocialEmp = archivedEntries.reduce((s, e) => s + e.socialEmp, 0) + socialEmp
|
||||||
const ytdHousingEmp = archivedEntries.reduce((s, e) => s + e.housingEmp, 0) + housingEmp
|
const ytdHousingEmp = archivedEntries.reduce((s, e) => s + e.housingEmp + (e.supplementaryHousingEmp || 0), 0) + housingEmp + suppHousingEmp
|
||||||
|
|
||||||
// 专项附加扣除:按月读取 SpecialDeductionRecord 实际填报金额累加
|
// 专项附加扣除:按月读取 SpecialDeductionRecord 实际填报金额累加
|
||||||
// 优先使用按月记录;若无按月记录,回退到员工便捷字段 × 月数(兼容旧数据)
|
// 优先使用按月记录;若无按月记录,回退到员工便捷字段 × 月数(兼容旧数据)
|
||||||
@@ -392,8 +411,8 @@ export async function calcBatchEntry(
|
|||||||
// 上月递延的最低工资补齐差额(本批次需扣回)
|
// 上月递延的最低工资补齐差额(本批次需扣回)
|
||||||
const prevDeferredMinWage = options?.prevDeferred?.minWage || 0
|
const prevDeferredMinWage = options?.prevDeferred?.minWage || 0
|
||||||
|
|
||||||
// 初始实发 = 应发 - 社保 - 公积金 - 个税 - 上月递延最低工资补扣
|
// 初始实发 = 应发 - 社保 - 公积金 - 补充公积金 - 个税 - 上月递延最低工资补扣
|
||||||
let netPay = totalPay - socialEmp - housingEmp - tax - prevDeferredMinWage
|
let netPay = totalPay - socialEmp - housingEmp - suppHousingEmp - tax - prevDeferredMinWage
|
||||||
|
|
||||||
// 递延金额(本批次扣不动、递延到次月的部分)
|
// 递延金额(本批次扣不动、递延到次月的部分)
|
||||||
let deferredSocialEmp = 0
|
let deferredSocialEmp = 0
|
||||||
@@ -433,21 +452,31 @@ export async function calcBatchEntry(
|
|||||||
socialEmp -= shortfall
|
socialEmp -= shortfall
|
||||||
netPay = targetNetPay
|
netPay = targetNetPay
|
||||||
minWageApplied = shortfall
|
minWageApplied = shortfall
|
||||||
} else if (shortfall <= socialEmp + housingEmp) {
|
} else if (shortfall <= socialEmp + housingEmp + suppHousingEmp) {
|
||||||
// 递延全部社保 + 部分公积金
|
// 递延全部社保 + 部分公积金(含补充)
|
||||||
deferredSocialEmp = socialEmp
|
deferredSocialEmp = socialEmp
|
||||||
deferredHousingEmp = shortfall - socialEmp
|
|
||||||
socialEmp = 0
|
socialEmp = 0
|
||||||
housingEmp -= deferredHousingEmp
|
const housingShortfall = shortfall - deferredSocialEmp
|
||||||
|
// 先递延基本公积金,再递延补充公积金
|
||||||
|
if (housingShortfall <= housingEmp) {
|
||||||
|
deferredHousingEmp = housingShortfall
|
||||||
|
housingEmp -= housingShortfall
|
||||||
|
} else {
|
||||||
|
deferredHousingEmp = housingEmp
|
||||||
|
const suppShortfall = housingShortfall - housingEmp
|
||||||
|
housingEmp = 0
|
||||||
|
suppHousingEmp -= suppShortfall
|
||||||
|
}
|
||||||
netPay = targetNetPay
|
netPay = targetNetPay
|
||||||
minWageApplied = shortfall
|
minWageApplied = shortfall
|
||||||
} else {
|
} else {
|
||||||
// 递延全部社保 + 全部公积金,仍不足 → 差额作为最低工资补齐递延
|
// 递延全部社保 + 全部公积金(含补充),仍不足 → 差额作为最低工资补齐递延
|
||||||
deferredSocialEmp = socialEmp
|
deferredSocialEmp = socialEmp
|
||||||
deferredHousingEmp = housingEmp
|
deferredHousingEmp = housingEmp
|
||||||
const remainingShortfall = shortfall - socialEmp - housingEmp
|
const remainingShortfall = shortfall - socialEmp - housingEmp - suppHousingEmp
|
||||||
socialEmp = 0
|
socialEmp = 0
|
||||||
housingEmp = 0
|
housingEmp = 0
|
||||||
|
suppHousingEmp = 0
|
||||||
deferredMinWage = remainingShortfall
|
deferredMinWage = remainingShortfall
|
||||||
netPay = targetNetPay
|
netPay = targetNetPay
|
||||||
minWageApplied = shortfall
|
minWageApplied = shortfall
|
||||||
@@ -461,10 +490,14 @@ export async function calcBatchEntry(
|
|||||||
socialOrg: Math.round(socialOrg * 100) / 100,
|
socialOrg: Math.round(socialOrg * 100) / 100,
|
||||||
housingEmp: Math.round(housingEmp * 100) / 100,
|
housingEmp: Math.round(housingEmp * 100) / 100,
|
||||||
housingOrg: Math.round(housingOrg * 100) / 100,
|
housingOrg: Math.round(housingOrg * 100) / 100,
|
||||||
|
supplementaryHousingEmp: Math.round(suppHousingEmp * 100) / 100,
|
||||||
|
supplementaryHousingOrg: Math.round(suppHousingOrg * 100) / 100,
|
||||||
systemSocialEmp: Math.round(systemSocialEmp * 100) / 100,
|
systemSocialEmp: Math.round(systemSocialEmp * 100) / 100,
|
||||||
systemSocialOrg: Math.round(systemSocialOrg * 100) / 100,
|
systemSocialOrg: Math.round(systemSocialOrg * 100) / 100,
|
||||||
systemHousingEmp: Math.round(systemHousingEmp * 100) / 100,
|
systemHousingEmp: Math.round(systemHousingEmp * 100) / 100,
|
||||||
systemHousingOrg: Math.round(systemHousingOrg * 100) / 100,
|
systemHousingOrg: Math.round(systemHousingOrg * 100) / 100,
|
||||||
|
systemSupplementaryHousingEmp: Math.round(systemSupplementaryHousingEmp * 100) / 100,
|
||||||
|
systemSupplementaryHousingOrg: Math.round(systemSupplementaryHousingOrg * 100) / 100,
|
||||||
tax,
|
tax,
|
||||||
taxBreakdown,
|
taxBreakdown,
|
||||||
totalPay: Math.round(totalPay * 100) / 100,
|
totalPay: Math.round(totalPay * 100) / 100,
|
||||||
|
|||||||
@@ -732,6 +732,7 @@ export function AddEmployeeModal({ onClose, onSubmit, loading, error }: {
|
|||||||
const [autoAccounts, setAutoAccounts] = useState<{ socialAccount: any; housingAccount: any; socialStandard: any; housingStandard: any } | null>(null)
|
const [autoAccounts, setAutoAccounts] = useState<{ socialAccount: any; housingAccount: any; socialStandard: any; housingStandard: any } | null>(null)
|
||||||
const [manualSocialAccountId, setManualSocialAccountId] = useState<string>('')
|
const [manualSocialAccountId, setManualSocialAccountId] = useState<string>('')
|
||||||
const [manualHousingAccountId, setManualHousingAccountId] = useState<string>('')
|
const [manualHousingAccountId, setManualHousingAccountId] = useState<string>('')
|
||||||
|
const [supplementaryHousingAccountId, setSupplementaryHousingAccountId] = useState<string>('')
|
||||||
|
|
||||||
// 部门变化时查询适用账户
|
// 部门变化时查询适用账户
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -757,6 +758,8 @@ export function AddEmployeeModal({ onClose, onSubmit, loading, error }: {
|
|||||||
queryFn: () => socialAccountApi.list('HOUSING'),
|
queryFn: () => socialAccountApi.list('HOUSING'),
|
||||||
enabled: !!form.departmentId,
|
enabled: !!form.departmentId,
|
||||||
})
|
})
|
||||||
|
// 补充公积金账户(accountType=SUPPLEMENTARY)
|
||||||
|
const supplementaryHousingAccounts = allHousingAccounts.filter((a: any) => a.accountType === 'SUPPLEMENTARY')
|
||||||
|
|
||||||
// 入职日期变更 → 同步合同开始日期 + 重算结束日期
|
// 入职日期变更 → 同步合同开始日期 + 重算结束日期
|
||||||
const handleHireDateChange = (hireDate: string) => {
|
const handleHireDateChange = (hireDate: string) => {
|
||||||
@@ -952,6 +955,7 @@ export function AddEmployeeModal({ onClose, onSubmit, loading, error }: {
|
|||||||
housingFundStartMonth: form.housingFundStartMonth || undefined,
|
housingFundStartMonth: form.housingFundStartMonth || undefined,
|
||||||
socialAccountId: manualSocialAccountId || undefined,
|
socialAccountId: manualSocialAccountId || undefined,
|
||||||
housingAccountId: manualHousingAccountId || undefined,
|
housingAccountId: manualHousingAccountId || undefined,
|
||||||
|
supplementaryHousingAccountId: supplementaryHousingAccountId || undefined,
|
||||||
}
|
}
|
||||||
if (form.contractType !== 'UNSIGNED' && form.startDate) {
|
if (form.contractType !== 'UNSIGNED' && form.startDate) {
|
||||||
data.contract = {
|
data.contract = {
|
||||||
@@ -1050,6 +1054,7 @@ export function AddEmployeeModal({ onClose, onSubmit, loading, error }: {
|
|||||||
</div>
|
</div>
|
||||||
{/* 账户选择(选部门后自动带出,可手动调整) */}
|
{/* 账户选择(选部门后自动带出,可手动调整) */}
|
||||||
{!isNoSocialContract && (
|
{!isNoSocialContract && (
|
||||||
|
<>
|
||||||
<div className="grid grid-cols-2 gap-4 mb-3">
|
<div className="grid grid-cols-2 gap-4 mb-3">
|
||||||
<div>
|
<div>
|
||||||
<Label>社保账户</Label>
|
<Label>社保账户</Label>
|
||||||
@@ -1080,6 +1085,20 @@ export function AddEmployeeModal({ onClose, onSubmit, loading, error }: {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{supplementaryHousingAccounts.length > 0 && (
|
||||||
|
<div className="grid grid-cols-2 gap-4 mb-3">
|
||||||
|
<div>
|
||||||
|
<Label>补充公积金账户(可选)</Label>
|
||||||
|
<Select value={supplementaryHousingAccountId} onChange={(e) => setSupplementaryHousingAccountId(e.target.value)} disabled={!form.departmentId || isNoSocialContract}>
|
||||||
|
<option value="">无补充公积金</option>
|
||||||
|
{supplementaryHousingAccounts.map((a: any) => (
|
||||||
|
<option key={a.id} value={a.id}>{a.name}({a.city})</option>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
<div className={`grid grid-cols-4 gap-4 ${isNoSocialContract ? 'opacity-50' : ''}`}>
|
<div className={`grid grid-cols-4 gap-4 ${isNoSocialContract ? 'opacity-50' : ''}`}>
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
Reference in New Issue
Block a user