9512b555ee
- ESignRecord 模型新增 scene 字段(CONTRACT/RESIGNATION/POLICY/PAYSLIP/ONBOARDING) - Organization 模型新增3个电子签开关:esignPolicyEnabled/esignPayslipEnabled/esignOnboardingEnabled - 设置页面企业信息新增电子签署设置区域,3个开关各自独立,缺省关闭 - 规章制度签收:开启电子签后,员工阅读确认时自动创建POLICY场景签署记录 - 工资条确认:开启电子签后,员工确认工资条时自动创建PAYSLIP场景签署记录 - 入职文件签署:开启电子签后,HR审批通过入职流程时自动创建ONBOARDING场景签署记录 - 电子签署列表增加场景筛选下拉(全部场景/劳动合同/离职协议/规章制度/工资条/入职文件) - 管理端和员工端列表均展示场景标签(基于scene字段,替代硬编码判断) - 合同和离职流程的esign调用已加scene参数
247 lines
9.0 KiB
TypeScript
247 lines
9.0 KiB
TypeScript
import { Router } from 'express'
|
|
import bcrypt from 'bcryptjs'
|
|
import prisma from '../lib/prisma'
|
|
import { authMiddleware, AuthRequest } from '../middleware/auth'
|
|
import { requireAdmin } from '../middleware/rbac'
|
|
import { z } from 'zod'
|
|
import { checkAndUpdateRetirementPolicy, confirmRetirementPolicy } from '../services/retirement.service'
|
|
|
|
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, city: true, contactName: true, contactPhone: true, payrollFrequency: true, retirementReminderEnabled: true, esignPolicyEnabled: true, esignPayslipEnabled: true, esignOnboardingEnabled: true, createdAt: true },
|
|
})
|
|
res.json({ success: true, data: org })
|
|
} catch (err) {
|
|
next(err)
|
|
}
|
|
})
|
|
|
|
// 更新企业信息
|
|
router.put('/org', requireAdmin, async (req: AuthRequest, res, next) => {
|
|
try {
|
|
const { name, payrollFrequency, city, contactName, contactPhone, retirementReminderEnabled, esignPolicyEnabled, esignPayslipEnabled, esignOnboardingEnabled } = req.body as { name?: string; payrollFrequency?: number; city?: string; contactName?: string; contactPhone?: string; retirementReminderEnabled?: boolean; esignPolicyEnabled?: boolean; esignPayslipEnabled?: boolean; esignOnboardingEnabled?: boolean }
|
|
const updateData: any = {}
|
|
if (name) updateData.name = name
|
|
if (payrollFrequency !== undefined) updateData.payrollFrequency = payrollFrequency
|
|
if (city !== undefined) updateData.city = city
|
|
if (contactName !== undefined) updateData.contactName = contactName
|
|
if (contactPhone !== undefined) updateData.contactPhone = contactPhone
|
|
if (retirementReminderEnabled !== undefined) updateData.retirementReminderEnabled = retirementReminderEnabled
|
|
if (esignPolicyEnabled !== undefined) updateData.esignPolicyEnabled = esignPolicyEnabled
|
|
if (esignPayslipEnabled !== undefined) updateData.esignPayslipEnabled = esignPayslipEnabled
|
|
if (esignOnboardingEnabled !== undefined) updateData.esignOnboardingEnabled = esignOnboardingEnabled
|
|
const org = await prisma.organization.update({
|
|
where: { id: req.user!.orgId },
|
|
data: updateData,
|
|
select: { id: true, name: true, plan: true, maxEmployees: true, city: true, contactName: true, contactPhone: true, payrollFrequency: true, retirementReminderEnabled: true, esignPolicyEnabled: true, esignPayslipEnabled: true, esignOnboardingEnabled: 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', requireAdmin, 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', requireAdmin, 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', requireAdmin, 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', requireAdmin, 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', requireAdmin, 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
|
|
|
|
// ========== 退休政策 ==========
|
|
|
|
// 获取当前生效的退休政策和待确认政策
|
|
router.get('/retirement-policy', async (req: AuthRequest, res, next) => {
|
|
try {
|
|
const orgId = req.user!.orgId
|
|
const org = await prisma.organization.findUnique({ where: { id: orgId }, select: { retirementReminderEnabled: true } })
|
|
|
|
// 如果开启了退休提醒,懒加载检查是否需要获取新政策
|
|
if (org?.retirementReminderEnabled) {
|
|
await checkAndUpdateRetirementPolicy(orgId).catch(() => {})
|
|
}
|
|
|
|
// 查询当前生效的政策
|
|
const confirmedPolicy = await prisma.retirementPolicy.findFirst({
|
|
where: { orgId, status: 'CONFIRMED' },
|
|
orderBy: { createdAt: 'desc' },
|
|
})
|
|
|
|
// 查询待确认的政策
|
|
const pendingPolicy = await prisma.retirementPolicy.findFirst({
|
|
where: { orgId, status: 'PENDING' },
|
|
orderBy: { createdAt: 'desc' },
|
|
})
|
|
|
|
res.json({
|
|
success: true,
|
|
data: {
|
|
enabled: org?.retirementReminderEnabled || false,
|
|
confirmed: confirmedPolicy,
|
|
pending: pendingPolicy,
|
|
},
|
|
})
|
|
} catch (err) {
|
|
next(err)
|
|
}
|
|
})
|
|
|
|
// 确认退休政策
|
|
router.post('/retirement-policy/:id/confirm', requireAdmin, async (req: AuthRequest, res, next) => {
|
|
try {
|
|
await confirmRetirementPolicy(req.user!.orgId, req.params.id)
|
|
res.json({ success: true, message: '退休政策已确认生效' })
|
|
} catch (err: any) {
|
|
res.status(400).json({ success: false, error: { code: 'BAD_REQUEST', message: err.message || '确认失败' } })
|
|
}
|
|
})
|
|
|