feat: 系统优化Phase2 - 面包屑导航/侧边栏间距/制度公示阅读签收/模板变量中文化/通知类型补全
- 面包屑导航组件,集成至TopNav header - 侧边栏菜单分组间距增大,分组间分隔线 - 制度公示员工阅读签收:PolicyReadRecord模型、portal路由、管理端阅读统计 - 修复Policies.tsx民主程序推进bug(字段名/API路径/参数) - 用工文本模板变量名英文转中文显示 - 通知类型TYPE_LABELS补全(RISK_ALERT/SOCIAL_INS/OVERTIME_ALERT/PAYSLIP_READY) - 通知示例数据补充 - h2标题统一为text-sm font-medium - 新增run.md
This commit is contained in:
@@ -0,0 +1,94 @@
|
||||
import { Router, Response, NextFunction } from 'express'
|
||||
import { authMiddleware, AuthRequest } from '../middleware/auth'
|
||||
import { z } from 'zod'
|
||||
import {
|
||||
createAttendanceConfirmation,
|
||||
batchCreateAttendanceConfirmations,
|
||||
getAttendanceConfirmations,
|
||||
confirmAttendance,
|
||||
getAttendanceStats,
|
||||
} from '../services/attendance.service'
|
||||
import { createEvidence } from '../services/evidence.service'
|
||||
|
||||
const router = Router()
|
||||
|
||||
/** 获取月度考勤确认列表 */
|
||||
router.get('/', authMiddleware, async (req: AuthRequest, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const month = req.query.month as string
|
||||
if (!month) {
|
||||
return res.status(400).json({ success: false, error: { code: 'BAD_REQUEST', message: '缺少 month 参数' } })
|
||||
}
|
||||
const status = req.query.status as string | undefined
|
||||
const data = await getAttendanceConfirmations(req.user!.orgId, month, status)
|
||||
res.json({ success: true, data })
|
||||
} catch (err) {
|
||||
next(err)
|
||||
}
|
||||
})
|
||||
|
||||
/** 获取考勤确认统计 */
|
||||
router.get('/stats', authMiddleware, async (req: AuthRequest, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const month = req.query.month as string
|
||||
if (!month) {
|
||||
return res.status(400).json({ success: false, error: { code: 'BAD_REQUEST', message: '缺少 month 参数' } })
|
||||
}
|
||||
const data = await getAttendanceStats(req.user!.orgId, month)
|
||||
res.json({ success: true, data })
|
||||
} catch (err) {
|
||||
next(err)
|
||||
}
|
||||
})
|
||||
|
||||
/** 批量创建考勤确认记录 */
|
||||
router.post('/batch', authMiddleware, async (req: AuthRequest, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const schema = z.object({
|
||||
month: z.string().regex(/^\d{4}-\d{2}$/),
|
||||
items: z.array(z.object({
|
||||
employeeId: z.string(),
|
||||
workDays: z.number().int().min(0),
|
||||
weekdayHours: z.number().min(0),
|
||||
weekendHours: z.number().min(0),
|
||||
holidayHours: z.number().min(0),
|
||||
overtimePay: z.number().min(0),
|
||||
})),
|
||||
})
|
||||
const { month, items } = schema.parse(req.body)
|
||||
const result = await batchCreateAttendanceConfirmations(req.user!.orgId, req.user!.id, month, items)
|
||||
res.json({ success: true, data: result })
|
||||
} catch (err) {
|
||||
next(err)
|
||||
}
|
||||
})
|
||||
|
||||
/** 员工确认考勤(员工端) */
|
||||
router.post('/confirm', authMiddleware, async (req: AuthRequest, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const schema = z.object({
|
||||
employeeId: z.string(),
|
||||
month: z.string().regex(/^\d{4}-\d{2}$/),
|
||||
disputeNote: z.string().optional(),
|
||||
})
|
||||
const { employeeId, month, disputeNote } = schema.parse(req.body)
|
||||
const ip = req.ip || req.socket.remoteAddress || ''
|
||||
const result = await confirmAttendance(req.user!.orgId, employeeId, month, ip, disputeNote)
|
||||
await createEvidence({
|
||||
orgId: req.user!.orgId,
|
||||
category: 'ATTENDANCE',
|
||||
refId: result.id,
|
||||
employeeId,
|
||||
events: [{ action: '考勤确认', timestamp: new Date().toISOString(), ip, userAgent: req.headers['user-agent'], ...(disputeNote ? { location: disputeNote } : {}) }],
|
||||
createdBy: req.user!.id,
|
||||
}).catch(() => {})
|
||||
res.json({ success: true, data: result })
|
||||
} catch (err: any) {
|
||||
if (err?.code === 'NOT_FOUND') {
|
||||
return res.status(404).json({ success: false, error: { code: err.code, message: err.message } })
|
||||
}
|
||||
next(err)
|
||||
}
|
||||
})
|
||||
|
||||
export default router
|
||||
Reference in New Issue
Block a user