e1b5ae9aab
P0紧急修复(6项): - 草稿保存完整恢复所有字段(含socialAvgWage) - 补偿金批次从compensationBreakdown读取 - 违法解除风险确认UI - 合同结束日期前后校验(前后端双保险) P1高优先级(14项): - 离职日期联动社保/公积金截止月(15号规则) - 合规检查+工作交接改为软阻断(生成待办) - 补偿月数(N/N+1/2N/自定义)+计算基数(近12月/合同/自定义) - 解聘并入花名册操作栏(类型选择跳转向导) - 合同续签开始日期自动推导(原合同结束日+1天) - 年龄合规筛查(童工阻断/未成年工/退休警告) - 编辑入职日期后状态联动(待入职↔在职) - 转正移植到花名册操作栏+薪资回写 - 男职工无法选择三期 P2体验优化(9项): - "劳动合同"调整为"用工关系" - 费用结算新增剩余年假折算(300%日工资) - 身份证号全域改为"证件号码"(前后端18个文件) - 手机号查重 - 开具证明+合同续签移植到花名册操作栏 - 批量转正+批量开具证明 - 去掉用工办理模块 P3规划(2项): - 组织架构+审批流(Department/Position/ApprovalFlow/ApprovalInstance) - 客服工作台(Ticket/ChatSession+SUPPORT角色) 新增模型: Department/Position/ApprovalFlow/ApprovalInstance/Ticket/TicketMessage/ChatSession/ChatMessage 新增字段: Employee.departmentId/supervisorId 新增角色: SUPPORT Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
108 lines
3.2 KiB
TypeScript
108 lines
3.2 KiB
TypeScript
/**
|
||
* 审批流路由
|
||
* 提供审批流配置和实例管理
|
||
*/
|
||
import { Router } from 'express'
|
||
import { authMiddleware, AuthRequest } from '../middleware/auth'
|
||
import prisma from '../lib/prisma'
|
||
import { z } from 'zod'
|
||
import { processApproval, cancelApproval } from '../services/approval.service'
|
||
|
||
const router = Router()
|
||
|
||
const flowSchema = z.object({
|
||
type: z.string(), // LEAVE / TERMINATION / SALARY_CHANGE / OTHER
|
||
name: z.string().min(1),
|
||
enabled: z.boolean().default(true),
|
||
steps: z.array(z.object({
|
||
step: z.number().int().min(1).max(3),
|
||
approverType: z.enum(['SUPERVISOR', 'DEPT_HEAD', 'PERSON']),
|
||
approverId: z.string().optional(),
|
||
name: z.string(),
|
||
})).min(1, '至少一个审批步骤').max(3, '最多三个审批步骤'),
|
||
})
|
||
|
||
/** 获取审批流配置列表 */
|
||
router.get('/', authMiddleware, async (req: AuthRequest, res, next) => {
|
||
try {
|
||
const flows = await prisma.approvalFlow.findMany({
|
||
where: { orgId: req.user!.orgId! },
|
||
orderBy: { createdAt: 'asc' },
|
||
})
|
||
res.json({ success: true, data: flows })
|
||
} catch (err) {
|
||
next(err)
|
||
}
|
||
})
|
||
|
||
/** 创建/更新审批流配置(upsert by type) */
|
||
router.post('/', authMiddleware, async (req: AuthRequest, res, next) => {
|
||
try {
|
||
const data = flowSchema.parse(req.body)
|
||
const existing = await prisma.approvalFlow.findFirst({
|
||
where: { orgId: req.user!.orgId!, type: data.type },
|
||
})
|
||
let flow
|
||
if (existing) {
|
||
flow = await prisma.approvalFlow.update({
|
||
where: { id: existing.id },
|
||
data: { ...data, createdBy: req.user!.id },
|
||
})
|
||
} else {
|
||
flow = await prisma.approvalFlow.create({
|
||
data: {
|
||
...data,
|
||
orgId: req.user!.orgId!,
|
||
createdBy: req.user!.id,
|
||
},
|
||
})
|
||
}
|
||
res.json({ success: true, data: flow })
|
||
} catch (err) {
|
||
next(err)
|
||
}
|
||
})
|
||
|
||
/** 获取待我审批的实例 */
|
||
router.get('/pending', authMiddleware, async (req: AuthRequest, res, next) => {
|
||
try {
|
||
const instances = await prisma.approvalInstance.findMany({
|
||
where: { orgId: req.user!.orgId!, status: 'PENDING' },
|
||
orderBy: { createdAt: 'desc' },
|
||
include: { employee: { select: { id: true, name: true, department: true } } },
|
||
})
|
||
res.json({ success: true, data: instances })
|
||
} catch (err) {
|
||
next(err)
|
||
}
|
||
})
|
||
|
||
/** 处理审批 */
|
||
router.post('/:id/process', authMiddleware, async (req: AuthRequest, res, next) => {
|
||
try {
|
||
const { id } = req.params
|
||
const { result, comment } = req.body as { result: 'APPROVED' | 'REJECTED'; comment?: string }
|
||
if (!result || !['APPROVED', 'REJECTED'].includes(result)) {
|
||
throw { code: 'VALIDATION_ERROR', message: 'result 必须为 APPROVED 或 REJECTED' }
|
||
}
|
||
const approverName = req.user!.id || '审批人'
|
||
const outcome = await processApproval(req.user!.orgId!, id, req.user!.id, approverName, result, comment)
|
||
res.json({ success: true, data: outcome })
|
||
} catch (err) {
|
||
next(err)
|
||
}
|
||
})
|
||
|
||
/** 取消审批 */
|
||
router.post('/:id/cancel', authMiddleware, async (req: AuthRequest, res, next) => {
|
||
try {
|
||
const { id } = req.params
|
||
await cancelApproval(req.user!.orgId!, id)
|
||
res.json({ success: true })
|
||
} catch (err) {
|
||
next(err)
|
||
}
|
||
})
|
||
|
||
export default router
|