a2e9ba55c2
P0: 福利批量参保/离职证明下载防乱码/考勤模板合并Sheet/补卡修改/附件在线查看删除 P1: 分页pageSize修复/离职导出筛选/撤回删除草稿/加班费自动计算/考勤加班汇总/证据链异常详情/制度催办/模板导入Word/社保封顶保底/校验字段提示/职务字段/社保费用明细/弹窗防误关/身份证查重/证明员工下拉/培训批量 P2: 离职流程去重/社保基数覆盖输入/薪税入口改名/添加员工引导/绩效模板清理
173 lines
6.0 KiB
TypeScript
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
|