feat: AIHR 智能人力资源管理系统初始提交
- 员工花名册管理(加密存储、导入导出) - 薪酬管理(发薪批次、薪酬模版、加班费计算、工资条) - 社保公积金(多城市配置、版本管理、基数调整) - 解聘管理(6步流程、证据链、工作交接) - AI 助手(合同审查、风险预测、RAG 知识库) - Dashboard 仪表盘 - 设置与通知
This commit is contained in:
@@ -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
|
||||
Reference in New Issue
Block a user