Files
TurboHR/backend/prisma/seed.ts
T
selfrelease 0df8aa77d9 feat: AIHR 智能人力资源管理系统初始提交
- 员工花名册管理(加密存储、导入导出)
- 薪酬管理(发薪批次、薪酬模版、加班费计算、工资条)
- 社保公积金(多城市配置、版本管理、基数调整)
- 解聘管理(6步流程、证据链、工作交接)
- AI 助手(合同审查、风险预测、RAG 知识库)
- Dashboard 仪表盘
- 设置与通知
2026-07-24 13:53:11 +08:00

299 lines
14 KiB
TypeScript

import { PrismaClient } from '@prisma/client'
import bcrypt from 'bcryptjs'
import { encrypt } from '../src/lib/crypto'
const prisma = new PrismaClient()
// 社保计算(与 payroll.service.ts 一致)
function calcSocial(base: number, config: any) {
const actualBase = Math.min(Math.max(base, config.baseMin), config.baseMax)
const socialEmp = actualBase * (config.pensionEmp + config.medicalEmp + config.unemploymentEmp) / 100
const socialOrg = actualBase * (config.pensionOrg + config.medicalOrg + config.unemploymentOrg + config.injuryOrg + config.maternityOrg) / 100
return { socialEmp: Math.round(socialEmp * 100) / 100, socialOrg: Math.round(socialOrg * 100) / 100 }
}
function calcHousing(base: number, config: any) {
const actualBase = Math.min(Math.max(base, config.baseMin), config.baseMax)
const housingEmp = actualBase * config.housingEmp / 100
const housingOrg = actualBase * config.housingOrg / 100
return { housingEmp: Math.round(housingEmp * 100) / 100, housingOrg: Math.round(housingOrg * 100) / 100 }
}
function calcTax(taxableIncome: number): number {
if (taxableIncome <= 0) return 0
let tax = 0
if (taxableIncome <= 36000) tax = taxableIncome * 0.03
else if (taxableIncome <= 144000) tax = taxableIncome * 0.10 - 2520
else if (taxableIncome <= 300000) tax = taxableIncome * 0.20 - 16920
else if (taxableIncome <= 420000) tax = taxableIncome * 0.25 - 31920
else if (taxableIncome <= 660000) tax = taxableIncome * 0.30 - 52920
else if (taxableIncome <= 960000) tax = taxableIncome * 0.35 - 85920
else tax = taxableIncome * 0.45 - 181920
return Math.max(0, Math.round(tax * 100) / 100)
}
// 9名员工完整数据
const EMPLOYEES = [
{ name: '张伟', gender: '男', dept: '技术部', phone: '13900000001', idCard: '310101199001011234', salary: 18000, hireDate: '2023-03-01', socialBase: 18000, housingBase: 18000, specialDeduction: 2000, contractType: 'FIXED', years: 3, probation: 3, probationSalary: 14400, bank: '工商银行', account: '6222021234567890001', emergency: '张父', emergencyPhone: '13800001001', address: '上海市浦东新区张江路100号' },
{ name: '李娜', gender: '女', dept: '技术部', phone: '13900000002', idCard: '310102199203052345', salary: 15000, hireDate: '2023-06-15', socialBase: 15000, housingBase: 15000, specialDeduction: 1000, contractType: 'FIXED', years: 3, probation: 2, probationSalary: 12000, bank: '建设银行', account: '6227001234567890002', emergency: '李母', emergencyPhone: '13800001002', address: '上海市徐汇区漕河泾50号', pregnant: true },
{ name: '王强', gender: '男', dept: '销售部', phone: '13900000003', idCard: '310103198812103456', salary: 12000, hireDate: '2024-01-10', socialBase: 12000, housingBase: 12000, specialDeduction: 3000, contractType: 'FIXED', years: 3, probation: 3, probationSalary: 9600, bank: '招商银行', account: '6225881234567890003', emergency: '王妻', emergencyPhone: '13800001003', address: '上海市闵行区莘庄路200号' },
{ name: '赵敏', gender: '女', dept: '人事部', phone: '13900000004', idCard: '310104199506154567', salary: 10000, hireDate: '2022-09-01', socialBase: 10000, housingBase: 10000, specialDeduction: 1500, contractType: 'UNFIXED', years: 0, probation: 0, probationSalary: 0, bank: '农业银行', account: '6228481234567890004', emergency: '赵父', emergencyPhone: '13800001004', address: '上海市黄浦区南京东路300号' },
{ name: '陈刚', gender: '男', dept: '销售部', phone: '13900000005', idCard: '310105199907205678', salary: 8000, hireDate: '2024-07-01', socialBase: 8000, housingBase: 8000, specialDeduction: 0, contractType: 'FIXED', years: 3, probation: 2, probationSalary: 6400, bank: '中国银行', account: '6217001234567890005', emergency: '陈母', emergencyPhone: '13800001005', address: '上海市杨浦区五角场400号' },
{ name: '刘洋', gender: '男', dept: '技术部', phone: '13900000006', idCard: '310106198504016789', salary: 22000, hireDate: '2021-04-01', socialBase: 33891, housingBase: 33891, specialDeduction: 4000, contractType: 'UNFIXED', years: 0, probation: 0, probationSalary: 0, bank: '交通银行', account: '6222601234567890006', emergency: '刘妻', emergencyPhone: '13800001006', address: '上海市长宁区中山公园500号' },
{ name: '周婷', gender: '女', dept: '财务部', phone: '13900000007', idCard: '310107199311157890', salary: 13000, hireDate: '2023-11-15', socialBase: 13000, housingBase: 13000, specialDeduction: 2500, contractType: 'FIXED', years: 3, probation: 2, probationSalary: 10400, bank: '浦发银行', account: '6225161234567890007', emergency: '周父', emergencyPhone: '13800001007', address: '上海市静安区南京西路600号' },
{ name: '孙磊', gender: '男', dept: '技术部', phone: '13900000008', idCard: '310108199008018901', salary: 16000, hireDate: '2022-06-01', socialBase: 16000, housingBase: 16000, specialDeduction: 1000, contractType: 'FIXED', years: 3, probation: 3, probationSalary: 12800, bank: '民生银行', account: '6226161234567890008', emergency: '孙母', emergencyPhone: '13800001008', address: '上海市虹口区四川北路700号' },
{ name: '吴芳', gender: '女', dept: '销售部', phone: '13900000009', idCard: '310109199702159012', salary: 9000, hireDate: '2025-02-15', socialBase: 9000, housingBase: 9000, specialDeduction: 500, contractType: 'FIXED', years: 3, probation: 2, probationSalary: 7200, bank: '光大银行', account: '6226621234567890009', emergency: '吴夫', emergencyPhone: '13800001009', address: '上海市宝山区牡丹江路800号' },
]
async function main() {
// 1. 清空所有数据(按依赖顺序删除)
console.log('清空现有数据...')
await prisma.notificationLog.deleteMany()
await prisma.auditLog.deleteMany()
await prisma.batchEntry.deleteMany()
await prisma.payrollBatch.deleteMany()
await prisma.payslipItem.deleteMany()
await prisma.salaryChangeRecord.deleteMany()
await prisma.payslip.deleteMany()
await prisma.overtimeRecord.deleteMany()
await prisma.terminationRecord.deleteMany()
await prisma.riskItem.deleteMany()
await prisma.employeeAttachment.deleteMany()
await prisma.disciplinaryRecord.deleteMany()
await prisma.attendanceRecord.deleteMany()
await prisma.trainingRecord.deleteMany()
await prisma.performanceRecord.deleteMany()
await prisma.laborContract.deleteMany()
await prisma.contractConfirmLink.deleteMany()
await prisma.onboardingLink.deleteMany()
await prisma.employee.deleteMany()
await prisma.socialInsuranceConfig.deleteMany()
await prisma.notificationSetting.deleteMany()
await prisma.user.deleteMany()
await prisma.organization.deleteMany()
console.log('数据已清空')
// 2. 创建企业
const org = await prisma.organization.create({
data: {
name: '智云科技有限公司',
plan: 'PRO',
maxEmployees: 50,
city: '上海',
payrollFrequency: 1,
},
})
console.log('企业已创建:', org.name)
// 3. 创建管理员
const passwordHash = await bcrypt.hash('12345678', 10)
const admin = await prisma.user.create({
data: {
orgId: org.id,
phone: '13800000001',
name: '管理员',
passwordHash,
role: 'ADMIN',
},
})
console.log('管理员已创建:', admin.phone)
// 4. 创建社保配置(上海标准)
await prisma.socialInsuranceConfig.create({
data: {
orgId: org.id,
city: '上海',
pensionOrg: 16,
pensionEmp: 8,
medicalOrg: 9.8,
medicalEmp: 2,
unemploymentOrg: 0.5,
unemploymentEmp: 0.5,
injuryOrg: 0.2,
maternityOrg: 0.8,
baseMin: 7384,
baseMax: 36921,
effectiveFrom: '2025-07',
createdBy: admin.id,
},
})
console.log('社保配置已创建')
// 4.5 创建公积金配置(上海标准)
await prisma.housingFundConfig.create({
data: {
orgId: org.id,
city: '上海',
housingOrg: 7,
housingEmp: 7,
baseMin: 7384,
baseMax: 36921,
effectiveFrom: '2025-07',
createdBy: admin.id,
},
})
console.log('公积金配置已创建')
// 5. 创建通知设置
await prisma.notificationSetting.create({
data: {
orgId: org.id,
contractExpiry: true,
expiryDays: 30,
contractUnsigned: true,
overtimeAlert: true,
payslipReady: true,
payrollDay: 10,
socialInsDay: 15,
housingFundDay: 15,
taxDay: 15,
},
})
// 6. 创建薪酬模版(预置项)
const defaultItems: { name: string; code: string; type: 'INPUT' | 'CALCULATED'; formula: string | null; order: number; isDefault: boolean; isEditable: boolean }[] = [
{ name: '基本工资', code: 'baseSalary', type: 'INPUT', formula: null, order: 1, isDefault: true, isEditable: true },
{ name: '加班费', code: 'overtimePay', type: 'CALCULATED', formula: 'weekdayOvertimePay + weekendOvertimePay + holidayOvertimePay', order: 2, isDefault: true, isEditable: false },
{ name: '津贴补贴', code: 'allowance', type: 'INPUT', formula: null, order: 3, isDefault: true, isEditable: true },
{ name: '奖金', code: 'bonus', type: 'INPUT', formula: null, order: 4, isDefault: true, isEditable: true },
{ name: '扣款', code: 'deduction', type: 'INPUT', formula: null, order: 5, isDefault: true, isEditable: true },
{ name: '应发合计', code: 'totalPay', type: 'CALCULATED', formula: 'baseSalary + overtimePay + allowance + bonus - deduction', order: 6, isDefault: true, isEditable: false },
{ name: '个人社保', code: 'socialEmp', type: 'CALCULATED', formula: 'SOCIAL_EMP', order: 7, isDefault: true, isEditable: false },
{ name: '个人公积金', code: 'housingEmp', type: 'CALCULATED', formula: 'HOUSING_EMP', order: 8, isDefault: true, isEditable: false },
{ name: '个人所得税', code: 'tax', type: 'CALCULATED', formula: 'TAX', order: 9, isDefault: true, isEditable: false },
{ name: '实发工资', code: 'netPay', type: 'CALCULATED', formula: 'totalPay - socialEmp - housingEmp - tax', order: 10, isDefault: true, isEditable: false },
]
for (const item of defaultItems) {
await prisma.payslipItem.create({
data: { orgId: org.id, ...item },
})
}
console.log('薪酬模版已创建')
// 7. 创建9名员工 + 合同
for (let i = 0; i < EMPLOYEES.length; i++) {
const e = EMPLOYEES[i]
const emp = await prisma.employee.create({
data: {
orgId: org.id,
name: e.name,
department: e.dept,
hireDate: new Date(e.hireDate),
monthlySalary: encrypt(String(e.salary)),
phone: e.phone,
idCardNumber: encrypt(e.idCard),
gender: e.gender,
socialInsBase: e.socialBase,
housingFundBase: e.housingBase,
specialDeduction: e.specialDeduction,
bankName: e.bank,
bankAccount: encrypt(e.account),
emergencyContact: e.emergency,
emergencyPhone: e.emergencyPhone,
address: e.address,
isPregnant: e.pregnant || false,
createdBy: admin.id,
},
})
// 创建合同
const startDate = new Date(e.hireDate)
const endDate = e.contractType === 'FIXED'
? new Date(startDate.getFullYear() + e.years, startDate.getMonth(), startDate.getDate() - 1)
: null
await prisma.laborContract.create({
data: {
orgId: org.id,
employeeId: emp.id,
signDate: new Date(e.hireDate),
startDate,
endDate,
contractType: e.contractType as any,
signMethod: 'PAPER',
contractYears: e.years,
probationMonths: e.probation,
probationSalary: e.probationSalary,
createdBy: admin.id,
},
})
console.log(`员工 ${i + 1}/9 已创建: ${e.name} - ${e.dept} - ¥${e.salary}/月`)
}
// 8. 生成 1-6 月历史工资条(已发布),使 7 月累计预扣个税有 YTD 数据
console.log('\n生成 1-6 月历史工资条...')
const socialConfig = await prisma.socialInsuranceConfig.findFirst({ where: { orgId: org.id, isCurrent: true } })
const housingConfig = await prisma.housingFundConfig.findFirst({ where: { orgId: org.id, isCurrent: true } })
const allEmployees = await prisma.employee.findMany({ where: { orgId: org.id } })
for (const emp of allEmployees) {
// 跳过 2026 年之后入职的员工
const hireYear = emp.hireDate.getFullYear()
if (hireYear > 2026) continue
const hireMonth = hireYear === 2026 ? emp.hireDate.getMonth() + 1 : 1
let ytdIncome = 0, ytdSocialEmp = 0, ytdHousingEmp = 0, ytdTaxDeducted = 0
for (let m = 1; m <= 6; m++) {
if (m < hireMonth) continue
const monthStr = `2026-${String(m).padStart(2, '0')}`
const baseSalary = emp.socialInsBase || 0 // 用社保基数作为基本工资(简化)
const social = calcSocial(emp.socialInsBase || baseSalary, socialConfig)
const housing = calcHousing(emp.housingFundBase || baseSalary, housingConfig || socialConfig)
const totalPay = baseSalary
const specialDeduction = emp.specialDeduction * m
ytdIncome += totalPay
ytdSocialEmp += social.socialEmp
ytdHousingEmp += housing.housingEmp
const ytdTaxableIncome = Math.max(0, ytdIncome - 5000 * m - ytdSocialEmp - ytdHousingEmp - specialDeduction)
const ytdTax = calcTax(ytdTaxableIncome)
const monthTax = Math.max(0, Math.round((ytdTax - ytdTaxDeducted) * 100) / 100)
ytdTaxDeducted += monthTax
const netPay = Math.round((totalPay - social.socialEmp - housing.housingEmp - monthTax) * 100) / 100
await prisma.payslip.create({
data: {
org: { connect: { id: org.id } },
employee: { connect: { id: emp.id } },
month: monthStr,
baseSalary,
overtimePay: 0,
allowance: 0,
deduction: 0,
bonus: 0,
totalPay,
socialEmp: social.socialEmp,
housingEmp: housing.housingEmp,
tax: monthTax,
netPay,
ytdIncome,
ytdTaxDeducted,
ytdSocialEmp,
ytdHousingEmp,
status: 'PUBLISHED',
publishedAt: new Date(`${monthStr}-10T10:00:00Z`),
confirmedAt: new Date(`${monthStr}-12T10:00:00Z`),
},
})
}
console.log(` ${emp.name}: 1-6月工资条已生成`)
}
console.log('\n===== 示例数据创建完成 =====')
console.log(`企业: ${org.name}`)
console.log(`管理员: 13800000001 / 密码: 12345678`)
console.log(`员工: ${EMPLOYEES.length}`)
console.log('社保配置: 上海标准')
console.log('薪酬模版: 10项预置')
}
main()
.catch((e) => {
console.error(e)
process.exit(1)
})
.finally(async () => {
await prisma.$disconnect()
})