2968484d2d
- Money.tsx (2260行→54行): 拆分为 money/ 子目录4个组件, React.lazy二级分割 - AIAssistant.tsx (2038行→63行): 拆分为 ai-assistant/ 子目录6个组件, React.lazy二级分割 - xlsx改为动态导入, OvertimeTab从345KB降至12.7KB - api-services.ts: 请求参数 any→Record<string,unknown> - 移除前端3处console.log残留 - 后端console替换为pino logger - 前后端未使用import/变量清理 - Zod schema验证: termination/platform/special-status/work-process - 新增 leave.routes.ts, acceptance-test.routes.ts - UI组件: PageGuide, QueryError, Stepper
204 lines
5.9 KiB
TypeScript
204 lines
5.9 KiB
TypeScript
/**
|
|
* 员工特殊状态台账路由
|
|
* 管理三期/工伤/医疗期等特殊状态的 CRUD 和提醒
|
|
*/
|
|
import { Router, Response, NextFunction } from 'express'
|
|
import { authMiddleware, AuthRequest } from '../middleware/auth'
|
|
import prisma from '../lib/prisma'
|
|
import { parsePagination } from '../lib/pagination'
|
|
import {
|
|
createSpecialStatus,
|
|
updateSpecialStatus,
|
|
deleteSpecialStatus,
|
|
getPendingReminders,
|
|
STATUS_TYPES,
|
|
getAlertLevel,
|
|
} from '../services/special-status.service'
|
|
import { createSpecialStatusSchema, updateSpecialStatusSchema } from '../schemas/special-status.schema'
|
|
|
|
const router = Router()
|
|
|
|
/**
|
|
* 获取特殊状态列表(分页 + 筛选)
|
|
*/
|
|
router.get('/', authMiddleware, async (req: AuthRequest, res: Response, next: NextFunction) => {
|
|
try {
|
|
const orgId = req.user!.orgId
|
|
const { page, pageSize } = parsePagination(req.query)
|
|
const type = (req.query.type as string) || ''
|
|
const status = (req.query.status as string) || ''
|
|
const search = (req.query.search as string) || ''
|
|
|
|
const where: any = { orgId }
|
|
if (type) where.type = type
|
|
if (status) where.status = status
|
|
if (search) {
|
|
where.employee = {
|
|
OR: [
|
|
{ name: { contains: search, mode: 'insensitive' } },
|
|
{ phone: { contains: search } },
|
|
],
|
|
}
|
|
}
|
|
|
|
const [list, total] = await Promise.all([
|
|
(prisma as any).employeeSpecialStatus.findMany({
|
|
where,
|
|
orderBy: { createdAt: 'desc' },
|
|
skip: (page - 1) * pageSize,
|
|
take: pageSize,
|
|
include: {
|
|
employee: {
|
|
select: { id: true, name: true, department: true, phone: true, gender: true, status: true },
|
|
},
|
|
},
|
|
}),
|
|
(prisma as any).employeeSpecialStatus.count({ where }),
|
|
])
|
|
|
|
// 附加预警级别
|
|
const listWithAlert = list.map((item: any) => ({
|
|
...item,
|
|
alertLevel: getAlertLevel(item.reminderDate, item.status),
|
|
}))
|
|
|
|
res.json({
|
|
success: true,
|
|
data: { list: listWithAlert, total, page, pageSize },
|
|
})
|
|
} catch (err) {
|
|
next(err)
|
|
}
|
|
})
|
|
|
|
/**
|
|
* 获取单条详情
|
|
*/
|
|
router.get('/:id', authMiddleware, async (req: AuthRequest, res: Response, next: NextFunction) => {
|
|
try {
|
|
const record = await (prisma as any).employeeSpecialStatus.findFirst({
|
|
where: { id: req.params.id, orgId: req.user!.orgId },
|
|
include: {
|
|
employee: {
|
|
select: { id: true, name: true, department: true, phone: true, gender: true, hireDate: true },
|
|
},
|
|
},
|
|
})
|
|
if (!record) {
|
|
return res.status(404).json({ success: false, error: { code: 'NOT_FOUND', message: '记录不存在' } })
|
|
}
|
|
res.json({ success: true, data: record })
|
|
} catch (err) {
|
|
next(err)
|
|
}
|
|
})
|
|
|
|
/**
|
|
* 创建特殊状态
|
|
*/
|
|
router.post('/', authMiddleware, async (req: AuthRequest, res: Response, next: NextFunction) => {
|
|
try {
|
|
const data = createSpecialStatusSchema.parse(req.body)
|
|
const record = await createSpecialStatus(req.user!.orgId, req.user!.id, data)
|
|
res.json({ success: true, data: record })
|
|
} catch (err: any) {
|
|
if (err.code) {
|
|
return res.status(400).json({ success: false, error: { code: err.code, message: err.message } })
|
|
}
|
|
next(err)
|
|
}
|
|
})
|
|
|
|
/**
|
|
* 更新特殊状态
|
|
*/
|
|
router.put('/:id', authMiddleware, async (req: AuthRequest, res: Response, next: NextFunction) => {
|
|
try {
|
|
const data = updateSpecialStatusSchema.parse(req.body)
|
|
const record = await updateSpecialStatus(req.user!.orgId, req.user!.id, req.params.id, data)
|
|
res.json({ success: true, data: record })
|
|
} catch (err: any) {
|
|
if (err.code) {
|
|
return res.status(400).json({ success: false, error: { code: err.code, message: err.message } })
|
|
}
|
|
next(err)
|
|
}
|
|
})
|
|
|
|
/**
|
|
* 删除特殊状态
|
|
*/
|
|
router.delete('/:id', authMiddleware, async (req: AuthRequest, res: Response, next: NextFunction) => {
|
|
try {
|
|
await deleteSpecialStatus(req.user!.orgId, req.params.id)
|
|
res.json({ success: true, data: { message: '已删除' } })
|
|
} catch (err: any) {
|
|
if (err.code) {
|
|
return res.status(400).json({ success: false, error: { code: err.code, message: err.message } })
|
|
}
|
|
next(err)
|
|
}
|
|
})
|
|
|
|
/**
|
|
* 获取待提醒列表
|
|
*/
|
|
router.get('/reminders/pending', authMiddleware, async (req: AuthRequest, res: Response, next: NextFunction) => {
|
|
try {
|
|
const reminders = await getPendingReminders(req.user!.orgId)
|
|
res.json({ success: true, data: reminders })
|
|
} catch (err) {
|
|
next(err)
|
|
}
|
|
})
|
|
|
|
/**
|
|
* 获取统计概览
|
|
*/
|
|
router.get('/stats/overview', authMiddleware, async (req: AuthRequest, res: Response, next: NextFunction) => {
|
|
try {
|
|
const orgId = req.user!.orgId
|
|
const [total, active, pending, byType] = await Promise.all([
|
|
(prisma as any).employeeSpecialStatus.count({ where: { orgId } }),
|
|
(prisma as any).employeeSpecialStatus.count({ where: { orgId, status: 'ACTIVE' } }),
|
|
(prisma as any).employeeSpecialStatus.count({ where: { orgId, status: 'PENDING' } }),
|
|
(prisma as any).employeeSpecialStatus.groupBy({
|
|
by: ['type'],
|
|
where: { orgId, status: 'ACTIVE' },
|
|
_count: true,
|
|
}),
|
|
])
|
|
|
|
// 预警统计
|
|
const soon7 = new Date()
|
|
soon7.setDate(soon7.getDate() + 7)
|
|
const soon30 = new Date()
|
|
soon30.setDate(soon30.getDate() + 30)
|
|
|
|
const [redCount, yellowCount] = await Promise.all([
|
|
(prisma as any).employeeSpecialStatus.count({
|
|
where: { orgId, status: 'ACTIVE', reminderDate: { lte: soon7 } },
|
|
}),
|
|
(prisma as any).employeeSpecialStatus.count({
|
|
where: { orgId, status: 'ACTIVE', reminderDate: { gt: soon7, lte: soon30 } },
|
|
}),
|
|
])
|
|
|
|
res.json({
|
|
success: true,
|
|
data: {
|
|
total,
|
|
active,
|
|
pending,
|
|
redAlert: redCount,
|
|
yellowAlert: yellowCount,
|
|
byType: byType.map((t: any) => ({ type: t.type, label: STATUS_TYPES[t.type]?.label || t.type, count: t._count })),
|
|
},
|
|
})
|
|
} catch (err) {
|
|
next(err)
|
|
}
|
|
})
|
|
|
|
export default router
|