feat: AIHR 智能人力资源管理系统初始提交

- 员工花名册管理(加密存储、导入导出)
- 薪酬管理(发薪批次、薪酬模版、加班费计算、工资条)
- 社保公积金(多城市配置、版本管理、基数调整)
- 解聘管理(6步流程、证据链、工作交接)
- AI 助手(合同审查、风险预测、RAG 知识库)
- Dashboard 仪表盘
- 设置与通知
This commit is contained in:
selfrelease
2026-07-24 13:53:11 +08:00
commit 0df8aa77d9
109 changed files with 38190 additions and 0 deletions
+188
View File
@@ -0,0 +1,188 @@
import { Router } from 'express'
import bcrypt from 'bcryptjs'
import prisma from '../lib/prisma'
import { authMiddleware, AuthRequest } from '../middleware/auth'
import { z } from 'zod'
const router = Router()
router.use(authMiddleware)
const updateUserSchema = z.object({
name: z.string().min(1).optional(),
phone: z.string().regex(/^1[3-9]\d{9}$/).optional(),
email: z.string().email().optional(),
role: z.enum(['ADMIN', 'HR', 'VIEWER']).optional(),
})
const createUserSchema = z.object({
name: z.string().min(1, '姓名不能为空'),
phone: z.string().regex(/^1[3-9]\d{9}$/, '手机号格式不正确'),
password: z.string().min(6, '密码至少6位'),
role: z.enum(['ADMIN', 'HR', 'VIEWER']).default('HR'),
})
// 获取企业信息
router.get('/org', async (req: AuthRequest, res, next) => {
try {
const org = await prisma.organization.findUnique({
where: { id: req.user!.orgId },
select: { id: true, name: true, plan: true, maxEmployees: true, createdAt: true },
})
res.json({ success: true, data: org })
} catch (err) {
next(err)
}
})
// 更新企业信息
router.put('/org', async (req: AuthRequest, res, next) => {
try {
const { name, payrollFrequency } = req.body as { name?: string; payrollFrequency?: number }
const updateData: any = {}
if (name) updateData.name = name
if (payrollFrequency !== undefined) updateData.payrollFrequency = payrollFrequency
const org = await prisma.organization.update({
where: { id: req.user!.orgId },
data: updateData,
select: { id: true, name: true, plan: true, maxEmployees: true, payrollFrequency: true },
})
res.json({ success: true, data: org })
} catch (err) {
next(err)
}
})
// 获取用户列表
router.get('/users', async (req: AuthRequest, res, next) => {
try {
const users = await prisma.user.findMany({
where: { orgId: req.user!.orgId },
select: { id: true, name: true, phone: true, email: true, role: true, disabled: true, createdAt: true, lastLoginAt: true },
orderBy: { createdAt: 'asc' },
})
res.json({ success: true, data: users })
} catch (err) {
next(err)
}
})
// 添加用户
router.post('/users', async (req: AuthRequest, res, next) => {
try {
const data = createUserSchema.parse(req.body)
const existing = await prisma.user.findFirst({ where: { phone: data.phone, orgId: req.user!.orgId } })
if (existing) {
return res.status(400).json({ success: false, error: { code: 'DUPLICATE', message: '该手机号已存在' } })
}
const passwordHash = await bcrypt.hash(data.password, 10)
const user = await prisma.user.create({
data: {
orgId: req.user!.orgId,
name: data.name,
phone: data.phone,
passwordHash,
role: data.role,
},
select: { id: true, name: true, phone: true, role: true },
})
res.json({ success: true, data: user })
} catch (err) {
next(err)
}
})
// 更新用户
router.put('/users/:id', async (req: AuthRequest, res, next) => {
try {
const data = updateUserSchema.parse(req.body)
const user = await prisma.user.update({
where: { id: req.params.id },
data: data,
select: { id: true, name: true, phone: true, email: true, role: true, disabled: true },
})
res.json({ success: true, data: user })
} catch (err) {
next(err)
}
})
// 删除用户
router.delete('/users/:id', async (req: AuthRequest, res, next) => {
try {
if (req.params.id === req.user!.id) {
return res.status(400).json({ success: false, error: { code: 'BAD_REQUEST', message: '不能删除自己' } })
}
await prisma.user.delete({ where: { id: req.params.id } })
res.json({ success: true })
} catch (err) {
next(err)
}
})
// 禁用/启用用户
router.patch('/users/:id/toggle-disable', async (req: AuthRequest, res, next) => {
try {
if (req.params.id === req.user!.id) {
return res.status(400).json({ success: false, error: { code: 'BAD_REQUEST', message: '不能禁用自己' } })
}
const existing = await prisma.user.findUnique({ where: { id: req.params.id } })
if (!existing) {
return res.status(404).json({ success: false, error: { code: 'NOT_FOUND', message: '用户不存在' } })
}
const user = await prisma.user.update({
where: { id: req.params.id },
data: { disabled: !existing.disabled },
select: { id: true, name: true, disabled: true },
})
res.json({ success: true, data: user })
} catch (err) {
next(err)
}
})
// 切换套餐
router.put('/plan', async (req: AuthRequest, res, next) => {
try {
const { plan } = req.body as { plan: 'FREE' | 'PRO' | 'ENTERPRISE' }
if (!['FREE', 'PRO', 'ENTERPRISE'].includes(plan)) {
return res.status(400).json({ success: false, error: { code: 'BAD_REQUEST', message: '无效的套餐' } })
}
const maxEmployees = plan === 'FREE' ? 10 : plan === 'PRO' ? 100 : 999999
const org = await prisma.organization.update({
where: { id: req.user!.orgId },
data: { plan, maxEmployees },
select: { id: true, name: true, plan: true, maxEmployees: true },
})
res.json({ success: true, data: org })
} catch (err) {
next(err)
}
})
// 用量统计
router.get('/usage', async (req: AuthRequest, res, next) => {
try {
const orgId = req.user!.orgId
const [employeeCount, aiConversations, contracts] = await Promise.all([
prisma.employee.count({ where: { orgId } }),
prisma.aIConversation.count({ where: { orgId } }),
prisma.laborContract.count({ where: { orgId } }),
])
const org = await prisma.organization.findUnique({ where: { id: orgId }, select: { plan: true, maxEmployees: true } })
res.json({
success: true,
data: {
plan: org?.plan || 'FREE',
maxEmployees: org?.maxEmployees || 10,
employeeCount,
aiConversations,
contracts,
employeeUsage: `${employeeCount}/${org?.maxEmployees || 10}`,
},
})
} catch (err) {
next(err)
}
})
export default router