chore: 添加社保公积金账户数据修复脚本
- fix-missing-standards.ts: 补齐无年度标准的账户 - fix-housing-base-limits.ts: 更新各城市公积金基数上下限 - check-test-accounts.ts: 删除不完整的测试账户 - assign-accounts.ts: 为全部员工按城市匹配社保公积金账户 Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
@@ -0,0 +1,154 @@
|
|||||||
|
/**
|
||||||
|
* 给全部员工对应上正确的社保和公积金账户
|
||||||
|
* 逻辑:按员工所在城市的账户匹配,回退到默认账户
|
||||||
|
*/
|
||||||
|
import { PrismaClient } from '@prisma/client'
|
||||||
|
const prisma = new PrismaClient()
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
// 1. 查所有账户(按城市分组)
|
||||||
|
const accounts = await prisma.socialAccount.findMany({
|
||||||
|
select: { id: true, name: true, type: true, city: true, isDefault: true },
|
||||||
|
})
|
||||||
|
console.log('=== 全部账户 ===')
|
||||||
|
for (const a of accounts) {
|
||||||
|
console.log(`${a.type} | ${a.name} | ${a.city} | default=${a.isDefault} | ${a.id}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 按城市+类型建索引
|
||||||
|
const socialByCity: Record<string, string> = {}
|
||||||
|
const housingByCity: Record<string, string> = {}
|
||||||
|
let defaultSocial: string | null = null
|
||||||
|
let defaultHousing: string | null = null
|
||||||
|
|
||||||
|
for (const a of accounts) {
|
||||||
|
if (a.type === 'SOCIAL') {
|
||||||
|
if (a.isDefault) defaultSocial = a.id
|
||||||
|
if (a.city) socialByCity[a.city] = a.id
|
||||||
|
} else {
|
||||||
|
if (a.isDefault) defaultHousing = a.id
|
||||||
|
if (a.city) housingByCity[a.city] = a.id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 查所有部门
|
||||||
|
const depts = await prisma.department.findMany({
|
||||||
|
select: { id: true, name: true, level: true, parentId: true, socialAccountId: true, housingAccountId: true },
|
||||||
|
})
|
||||||
|
console.log('\n=== 部门账户关联(修改前)===')
|
||||||
|
for (const d of depts) {
|
||||||
|
console.log(`${d.name} | level=${d.level} | social=${d.socialAccountId || '无'} | housing=${d.housingAccountId || '无'}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 查所有员工及其部门和城市
|
||||||
|
const employees = await prisma.employee.findMany({
|
||||||
|
select: { id: true, name: true, department: true, departmentId: true, city: true, dept: true },
|
||||||
|
})
|
||||||
|
console.log(`\n=== 员工总数: ${employees.length} ===`)
|
||||||
|
|
||||||
|
// 4. 为每个员工找到根部门,检查是否有账户
|
||||||
|
// 如果根部门没有账户,按员工城市匹配账户,更新根部门
|
||||||
|
const rootDeptMap = new Map<string, { name: string; city: string; empCount: number }>()
|
||||||
|
|
||||||
|
for (const emp of employees) {
|
||||||
|
// 向上找根部门
|
||||||
|
let dept: any = emp.dept
|
||||||
|
if (!dept && emp.departmentId) {
|
||||||
|
dept = await prisma.department.findUnique({ where: { id: emp.departmentId } })
|
||||||
|
}
|
||||||
|
while (dept && dept.level > 0 && dept.parentId) {
|
||||||
|
dept = await prisma.department.findUnique({ where: { id: dept.parentId } })
|
||||||
|
}
|
||||||
|
if (dept) {
|
||||||
|
const existing = rootDeptMap.get(dept.id)
|
||||||
|
if (existing) {
|
||||||
|
existing.empCount++
|
||||||
|
} else {
|
||||||
|
rootDeptMap.set(dept.id, { name: dept.name, city: emp.city || '', empCount: 1 })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('\n=== 根部门及员工城市 ===')
|
||||||
|
for (const [deptId, info] of rootDeptMap) {
|
||||||
|
const dept = await prisma.department.findUnique({ where: { id: deptId }, select: { socialAccountId: true, housingAccountId: true } })
|
||||||
|
console.log(`${info.name} | 员工数=${info.empCount} | 员工城市=${info.city || '无'} | social=${dept?.socialAccountId || '无'} | housing=${dept?.housingAccountId || '无'}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. 为没有账户的根部门按员工城市匹配账户
|
||||||
|
let updated = 0
|
||||||
|
for (const [deptId, info] of rootDeptMap) {
|
||||||
|
const dept = await prisma.department.findUnique({ where: { id: deptId }, select: { socialAccountId: true, housingAccountId: true, name: true } })
|
||||||
|
|
||||||
|
let socialId = dept?.socialAccountId || null
|
||||||
|
let housingId = dept?.housingAccountId || null
|
||||||
|
|
||||||
|
// 社保账户
|
||||||
|
if (!socialId) {
|
||||||
|
if (info.city && socialByCity[info.city]) {
|
||||||
|
socialId = socialByCity[info.city]
|
||||||
|
} else if (defaultSocial) {
|
||||||
|
socialId = defaultSocial
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 公积金账户
|
||||||
|
if (!housingId) {
|
||||||
|
if (info.city && housingByCity[info.city]) {
|
||||||
|
housingId = housingByCity[info.city]
|
||||||
|
} else if (defaultHousing) {
|
||||||
|
housingId = defaultHousing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((socialId && !dept?.socialAccountId) || (housingId && !dept?.housingAccountId)) {
|
||||||
|
await prisma.department.update({
|
||||||
|
where: { id: deptId },
|
||||||
|
data: {
|
||||||
|
...(socialId && !dept?.socialAccountId ? { socialAccountId: socialId } : {}),
|
||||||
|
...(housingId && !dept?.housingAccountId ? { housingAccountId: housingId } : {}),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
console.log(`[更新] 根部门 ${dept?.name} → social=${socialId || '无'} housing=${housingId || '无'}`)
|
||||||
|
updated++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`\n更新了 ${updated} 个根部门的账户关联`)
|
||||||
|
|
||||||
|
// 6. 验证:重新查每个员工是否有账户
|
||||||
|
let noSocial = 0
|
||||||
|
let noHousing = 0
|
||||||
|
let hasBoth = 0
|
||||||
|
for (const emp of employees) {
|
||||||
|
let dept: any = emp.dept
|
||||||
|
if (!dept && emp.departmentId) {
|
||||||
|
dept = await prisma.department.findUnique({ where: { id: emp.departmentId } })
|
||||||
|
}
|
||||||
|
while (dept && dept.level > 0 && dept.parentId) {
|
||||||
|
dept = await prisma.department.findUnique({ where: { id: dept.parentId } })
|
||||||
|
}
|
||||||
|
|
||||||
|
let socialId = dept?.socialAccountId || null
|
||||||
|
let housingId = dept?.housingAccountId || null
|
||||||
|
|
||||||
|
// 回退到默认
|
||||||
|
if (!socialId) socialId = defaultSocial
|
||||||
|
if (!housingId) housingId = defaultHousing
|
||||||
|
|
||||||
|
if (socialId && housingId) {
|
||||||
|
hasBoth++
|
||||||
|
} else {
|
||||||
|
if (!socialId) noSocial++
|
||||||
|
if (!housingId) noHousing++
|
||||||
|
console.log(`[缺失] ${emp.name} | social=${socialId ? '有' : '无'} housing=${housingId ? '有' : '无'} | city=${emp.city || '无'} | dept=${dept?.name || '无'}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`\n=== 验证结果 ===`)
|
||||||
|
console.log(`有社保+公积金: ${hasBoth}`)
|
||||||
|
console.log(`缺社保: ${noSocial}`)
|
||||||
|
console.log(`缺公积金: ${noHousing}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
main().then(() => process.exit(0)).catch(e => { console.error(e); process.exit(1) })
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
/**
|
||||||
|
* 检查测试账户是否有关联数据(员工/部门),然后删除无关联的测试账户
|
||||||
|
*/
|
||||||
|
import { PrismaClient } from '@prisma/client'
|
||||||
|
const prisma = new PrismaClient()
|
||||||
|
|
||||||
|
const TEST_IDS = [
|
||||||
|
'cmsve0ucj000h5kfta5te1qli', // 几何社保账户
|
||||||
|
'cmsve0uea001x5kftbvf9e6zq', // 北公积金账户
|
||||||
|
'cmsve0uec001z5kft5sfl4ika', // s公积金账户
|
||||||
|
'cmsve0ued00215kft32gmeug5', // sh公积金账户
|
||||||
|
'cmsve0uef00235kftz4clk842', // sha公积金账户
|
||||||
|
'cmsve0ueg00255kfttwtfeikj', // shan公积金账户
|
||||||
|
'cmsve0uei00275kftbwdrkf2n', // shang公积金账户
|
||||||
|
'cmsve0uej00295kftc157ts8b', // 上公积金账户
|
||||||
|
'cmsve0uek002b5kftnmmsqpfl', // shi公积金账户
|
||||||
|
'cmsve0uen002d5kftnzstcqa3', // shi'j公积金账户
|
||||||
|
'cmsve0ueo002f5kftxhh9r5lw', // shi'ji公积金账户
|
||||||
|
'cmsve0ueq002h5kft2fdu2tlk', // shi'jia公积金账户
|
||||||
|
'cmsve0uer002j5kftsi37wcuo', // shi'jia'z公积金账户
|
||||||
|
'cmsve0uet002l5kftev27qy7o', // shi'jia'zh公积金账户
|
||||||
|
'cmsve0uf5002x5kft16esy662', // 唐山12+6公积金账户
|
||||||
|
'cmsve0uf9002z5kft3lx7yl1x', // 几何公积金账户
|
||||||
|
'cmsve0ufb00315kftp9mnovag', // 几何5+5公积金账户
|
||||||
|
]
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
console.log('=== 检查关联 ===')
|
||||||
|
let canDelete: string[] = []
|
||||||
|
let blocked: string[] = []
|
||||||
|
|
||||||
|
for (const id of TEST_IDS) {
|
||||||
|
const account = await prisma.socialAccount.findUnique({ where: { id }, select: { name: true, city: true, type: true } })
|
||||||
|
if (!account) {
|
||||||
|
console.log(`[不存在] ${id}`)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
const d1 = await prisma.department.count({ where: { socialAccountId: id } })
|
||||||
|
const d2 = await prisma.department.count({ where: { housingAccountId: id } })
|
||||||
|
|
||||||
|
if (d1 + d2 > 0) {
|
||||||
|
console.log(`[阻止] ${account.type} ${account.name} (${account.city}) — 部门S/H:${d1}/${d2}`)
|
||||||
|
blocked.push(id)
|
||||||
|
} else {
|
||||||
|
console.log(`[可删] ${account.type} ${account.name} (${account.city})`)
|
||||||
|
canDelete.push(id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`\n可删除 ${canDelete.length} 个,阻止 ${blocked.length} 个`)
|
||||||
|
|
||||||
|
if (canDelete.length === 0) {
|
||||||
|
console.log('无可删除账户')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除:先删关联的年度标准、社保记录、公积金记录,再删账户
|
||||||
|
console.log('\n=== 开始删除 ===')
|
||||||
|
for (const id of canDelete) {
|
||||||
|
const account = await prisma.socialAccount.findUnique({ where: { id }, select: { name: true } })
|
||||||
|
|
||||||
|
// 删年度标准
|
||||||
|
const stdDeleted = await prisma.socialYearStandard.deleteMany({ where: { accountId: id } })
|
||||||
|
// 删员工社保记录
|
||||||
|
const srDeleted = await prisma.employeeSocialInsRecord.deleteMany({ where: { accountId: id } })
|
||||||
|
// 删员工公积金记录
|
||||||
|
const hrDeleted = await prisma.employeeHousingFundRecord.deleteMany({ where: { accountId: id } })
|
||||||
|
// 删月度处理记录
|
||||||
|
const mpDeleted = await prisma.socialMonthlyProcess.deleteMany({ where: { accountId: id } })
|
||||||
|
// 删账户
|
||||||
|
await prisma.socialAccount.delete({ where: { id } })
|
||||||
|
|
||||||
|
console.log(`[已删] ${account?.name} — 标准:${stdDeleted.count} 社保记录:${srDeleted.count} 公积金记录:${hrDeleted.count} 月度:${mpDeleted.count}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('\n删除完成')
|
||||||
|
}
|
||||||
|
|
||||||
|
main().then(() => process.exit(0)).catch(e => { console.error(e); process.exit(1) })
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
/**
|
||||||
|
* 批量更新公积金账户的 housingBaseMin/housingBaseMax
|
||||||
|
* 数据来源:各城市2025年度公积金缴存基数上下限
|
||||||
|
*/
|
||||||
|
import { PrismaClient } from '@prisma/client'
|
||||||
|
const prisma = new PrismaClient()
|
||||||
|
|
||||||
|
// 2025年度各城市公积金基数上下限
|
||||||
|
const CITY_LIMITS: Record<string, { min: number; max: number }> = {
|
||||||
|
'北京': { min: 2540, max: 35811 },
|
||||||
|
'上海': { min: 2690, max: 37302 },
|
||||||
|
'深圳': { min: 2360, max: 44265 },
|
||||||
|
'杭州': { min: 2490, max: 40694 },
|
||||||
|
'石家庄': { min: 2200, max: 26420 },
|
||||||
|
'天津': { min: 2320, max: 27861 },
|
||||||
|
'唐山': { min: 2200, max: 26420 }, // 河北省标准
|
||||||
|
}
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
const accounts = await prisma.socialAccount.findMany({ where: { type: 'HOUSING' } })
|
||||||
|
let updated = 0
|
||||||
|
let skipped = 0
|
||||||
|
|
||||||
|
for (const account of accounts) {
|
||||||
|
const limits = CITY_LIMITS[account.city]
|
||||||
|
if (!limits) {
|
||||||
|
console.log(`[跳过] ${account.name} (${account.city}) — 无该城市的公积金上下限数据`)
|
||||||
|
skipped++
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新该账户下所有年度标准的 housingBaseMin/housingBaseMax
|
||||||
|
const result = await prisma.socialYearStandard.updateMany({
|
||||||
|
where: { accountId: account.id },
|
||||||
|
data: {
|
||||||
|
housingBaseMin: limits.min,
|
||||||
|
housingBaseMax: limits.max,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
console.log(`[更新] ${account.name} (${account.city}) → 下限 ${limits.min} / 上限 ${limits.max},影响 ${result.count} 条标准`)
|
||||||
|
updated++
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`\n完成:更新 ${updated} 个账户,跳过 ${skipped} 个`)
|
||||||
|
}
|
||||||
|
|
||||||
|
main().then(() => process.exit(0)).catch(e => { console.error(e); process.exit(1) })
|
||||||
@@ -0,0 +1,99 @@
|
|||||||
|
/**
|
||||||
|
* 批量补齐无年度标准的账户:从旧 SocialInsuranceConfig / HousingFundConfig 继承数据
|
||||||
|
*/
|
||||||
|
import { PrismaClient } from '@prisma/client'
|
||||||
|
const prisma = new PrismaClient()
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
const accounts = await prisma.socialAccount.findMany()
|
||||||
|
let created = 0
|
||||||
|
let skipped = 0
|
||||||
|
|
||||||
|
for (const account of accounts) {
|
||||||
|
// 检查是否已有标准
|
||||||
|
const existing = await prisma.socialYearStandard.findFirst({
|
||||||
|
where: { accountId: account.id, isCurrent: true },
|
||||||
|
})
|
||||||
|
if (existing) {
|
||||||
|
skipped++
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查旧配置
|
||||||
|
let oldConfig: any = null
|
||||||
|
if (account.type === 'HOUSING') {
|
||||||
|
oldConfig = await prisma.housingFundConfig.findFirst({
|
||||||
|
where: { orgId: account.orgId, city: account.city },
|
||||||
|
orderBy: { effectiveFrom: 'desc' },
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
oldConfig = await prisma.socialInsuranceConfig.findFirst({
|
||||||
|
where: { orgId: account.orgId, city: account.city },
|
||||||
|
orderBy: { effectiveFrom: 'desc' },
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!oldConfig) {
|
||||||
|
console.log(`[跳过] ${account.type} ${account.name} (${account.city}) — 无旧配置可继承`)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查是否已有同 effectiveFrom 的标准(含历史标准)
|
||||||
|
const dupCheck = await prisma.socialYearStandard.findFirst({
|
||||||
|
where: { accountId: account.id, effectiveFrom: oldConfig.effectiveFrom },
|
||||||
|
})
|
||||||
|
if (dupCheck) {
|
||||||
|
// 把已有的设为 current
|
||||||
|
await prisma.socialYearStandard.updateMany({
|
||||||
|
where: { accountId: account.id, effectiveFrom: oldConfig.effectiveFrom },
|
||||||
|
data: { isCurrent: true },
|
||||||
|
})
|
||||||
|
console.log(`[修复] ${account.type} ${account.name} (${account.city}) — 已有标准设为 current`)
|
||||||
|
created++
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建年度标准
|
||||||
|
const std = await prisma.socialYearStandard.create({
|
||||||
|
data: {
|
||||||
|
orgId: account.orgId,
|
||||||
|
accountId: account.id,
|
||||||
|
// 社保比例
|
||||||
|
pensionOrg: oldConfig.pensionOrg || 16,
|
||||||
|
pensionEmp: oldConfig.pensionEmp || 8,
|
||||||
|
medicalOrg: oldConfig.medicalOrg || 9.8,
|
||||||
|
medicalEmp: oldConfig.medicalEmp || 2,
|
||||||
|
medicalOrgExtra: (oldConfig as any).medicalOrgExtra || 0,
|
||||||
|
medicalEmpExtra: (oldConfig as any).medicalEmpExtra || 0,
|
||||||
|
unemploymentOrg: oldConfig.unemploymentOrg || 0.5,
|
||||||
|
unemploymentEmp: oldConfig.unemploymentEmp || 0.5,
|
||||||
|
injuryOrg: oldConfig.injuryOrg || 0.2,
|
||||||
|
maternityOrg: oldConfig.maternityOrg || 0.8,
|
||||||
|
baseMin: oldConfig.baseMin || 6326,
|
||||||
|
baseMax: oldConfig.baseMax || 33891,
|
||||||
|
medicalBaseMin: oldConfig.medicalBaseMin || 0,
|
||||||
|
medicalBaseMax: oldConfig.medicalBaseMax || 0,
|
||||||
|
extraInsurances: oldConfig.extraInsurances || null,
|
||||||
|
// 公积金比例
|
||||||
|
housingOrg: (oldConfig as any).housingOrg || 12,
|
||||||
|
housingEmp: (oldConfig as any).housingEmp || 12,
|
||||||
|
housingBaseMin: 0,
|
||||||
|
housingBaseMax: 0,
|
||||||
|
// 最低工资
|
||||||
|
minWage: (oldConfig as any).minWage || 0,
|
||||||
|
// 生效信息
|
||||||
|
effectiveFrom: oldConfig.effectiveFrom,
|
||||||
|
effectiveTo: null,
|
||||||
|
isCurrent: true,
|
||||||
|
adjustmentDone: false,
|
||||||
|
createdBy: account.createdBy,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
console.log(`[创建] ${account.type} ${account.name} (${account.city}) ← ${oldConfig.effectiveFrom}`)
|
||||||
|
created++
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`\n完成:创建 ${created} 个标准,跳过 ${skipped} 个已有标准`)
|
||||||
|
}
|
||||||
|
|
||||||
|
main().then(() => process.exit(0)).catch(e => { console.error(e); process.exit(1) })
|
||||||
Reference in New Issue
Block a user