0df8aa77d9
- 员工花名册管理(加密存储、导入导出) - 薪酬管理(发薪批次、薪酬模版、加班费计算、工资条) - 社保公积金(多城市配置、版本管理、基数调整) - 解聘管理(6步流程、证据链、工作交接) - AI 助手(合同审查、风险预测、RAG 知识库) - Dashboard 仪表盘 - 设置与通知
273 lines
10 KiB
TypeScript
273 lines
10 KiB
TypeScript
import { Router } from 'express'
|
|
import { authMiddleware, AuthRequest } from '../middleware/auth'
|
|
import { auditLog } from '../middleware/auditLog'
|
|
import { terminationChecklistSchema } from '../schemas/termination.schema'
|
|
import { createTermination, createResignation, revokeTermination, getTerminations, getChecklistForReason, assessRisk, batchTerminatePreview, batchTerminate, createDraft, updateDraft, submitForApproval, approveTermination, rejectTermination, executeTermination, cancelTermination, getDrafts, getTerminationDetail, getDefaultHandoverItems } from '../services/termination.service'
|
|
import prisma from '../lib/prisma'
|
|
|
|
const router = Router()
|
|
|
|
router.get('/', authMiddleware, async (req: AuthRequest, res, next) => {
|
|
try {
|
|
const page = parseInt(req.query.page as string) || 1
|
|
const pageSize = parseInt(req.query.pageSize as string) || 20
|
|
const result = await getTerminations(req.user!.orgId, page, pageSize)
|
|
res.json({ success: true, data: result })
|
|
} catch (err) {
|
|
next(err)
|
|
}
|
|
})
|
|
|
|
router.get('/checklist/:reason', authMiddleware, async (req: AuthRequest, res, next) => {
|
|
try {
|
|
const employeeId = req.query.employeeId as string
|
|
let employee: any = undefined
|
|
|
|
if (employeeId) {
|
|
const emp = await prisma.employee.findFirst({
|
|
where: { id: employeeId, orgId: req.user!.orgId },
|
|
include: {
|
|
trainingRecords: true,
|
|
},
|
|
})
|
|
if (emp) {
|
|
employee = {
|
|
isInMedicalPeriod: emp.isInMedicalPeriod,
|
|
trainingRecords: emp.trainingRecords,
|
|
}
|
|
}
|
|
}
|
|
|
|
const checklist = getChecklistForReason(req.params.reason, employee)
|
|
res.json({ success: true, data: checklist })
|
|
} catch (err) {
|
|
next(err)
|
|
}
|
|
})
|
|
|
|
router.get('/assess/:employeeId', authMiddleware, async (req: AuthRequest, res, next) => {
|
|
try {
|
|
const employee = await prisma.employee.findFirst({ where: { id: req.params.employeeId, orgId: req.user!.orgId } })
|
|
if (!employee) {
|
|
return res.status(404).json({ success: false, error: { code: 'NOT_FOUND', message: '员工不存在' } })
|
|
}
|
|
const assessment = assessRisk(employee, req.query.reason as string || '')
|
|
res.json({ success: true, data: assessment })
|
|
} catch (err) {
|
|
next(err)
|
|
}
|
|
})
|
|
|
|
router.post('/', authMiddleware, async (req: AuthRequest, res, next) => {
|
|
try {
|
|
const data = terminationChecklistSchema.parse(req.body)
|
|
const result = await createTermination(req.user!.orgId, req.user!.id, data)
|
|
await auditLog(req, 'TERMINATE', 'EMPLOYEE', data.employeeId, { reason: data.reason })
|
|
res.json({ success: true, data: result })
|
|
} catch (err) {
|
|
next(err)
|
|
}
|
|
})
|
|
|
|
router.post('/resignation', authMiddleware, async (req: AuthRequest, res, next) => {
|
|
try {
|
|
const { employeeId, terminationDate, resignationReason, remark } = req.body
|
|
if (!employeeId || !terminationDate) {
|
|
return res.status(400).json({ success: false, error: { code: 'VALIDATION_ERROR', message: '缺少必填字段' } })
|
|
}
|
|
const result = await createResignation(req.user!.orgId, req.user!.id, { employeeId, terminationDate, resignationReason, remark })
|
|
await auditLog(req, 'RESIGN', 'EMPLOYEE', employeeId, { resignationReason })
|
|
res.json({ success: true, data: result })
|
|
} catch (err: any) {
|
|
if (err?.code === 'CONFLICT') {
|
|
return res.status(409).json({ success: false, error: { code: err.code, message: err.message } })
|
|
}
|
|
next(err)
|
|
}
|
|
})
|
|
|
|
router.delete('/:id/revoke', authMiddleware, async (req: AuthRequest, res, next) => {
|
|
try {
|
|
const result = await revokeTermination(req.user!.orgId, req.params.id)
|
|
await auditLog(req, 'REVOKE_TERMINATION', 'TERMINATION_RECORD', req.params.id, {})
|
|
res.json({ success: true, data: result })
|
|
} catch (err: any) {
|
|
if (err?.code === 'CONFLICT') {
|
|
return res.status(409).json({ success: false, error: { code: err.code, message: err.message } })
|
|
}
|
|
if (err?.code === 'NOT_FOUND') {
|
|
return res.status(404).json({ success: false, error: { code: err.code, message: err.message } })
|
|
}
|
|
next(err)
|
|
}
|
|
})
|
|
|
|
// 批量解聘预检
|
|
router.post('/batch/preview', authMiddleware, async (req: AuthRequest, res, next) => {
|
|
try {
|
|
const { items } = req.body as {
|
|
items: Array<{ employeeId: string; reason: string; terminationDate: string }>
|
|
}
|
|
if (!items || !Array.isArray(items) || items.length === 0) {
|
|
return res.status(400).json({ success: false, error: { code: 'BAD_REQUEST', message: '缺少 items' } })
|
|
}
|
|
const results = await batchTerminatePreview(req.user!.orgId, items)
|
|
res.json({ success: true, data: { total: results.length, warnings: results.filter(r => r.warnings.length > 0).length, results } })
|
|
} catch (err) {
|
|
next(err)
|
|
}
|
|
})
|
|
|
|
// 批量解聘执行
|
|
router.post('/batch', authMiddleware, async (req: AuthRequest, res, next) => {
|
|
try {
|
|
const { items } = req.body as {
|
|
items: Array<{ employeeId: string; reason: string; terminationDate: string; compensation?: number }>
|
|
}
|
|
if (!items || !Array.isArray(items) || items.length === 0) {
|
|
return res.status(400).json({ success: false, error: { code: 'BAD_REQUEST', message: '缺少 items' } })
|
|
}
|
|
const result = await batchTerminate(req.user!.orgId, req.user!.id, items)
|
|
for (const id of result.success) {
|
|
await auditLog(req, 'TERMINATE', 'EMPLOYEE', id, { batch: true })
|
|
}
|
|
res.json({ success: true, data: result })
|
|
} catch (err) {
|
|
next(err)
|
|
}
|
|
})
|
|
|
|
// ============================================================
|
|
// 解聘流程状态机 API
|
|
// ============================================================
|
|
|
|
// 获取草稿/流程列表
|
|
router.get('/drafts', authMiddleware, async (req: AuthRequest, res, next) => {
|
|
try {
|
|
const status = req.query.status as string | undefined
|
|
const result = await getDrafts(req.user!.orgId, status)
|
|
res.json({ success: true, data: result })
|
|
} catch (err) {
|
|
next(err)
|
|
}
|
|
})
|
|
|
|
// 获取单条记录详情
|
|
router.get('/detail/:id', authMiddleware, async (req: AuthRequest, res, next) => {
|
|
try {
|
|
const result = await getTerminationDetail(req.user!.orgId, req.params.id)
|
|
res.json({ success: true, data: result })
|
|
} catch (err: any) {
|
|
if (err?.code === 'NOT_FOUND') {
|
|
return res.status(404).json({ success: false, error: { code: err.code, message: err.message } })
|
|
}
|
|
next(err)
|
|
}
|
|
})
|
|
|
|
// 获取默认工作交接清单模板
|
|
router.get('/handover-template', authMiddleware, async (req: AuthRequest, res) => {
|
|
res.json({ success: true, data: getDefaultHandoverItems() })
|
|
})
|
|
|
|
// 创建草稿
|
|
router.post('/draft', authMiddleware, async (req: AuthRequest, res, next) => {
|
|
try {
|
|
const result = await createDraft(req.user!.orgId, req.user!.id, req.body)
|
|
await auditLog(req, 'CREATE_DRAFT', 'TERMINATION_RECORD', result.id, { reason: req.body.reason })
|
|
res.json({ success: true, data: result })
|
|
} catch (err: any) {
|
|
if (err?.code === 'NOT_FOUND') {
|
|
return res.status(404).json({ success: false, error: { code: err.code, message: err.message } })
|
|
}
|
|
next(err)
|
|
}
|
|
})
|
|
|
|
// 更新草稿
|
|
router.put('/draft/:id', authMiddleware, async (req: AuthRequest, res, next) => {
|
|
try {
|
|
const result = await updateDraft(req.user!.orgId, req.params.id, req.user!.id, req.body)
|
|
res.json({ success: true, data: result })
|
|
} catch (err: any) {
|
|
if (err?.code === 'CONFLICT' || err?.code === 'NOT_FOUND') {
|
|
return res.status(err.code === 'NOT_FOUND' ? 404 : 409).json({ success: false, error: { code: err.code, message: err.message } })
|
|
}
|
|
next(err)
|
|
}
|
|
})
|
|
|
|
// 提交审批
|
|
router.post('/draft/:id/submit', authMiddleware, async (req: AuthRequest, res, next) => {
|
|
try {
|
|
const result = await submitForApproval(req.user!.orgId, req.params.id, req.user!.id)
|
|
await auditLog(req, 'SUBMIT_TERMINATION', 'TERMINATION_RECORD', req.params.id, {})
|
|
res.json({ success: true, data: result })
|
|
} catch (err: any) {
|
|
if (err?.code === 'CONFLICT' || err?.code === 'NOT_FOUND') {
|
|
return res.status(err.code === 'NOT_FOUND' ? 404 : 409).json({ success: false, error: { code: err.code, message: err.message } })
|
|
}
|
|
next(err)
|
|
}
|
|
})
|
|
|
|
// 审批通过
|
|
router.post('/draft/:id/approve', authMiddleware, async (req: AuthRequest, res, next) => {
|
|
try {
|
|
const { comment } = req.body
|
|
const result = await approveTermination(req.user!.orgId, req.params.id, req.user!.id, comment || '')
|
|
await auditLog(req, 'APPROVE_TERMINATION', 'TERMINATION_RECORD', req.params.id, { comment })
|
|
res.json({ success: true, data: result })
|
|
} catch (err: any) {
|
|
if (err?.code === 'CONFLICT' || err?.code === 'NOT_FOUND') {
|
|
return res.status(err.code === 'NOT_FOUND' ? 404 : 409).json({ success: false, error: { code: err.code, message: err.message } })
|
|
}
|
|
next(err)
|
|
}
|
|
})
|
|
|
|
// 审批驳回
|
|
router.post('/draft/:id/reject', authMiddleware, async (req: AuthRequest, res, next) => {
|
|
try {
|
|
const { comment } = req.body
|
|
const result = await rejectTermination(req.user!.orgId, req.params.id, req.user!.id, comment || '')
|
|
await auditLog(req, 'REJECT_TERMINATION', 'TERMINATION_RECORD', req.params.id, { comment })
|
|
res.json({ success: true, data: result })
|
|
} catch (err: any) {
|
|
if (err?.code === 'CONFLICT' || err?.code === 'NOT_FOUND') {
|
|
return res.status(err.code === 'NOT_FOUND' ? 404 : 409).json({ success: false, error: { code: err.code, message: err.message } })
|
|
}
|
|
next(err)
|
|
}
|
|
})
|
|
|
|
// 执行解聘
|
|
router.post('/draft/:id/execute', authMiddleware, async (req: AuthRequest, res, next) => {
|
|
try {
|
|
const result = await executeTermination(req.user!.orgId, req.params.id, req.user!.id)
|
|
await auditLog(req, 'EXECUTE_TERMINATION', 'TERMINATION_RECORD', req.params.id, {})
|
|
res.json({ success: true, data: result })
|
|
} catch (err: any) {
|
|
if (err?.code === 'CONFLICT' || err?.code === 'NOT_FOUND') {
|
|
return res.status(err.code === 'NOT_FOUND' ? 404 : 409).json({ success: false, error: { code: err.code, message: err.message } })
|
|
}
|
|
next(err)
|
|
}
|
|
})
|
|
|
|
// 撤销
|
|
router.post('/draft/:id/cancel', authMiddleware, async (req: AuthRequest, res, next) => {
|
|
try {
|
|
const result = await cancelTermination(req.user!.orgId, req.params.id, req.user!.id)
|
|
await auditLog(req, 'CANCEL_TERMINATION', 'TERMINATION_RECORD', req.params.id, {})
|
|
res.json({ success: true, data: result })
|
|
} catch (err: any) {
|
|
if (err?.code === 'CONFLICT' || err?.code === 'NOT_FOUND') {
|
|
return res.status(err.code === 'NOT_FOUND' ? 404 : 409).json({ success: false, error: { code: err.code, message: err.message } })
|
|
}
|
|
next(err)
|
|
}
|
|
})
|
|
|
|
export default router
|