feat: 20260809 系统优化 - 全部28项问题修复(P0×6+P1×16+P2×6)

P0: 福利批量参保/离职证明下载防乱码/考勤模板合并Sheet/补卡修改/附件在线查看删除
P1: 分页pageSize修复/离职导出筛选/撤回删除草稿/加班费自动计算/考勤加班汇总/证据链异常详情/制度催办/模板导入Word/社保封顶保底/校验字段提示/职务字段/社保费用明细/弹窗防误关/身份证查重/证明员工下拉/培训批量
P2: 离职流程去重/社保基数覆盖输入/薪税入口改名/添加员工引导/绩效模板清理
This commit is contained in:
freedakgmail
2026-08-09 11:59:02 +08:00
parent c355a7d208
commit a2e9ba55c2
43 changed files with 2913 additions and 324 deletions
+52 -5
View File
@@ -1,5 +1,6 @@
import { Router, Response, NextFunction } from 'express'
import { authMiddleware, AuthRequest } from '../middleware/auth'
import { requireAdmin } from '../middleware/rbac'
import { z } from 'zod'
import prisma from '../lib/prisma'
import {
@@ -90,24 +91,26 @@ router.delete('/:id', authMiddleware, async (req: AuthRequest, res: Response, ne
router.get('/:id/read-stats', authMiddleware, async (req: AuthRequest, res: Response, next: NextFunction) => {
try {
const orgId = req.user!.orgId
const policy = await prisma.policyDocument.findFirst({ where: { id: req.params.id, orgId }, select: { id: true } })
const policy = await prisma.policyDocument.findFirst({ where: { id: req.params.id, orgId }, select: { id: true, title: true } })
if (!policy) {
return res.status(404).json({ success: false, error: { code: 'NOT_FOUND', message: '制度不存在' } })
}
const [totalEmployees, readRecords] = await Promise.all([
prisma.employee.count({ where: { orgId, status: 'ACTIVE' } }),
const [allEmployees, readRecords] = await Promise.all([
prisma.employee.findMany({ where: { orgId, status: 'ACTIVE' }, select: { id: true, name: true, department: true }, orderBy: { name: 'asc' } }),
prisma.policyReadRecord.findMany({
where: { policyId: req.params.id, orgId },
include: { employee: { select: { id: true, name: true, department: true } } },
orderBy: { readAt: 'desc' },
}),
])
const readEmpIds = new Set(readRecords.map(r => r.employeeId))
const unreadEmployees = allEmployees.filter(e => !readEmpIds.has(e.id))
res.json({
success: true,
data: {
total: totalEmployees,
total: allEmployees.length,
readCount: readRecords.length,
unreadCount: totalEmployees - readRecords.length,
unreadCount: unreadEmployees.length,
records: readRecords.map(r => ({
employeeId: r.employeeId,
employeeName: r.employee.name,
@@ -115,6 +118,11 @@ router.get('/:id/read-stats', authMiddleware, async (req: AuthRequest, res: Resp
readAt: r.readAt.toISOString(),
ip: r.ip,
})),
unreadEmployees: unreadEmployees.map(e => ({
employeeId: e.id,
employeeName: e.name,
department: e.department,
})),
},
})
} catch (err) {
@@ -122,4 +130,43 @@ router.get('/:id/read-stats', authMiddleware, async (req: AuthRequest, res: Resp
}
})
/** 催办未签收员工 */
router.post('/:id/remind', authMiddleware, requireAdmin, async (req: AuthRequest, res: Response, next: NextFunction) => {
try {
const orgId = req.user!.orgId
const policy = await prisma.policyDocument.findFirst({ where: { id: req.params.id, orgId }, select: { id: true, title: true } })
if (!policy) {
return res.status(404).json({ success: false, error: { code: 'NOT_FOUND', message: '制度不存在' } })
}
const { employeeIds } = req.body as { employeeIds?: string[] }
const readRecords = await prisma.policyReadRecord.findMany({ where: { policyId: req.params.id, orgId }, select: { employeeId: true } })
const readEmpIds = new Set(readRecords.map(r => r.employeeId))
const targetEmployees = await prisma.employee.findMany({
where: {
orgId, status: 'ACTIVE',
id: employeeIds && employeeIds.length > 0 ? { in: employeeIds } : undefined,
},
select: { id: true, name: true },
})
const unreadEmployees = targetEmployees.filter(e => !readEmpIds.has(e.id))
// 创建催办通知
for (const emp of unreadEmployees) {
await prisma.notificationLog.create({
data: {
orgId,
employeeId: emp.id,
type: 'POLICY_REMIND',
title: `制度签收提醒:${policy.title}`,
content: `您有一项制度「${policy.title}」尚未签收,请尽快完成阅读确认。`,
channel: 'IN_APP',
status: 'SENT',
},
}).catch(() => {})
}
res.json({ success: true, data: { reminded: unreadEmployees.length } })
} catch (err) {
next(err)
}
})
export default router