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>
163 lines
4.5 KiB
TypeScript
163 lines
4.5 KiB
TypeScript
/**
|
|
* 审批流引擎
|
|
* 支持最多 3 步审批(发起人 → 直属上级 → 部门负责人)
|
|
*/
|
|
import prisma from '../lib/prisma'
|
|
|
|
interface ApprovalStep {
|
|
step: number
|
|
approverType: 'SUPERVISOR' | 'DEPT_HEAD' | 'PERSON'
|
|
approverId?: string
|
|
name: string
|
|
}
|
|
|
|
interface ApprovalRecord {
|
|
step: number
|
|
approverId: string
|
|
approverName: string
|
|
result: 'APPROVED' | 'REJECTED'
|
|
comment?: string
|
|
timestamp: string
|
|
}
|
|
|
|
/**
|
|
* 创建审批实例
|
|
*/
|
|
export async function createApprovalInstance(
|
|
orgId: string,
|
|
userId: string,
|
|
type: string,
|
|
bizId: string,
|
|
bizType: string,
|
|
employeeId: string,
|
|
): Promise<{ instance: any; firstApprover?: any }> {
|
|
// 查找该类型的审批流配置
|
|
const flow = await prisma.approvalFlow.findFirst({
|
|
where: { orgId, type, enabled: true },
|
|
})
|
|
if (!flow) {
|
|
// 无审批流配置,直接通过
|
|
return { instance: null }
|
|
}
|
|
|
|
const steps = flow.steps as unknown as ApprovalStep[]
|
|
if (!steps || steps.length === 0) {
|
|
return { instance: null }
|
|
}
|
|
|
|
const instance = await prisma.approvalInstance.create({
|
|
data: {
|
|
orgId,
|
|
flowId: flow.id,
|
|
type,
|
|
bizId,
|
|
bizType,
|
|
status: 'PENDING',
|
|
currentStep: 1,
|
|
approvals: [],
|
|
employeeId,
|
|
createdBy: userId,
|
|
},
|
|
})
|
|
|
|
// 计算第一步审批人
|
|
const firstApprover = await resolveApprover(orgId, employeeId, steps[0])
|
|
return { instance, firstApprover }
|
|
}
|
|
|
|
/**
|
|
* 根据步骤配置解析审批人
|
|
*/
|
|
async function resolveApprover(orgId: string, employeeId: string, step: ApprovalStep): Promise<any> {
|
|
const employee = await prisma.employee.findFirst({
|
|
where: { id: employeeId, orgId },
|
|
include: { dept: true, supervisor: true },
|
|
})
|
|
if (!employee) return null
|
|
|
|
if (step.approverType === 'SUPERVISOR') {
|
|
return employee.supervisor ? { id: employee.supervisor.id, name: employee.supervisor.name } : null
|
|
} else if (step.approverType === 'DEPT_HEAD') {
|
|
// 部门负责人暂用部门创建人(简化实现)
|
|
if (employee.dept) {
|
|
return { id: employee.dept.createdBy, name: '部门负责人' }
|
|
}
|
|
return null
|
|
} else if (step.approverType === 'PERSON' && step.approverId) {
|
|
const approver = await prisma.employee.findFirst({ where: { id: step.approverId, orgId } })
|
|
return approver ? { id: approver.id, name: approver.name } : null
|
|
}
|
|
return null
|
|
}
|
|
|
|
/**
|
|
* 处理审批
|
|
*/
|
|
export async function processApproval(
|
|
orgId: string,
|
|
instanceId: string,
|
|
approverId: string,
|
|
approverName: string,
|
|
result: 'APPROVED' | 'REJECTED',
|
|
comment?: string,
|
|
): Promise<{ status: string; nextApprover?: any }> {
|
|
const instance = await prisma.approvalInstance.findFirst({
|
|
where: { id: instanceId, orgId },
|
|
include: { flow: true },
|
|
})
|
|
if (!instance) throw { code: 'NOT_FOUND', message: '审批实例不存在' }
|
|
if (instance.status !== 'PENDING') throw { code: 'VALIDATION_ERROR', message: '审批实例已处理' }
|
|
|
|
const steps = instance.flow.steps as unknown as ApprovalStep[]
|
|
const currentStepConfig = steps.find(s => s.step === instance.currentStep)
|
|
if (!currentStepConfig) throw { code: 'VALIDATION_ERROR', message: '步骤配置错误' }
|
|
|
|
// 记录审批结果
|
|
const approvals = (instance.approvals as unknown as ApprovalRecord[]) || []
|
|
approvals.push({
|
|
step: instance.currentStep,
|
|
approverId,
|
|
approverName,
|
|
result,
|
|
comment,
|
|
timestamp: new Date().toISOString(),
|
|
})
|
|
|
|
if (result === 'REJECTED') {
|
|
await prisma.approvalInstance.update({
|
|
where: { id: instanceId },
|
|
data: { status: 'REJECTED', approvals: approvals as any },
|
|
})
|
|
return { status: 'REJECTED' }
|
|
}
|
|
|
|
// 查找下一步
|
|
const nextStepConfig = steps.find(s => s.step === instance.currentStep + 1)
|
|
if (!nextStepConfig) {
|
|
// 全部通过
|
|
await prisma.approvalInstance.update({
|
|
where: { id: instanceId },
|
|
data: { status: 'APPROVED', approvals: approvals as any },
|
|
})
|
|
return { status: 'APPROVED' }
|
|
}
|
|
|
|
// 进入下一步
|
|
const nextApprover = await resolveApprover(orgId, instance.employeeId || '', nextStepConfig)
|
|
await prisma.approvalInstance.update({
|
|
where: { id: instanceId },
|
|
data: { currentStep: instance.currentStep + 1, approvals: approvals as any },
|
|
})
|
|
return { status: 'PENDING', nextApprover }
|
|
}
|
|
|
|
/**
|
|
* 取消审批
|
|
*/
|
|
export async function cancelApproval(orgId: string, instanceId: string): Promise<void> {
|
|
await prisma.approvalInstance.update({
|
|
where: { id: instanceId },
|
|
data: { status: 'CANCELLED' },
|
|
})
|
|
}
|