feat: 实现20260730优化方案全部功能
- AI文件审查:.docx上传提取文本,支持多种文档类型 - 用工办理工作流:WorkProcess页面+后端API,支持入职/续签/终止等流程 - 企业自建文本库:Templates页面Tab切换,企业模板CRUD+渲染+下载Word - 考勤发布:Attendance发布/取消发布按钮,员工端MyAttendance页面 - 工资条发布:Money发布/定时发送按钮+弹窗,portal端publishStatus过滤 - 合同到期弹窗:Dashboard合同到期预警可点击打开弹窗,支持续签/终止操作 - Prisma schema新增WorkProcess/EnterpriseTemplate/AttendancePublish模型 - 前后端编译验证全部通过
This commit is contained in:
@@ -0,0 +1,267 @@
|
||||
import { Router, Response, NextFunction } from 'express'
|
||||
import { authMiddleware, AuthRequest } from '../middleware/auth'
|
||||
import prisma from '../lib/prisma'
|
||||
import { executeWorkProcess, generateDocument, PROCESS_TYPES, PROCESS_STATUS } from '../services/work-process.service'
|
||||
|
||||
const router = Router()
|
||||
|
||||
// 创建办理(含草稿)
|
||||
router.post('/', authMiddleware, async (req: AuthRequest, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const { type, title, employeeId, formData, status = 'DRAFT', remark } = req.body
|
||||
if (!type || !PROCESS_TYPES[type]) {
|
||||
return res.status(400).json({ success: false, error: { code: 'INVALID_TYPE', message: '无效的流程类型' } })
|
||||
}
|
||||
const process = await (prisma as any).workProcess.create({
|
||||
data: {
|
||||
orgId: req.user!.orgId,
|
||||
type,
|
||||
title: title || PROCESS_TYPES[type].label,
|
||||
employeeId: employeeId || null,
|
||||
formData: formData || {},
|
||||
status,
|
||||
remark: remark || null,
|
||||
createdBy: req.user!.id,
|
||||
},
|
||||
})
|
||||
res.json({ success: true, data: process })
|
||||
} catch (err) {
|
||||
next(err)
|
||||
}
|
||||
})
|
||||
|
||||
// 列表查询
|
||||
router.get('/', authMiddleware, async (req: AuthRequest, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const { type, status, page = '1', pageSize = '20' } = req.query
|
||||
const where: any = { orgId: req.user!.orgId }
|
||||
if (type) where.type = type
|
||||
if (status) where.status = status
|
||||
const total = await (prisma as any).workProcess.count({ where })
|
||||
const items = await (prisma as any).workProcess.findMany({
|
||||
where,
|
||||
include: { employee: { select: { id: true, name: true, department: true } } },
|
||||
orderBy: { createdAt: 'desc' },
|
||||
skip: (Number(page) - 1) * Number(pageSize),
|
||||
take: Number(pageSize),
|
||||
})
|
||||
res.json({ success: true, data: { items, total, page: Number(page), pageSize: Number(pageSize) } })
|
||||
} catch (err) {
|
||||
next(err)
|
||||
}
|
||||
})
|
||||
|
||||
// 详情
|
||||
router.get('/:id', authMiddleware, async (req: AuthRequest, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const process = await (prisma as any).workProcess.findFirst({
|
||||
where: { id: req.params.id, orgId: req.user!.orgId },
|
||||
include: { employee: { select: { id: true, name: true, department: true, status: true } } },
|
||||
})
|
||||
if (!process) {
|
||||
return res.status(404).json({ success: false, error: { code: 'NOT_FOUND', message: '办理记录不存在' } })
|
||||
}
|
||||
res.json({ success: true, data: process })
|
||||
} catch (err) {
|
||||
next(err)
|
||||
}
|
||||
})
|
||||
|
||||
// 更新草稿
|
||||
router.patch('/:id', authMiddleware, async (req: AuthRequest, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const existing = await (prisma as any).workProcess.findFirst({
|
||||
where: { id: req.params.id, orgId: req.user!.orgId },
|
||||
})
|
||||
if (!existing) {
|
||||
return res.status(404).json({ success: false, error: { code: 'NOT_FOUND', message: '办理记录不存在' } })
|
||||
}
|
||||
if (existing.status !== 'DRAFT') {
|
||||
return res.status(400).json({ success: false, error: { code: 'NOT_DRAFT', message: '仅草稿状态可编辑' } })
|
||||
}
|
||||
const { title, employeeId, formData, remark } = req.body
|
||||
const updated = await (prisma as any).workProcess.update({
|
||||
where: { id: req.params.id },
|
||||
data: {
|
||||
...(title !== undefined && { title }),
|
||||
...(employeeId !== undefined && { employeeId: employeeId || null }),
|
||||
...(formData !== undefined && { formData }),
|
||||
...(remark !== undefined && { remark }),
|
||||
},
|
||||
})
|
||||
res.json({ success: true, data: updated })
|
||||
} catch (err) {
|
||||
next(err)
|
||||
}
|
||||
})
|
||||
|
||||
// 提交办理
|
||||
router.post('/:id/submit', authMiddleware, async (req: AuthRequest, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const process = await (prisma as any).workProcess.findFirst({
|
||||
where: { id: req.params.id, orgId: req.user!.orgId },
|
||||
})
|
||||
if (!process) {
|
||||
return res.status(404).json({ success: false, error: { code: 'NOT_FOUND', message: '办理记录不存在' } })
|
||||
}
|
||||
if (process.status !== 'DRAFT') {
|
||||
return res.status(400).json({ success: false, error: { code: 'NOT_DRAFT', message: '仅草稿状态可提交' } })
|
||||
}
|
||||
// 执行业务联动
|
||||
let execResult: any = {}
|
||||
try {
|
||||
execResult = await executeWorkProcess(process.id, process.type, process.formData, req.user!.orgId, req.user!.id)
|
||||
} catch (execErr: any) {
|
||||
return res.status(400).json({ success: false, error: { code: 'EXEC_FAILED', message: `执行失败:${execErr?.message || '未知错误'}` } })
|
||||
}
|
||||
// 生成文书
|
||||
const org = await prisma.organization.findUnique({ where: { id: req.user!.orgId } })
|
||||
const doc = generateDocument(process.type, process.formData, org?.name || '')
|
||||
const documents = doc.content ? [doc] : []
|
||||
const updated = await (prisma as any).workProcess.update({
|
||||
where: { id: process.id },
|
||||
data: {
|
||||
status: 'COMPLETED',
|
||||
documents: documents.length > 0 ? documents : null,
|
||||
...(execResult.employeeId && !process.employeeId && { employeeId: execResult.employeeId }),
|
||||
},
|
||||
})
|
||||
res.json({ success: true, data: updated })
|
||||
} catch (err) {
|
||||
next(err)
|
||||
}
|
||||
})
|
||||
|
||||
// 审批通过
|
||||
router.post('/:id/approve', authMiddleware, async (req: AuthRequest, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const process = await (prisma as any).workProcess.findFirst({
|
||||
where: { id: req.params.id, orgId: req.user!.orgId },
|
||||
})
|
||||
if (!process) {
|
||||
return res.status(404).json({ success: false, error: { code: 'NOT_FOUND', message: '办理记录不存在' } })
|
||||
}
|
||||
if (process.status !== 'PENDING_APPROVAL') {
|
||||
return res.status(400).json({ success: false, error: { code: 'NOT_PENDING', message: '仅待审批状态可审批' } })
|
||||
}
|
||||
let execResult: any = {}
|
||||
try {
|
||||
execResult = await executeWorkProcess(process.id, process.type, process.formData, req.user!.orgId, req.user!.id)
|
||||
} catch (execErr: any) {
|
||||
return res.status(400).json({ success: false, error: { code: 'EXEC_FAILED', message: `执行失败:${execErr?.message || '未知错误'}` } })
|
||||
}
|
||||
const org = await prisma.organization.findUnique({ where: { id: req.user!.orgId } })
|
||||
const doc = generateDocument(process.type, process.formData, org?.name || '')
|
||||
const documents = doc.content ? [doc] : []
|
||||
const updated = await (prisma as any).workProcess.update({
|
||||
where: { id: process.id },
|
||||
data: {
|
||||
status: 'COMPLETED',
|
||||
approverId: req.user!.id,
|
||||
approvedAt: new Date(),
|
||||
documents: documents.length > 0 ? documents : null,
|
||||
...(execResult.employeeId && !process.employeeId && { employeeId: execResult.employeeId }),
|
||||
},
|
||||
})
|
||||
res.json({ success: true, data: updated })
|
||||
} catch (err) {
|
||||
next(err)
|
||||
}
|
||||
})
|
||||
|
||||
// 驳回
|
||||
router.post('/:id/reject', authMiddleware, async (req: AuthRequest, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const process = await (prisma as any).workProcess.findFirst({
|
||||
where: { id: req.params.id, orgId: req.user!.orgId },
|
||||
})
|
||||
if (!process) {
|
||||
return res.status(404).json({ success: false, error: { code: 'NOT_FOUND', message: '办理记录不存在' } })
|
||||
}
|
||||
if (process.status !== 'PENDING_APPROVAL') {
|
||||
return res.status(400).json({ success: false, error: { code: 'NOT_PENDING', message: '仅待审批状态可驳回' } })
|
||||
}
|
||||
const updated = await (prisma as any).workProcess.update({
|
||||
where: { id: process.id },
|
||||
data: {
|
||||
status: 'REJECTED',
|
||||
approverId: req.user!.id,
|
||||
approvedAt: new Date(),
|
||||
remark: req.body.reason || '驳回',
|
||||
},
|
||||
})
|
||||
res.json({ success: true, data: updated })
|
||||
} catch (err) {
|
||||
next(err)
|
||||
}
|
||||
})
|
||||
|
||||
// 撤销
|
||||
router.post('/:id/cancel', authMiddleware, async (req: AuthRequest, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const process = await (prisma as any).workProcess.findFirst({
|
||||
where: { id: req.params.id, orgId: req.user!.orgId },
|
||||
})
|
||||
if (!process) {
|
||||
return res.status(404).json({ success: false, error: { code: 'NOT_FOUND', message: '办理记录不存在' } })
|
||||
}
|
||||
if (['COMPLETED', 'CANCELLED'].includes(process.status)) {
|
||||
return res.status(400).json({ success: false, error: { code: 'INVALID_STATUS', message: '已完成或已撤销的记录不可撤销' } })
|
||||
}
|
||||
const updated = await (prisma as any).workProcess.update({
|
||||
where: { id: process.id },
|
||||
data: { status: 'CANCELLED' },
|
||||
})
|
||||
res.json({ success: true, data: updated })
|
||||
} catch (err) {
|
||||
next(err)
|
||||
}
|
||||
})
|
||||
|
||||
// 删除草稿
|
||||
router.delete('/:id', authMiddleware, async (req: AuthRequest, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const process = await (prisma as any).workProcess.findFirst({
|
||||
where: { id: req.params.id, orgId: req.user!.orgId },
|
||||
})
|
||||
if (!process) {
|
||||
return res.status(404).json({ success: false, error: { code: 'NOT_FOUND', message: '办理记录不存在' } })
|
||||
}
|
||||
if (process.status !== 'DRAFT') {
|
||||
return res.status(400).json({ success: false, error: { code: 'NOT_DRAFT', message: '仅草稿状态可删除' } })
|
||||
}
|
||||
await (prisma as any).workProcess.delete({ where: { id: process.id } })
|
||||
res.json({ success: true, data: { message: '已删除' } })
|
||||
} catch (err) {
|
||||
next(err)
|
||||
}
|
||||
})
|
||||
|
||||
// 预览文书
|
||||
router.get('/:id/preview', authMiddleware, async (req: AuthRequest, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const process = await (prisma as any).workProcess.findFirst({
|
||||
where: { id: req.params.id, orgId: req.user!.orgId },
|
||||
})
|
||||
if (!process) {
|
||||
return res.status(404).json({ success: false, error: { code: 'NOT_FOUND', message: '办理记录不存在' } })
|
||||
}
|
||||
const org = await prisma.organization.findUnique({ where: { id: req.user!.orgId } })
|
||||
const doc = generateDocument(process.type, process.formData, org?.name || '')
|
||||
res.json({ success: true, data: doc })
|
||||
} catch (err) {
|
||||
next(err)
|
||||
}
|
||||
})
|
||||
|
||||
// 获取流程类型列表
|
||||
router.get('/meta/types', authMiddleware, (_req: AuthRequest, res: Response) => {
|
||||
res.json({ success: true, data: PROCESS_TYPES })
|
||||
})
|
||||
|
||||
// 获取状态列表
|
||||
router.get('/meta/statuses', authMiddleware, (_req: AuthRequest, res: Response) => {
|
||||
res.json({ success: true, data: PROCESS_STATUS })
|
||||
})
|
||||
|
||||
export default router
|
||||
Reference in New Issue
Block a user