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:
freedakgmail
2026-07-30 10:21:22 +08:00
parent 38b8849332
commit 42e0c650a4
24 changed files with 3639 additions and 35 deletions
+95
View File
@@ -870,4 +870,99 @@ router.get('/batches/:batchId/pre-check', async (req: AuthRequest, res: Response
}
})
// ========== 工资条发布 ==========
// 发布工资条(将批次内所有工资条标记为 PUBLISHED)
router.post('/batches/:batchId/publish', async (req: AuthRequest, res: Response, next: NextFunction) => {
try {
const { batchId } = req.params
const orgId = req.user!.orgId
const batch = await prisma.payrollBatch.findFirst({ where: { id: batchId, orgId } })
if (!batch) {
return res.status(404).json({ success: false, error: { code: 'NOT_FOUND', message: '批次不存在' } })
}
// 查找该批次关联的所有工资条(通过 BatchEntry 关联的 employeeId + month
const entries = await prisma.batchEntry.findMany({
where: { batchId, orgId },
select: { employeeId: true },
})
const employeeIds = entries.map(e => e.employeeId)
if (employeeIds.length === 0) {
return res.status(400).json({ success: false, error: { code: 'EMPTY', message: '批次内无员工' } })
}
// 更新对应月份的工资条
const result = await prisma.payslip.updateMany({
where: { orgId, employeeId: { in: employeeIds }, month: batch.month },
data: { publishStatus: 'PUBLISHED', publishedAt: new Date() },
})
res.json({ success: true, data: { published: result.count, month: batch.month } })
} catch (err) {
next(err)
}
})
// 定时发送工资条
router.post('/batches/:batchId/schedule', async (req: AuthRequest, res: Response, next: NextFunction) => {
try {
const { batchId } = req.params
const { scheduledAt } = req.body
if (!scheduledAt) {
return res.status(400).json({ success: false, error: { code: 'MISSING_DATE', message: '请选择发送时间' } })
}
const orgId = req.user!.orgId
const batch = await prisma.payrollBatch.findFirst({ where: { id: batchId, orgId } })
if (!batch) {
return res.status(404).json({ success: false, error: { code: 'NOT_FOUND', message: '批次不存在' } })
}
const entries = await prisma.batchEntry.findMany({
where: { batchId, orgId },
select: { employeeId: true },
})
const employeeIds = entries.map(e => e.employeeId)
if (employeeIds.length === 0) {
return res.status(400).json({ success: false, error: { code: 'EMPTY', message: '批次内无员工' } })
}
const result = await prisma.payslip.updateMany({
where: { orgId, employeeId: { in: employeeIds }, month: batch.month },
data: { publishStatus: 'SCHEDULED', scheduledAt: new Date(scheduledAt) },
})
res.json({ success: true, data: { scheduled: result.count, scheduledAt } })
} catch (err) {
next(err)
}
})
// 定时发送记录
router.get('/schedule-records', async (req: AuthRequest, res: Response, next: NextFunction) => {
try {
const records = await prisma.payslip.findMany({
where: { orgId: req.user!.orgId, publishStatus: 'SCHEDULED' },
include: { employee: { select: { name: true, department: true } } },
orderBy: { scheduledAt: 'asc' },
})
res.json({ success: true, data: records })
} catch (err) {
next(err)
}
})
// 取消定时发送
router.post('/schedule/:id/cancel', async (req: AuthRequest, res: Response, next: NextFunction) => {
try {
const payslip = await prisma.payslip.findFirst({
where: { id: req.params.id, orgId: req.user!.orgId, publishStatus: 'SCHEDULED' },
})
if (!payslip) {
return res.status(404).json({ success: false, error: { code: 'NOT_FOUND', message: '定时发送记录不存在' } })
}
const updated = await prisma.payslip.update({
where: { id: payslip.id },
data: { publishStatus: 'UNPUBLISHED', scheduledAt: null },
})
res.json({ success: true, data: updated })
} catch (err) {
next(err)
}
})
export default router