Files
TurboHR/backend/src/routes/policy.routes.ts
T
freedakgmail a2e9ba55c2 feat: 20260809 系统优化 - 全部28项问题修复(P0×6+P1×16+P2×6)
P0: 福利批量参保/离职证明下载防乱码/考勤模板合并Sheet/补卡修改/附件在线查看删除
P1: 分页pageSize修复/离职导出筛选/撤回删除草稿/加班费自动计算/考勤加班汇总/证据链异常详情/制度催办/模板导入Word/社保封顶保底/校验字段提示/职务字段/社保费用明细/弹窗防误关/身份证查重/证明员工下拉/培训批量
P2: 离职流程去重/社保基数覆盖输入/薪税入口改名/添加员工引导/绩效模板清理
2026-08-09 11:59:02 +08:00

173 lines
6.0 KiB
TypeScript

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 {
createPolicy, getPolicies, getPolicyDetail, updatePolicy,
advanceDemocracyStep, deletePolicy,
} from '../services/policy.service'
const router = Router()
/** 获取制度列表 */
router.get('/', authMiddleware, async (req: AuthRequest, res: Response, next: NextFunction) => {
try {
const status = req.query.status as string | undefined
const page = parseInt(req.query.page as string) || 1
const pageSize = Math.min(parseInt(req.query.pageSize as string) || 20, 200)
const result = await getPolicies(req.user!.orgId, status, page, pageSize)
res.json({ success: true, data: result })
} catch (err) {
next(err)
}
})
/** 获取制度详情 */
router.get('/:id', authMiddleware, async (req: AuthRequest, res: Response, next: NextFunction) => {
try {
const policy = await getPolicyDetail(req.user!.orgId, req.params.id)
res.json({ success: true, data: policy })
} catch (err) {
next(err)
}
})
/** 创建制度 */
router.post('/', authMiddleware, async (req: AuthRequest, res: Response, next: NextFunction) => {
try {
const schema = z.object({
title: z.string().min(1),
content: z.string().min(1),
type: z.string().optional(),
})
const data = schema.parse(req.body)
const policy = await createPolicy(req.user!.orgId, req.user!.id, data)
res.json({ success: true, data: { id: policy.id } })
} catch (err) {
next(err)
}
})
/** 更新制度 */
router.put('/:id', authMiddleware, async (req: AuthRequest, res: Response, next: NextFunction) => {
try {
const schema = z.object({
title: z.string().optional(),
content: z.string().optional(),
type: z.string().optional(),
})
const data = schema.parse(req.body)
await updatePolicy(req.user!.orgId, req.params.id, data)
res.json({ success: true })
} catch (err) {
next(err)
}
})
/** 推进民主程序步骤 */
router.post('/:id/advance-step', authMiddleware, async (req: AuthRequest, res: Response, next: NextFunction) => {
try {
const schema = z.object({ step: z.number().int().min(1).max(4), note: z.string().optional() })
const { step, note } = schema.parse(req.body)
await advanceDemocracyStep(req.user!.orgId, req.params.id, step, note)
res.json({ success: true })
} catch (err) {
next(err)
}
})
/** 删除制度 */
router.delete('/:id', authMiddleware, async (req: AuthRequest, res: Response, next: NextFunction) => {
try {
await deletePolicy(req.user!.orgId, req.params.id)
res.json({ success: true })
} catch (err) {
next(err)
}
})
/** 获取制度的阅读签收统计 */
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, title: true } })
if (!policy) {
return res.status(404).json({ success: false, error: { code: 'NOT_FOUND', message: '制度不存在' } })
}
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: allEmployees.length,
readCount: readRecords.length,
unreadCount: unreadEmployees.length,
records: readRecords.map(r => ({
employeeId: r.employeeId,
employeeName: r.employee.name,
department: r.employee.department,
readAt: r.readAt.toISOString(),
ip: r.ip,
})),
unreadEmployees: unreadEmployees.map(e => ({
employeeId: e.id,
employeeName: e.name,
department: e.department,
})),
},
})
} catch (err) {
next(err)
}
})
/** 催办未签收员工 */
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