feat: 工作日历/考勤管理重构/AI人力报告/工作台员工分布/筛选优化/导入导出增强
- 新增工作日历页面(月历视图、事件管理、自定义事件) - 考勤管理重构为6 Tab模块(班次/排班/每日出勤/月度报表/休假记录) - AI顾问新增人力报告Tab,支持流式生成+Word导出 - 工作台总览新增员工分布统计(性别/年龄/学历/司龄饼图)+部门成本拆分 - 花名册/合同/解聘补偿新增部门和状态筛选 - 薪税管理新增工资表导入模板下载、银行代发CSV导出 - 社保公积金支持多公积金账户类型显示 - 数据导出新增花名册/解聘记录导出,中文文件名编码修复 - 数据导入新增模板下载(员工/增减员/工资表)+错误日志导出 - 移除工作台日历卡片(已迁移至独立工作日历页面) - 新增20260728/20260729更新测试指导文档
This commit is contained in:
@@ -7,6 +7,18 @@ import {
|
||||
getAttendanceConfirmations,
|
||||
confirmAttendance,
|
||||
getAttendanceStats,
|
||||
getShifts,
|
||||
createShift,
|
||||
updateShift,
|
||||
deleteShift,
|
||||
getShiftAssignments,
|
||||
batchAssignShifts,
|
||||
deleteShiftAssignment,
|
||||
getDailyAttendance,
|
||||
getMonthlyReport,
|
||||
getLeaveRecords,
|
||||
createLeaveRecord,
|
||||
deleteLeaveRecord,
|
||||
} from '../services/attendance.service'
|
||||
import { createEvidence } from '../services/evidence.service'
|
||||
|
||||
@@ -20,7 +32,8 @@ router.get('/', authMiddleware, async (req: AuthRequest, res: Response, next: Ne
|
||||
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)
|
||||
const department = req.query.department as string | undefined
|
||||
const data = await getAttendanceConfirmations(req.user!.orgId, month, status, department)
|
||||
res.json({ success: true, data })
|
||||
} catch (err) {
|
||||
next(err)
|
||||
@@ -91,4 +104,138 @@ router.post('/confirm', authMiddleware, async (req: AuthRequest, res: Response,
|
||||
}
|
||||
})
|
||||
|
||||
// ========== 班次管理 ==========
|
||||
|
||||
router.get('/shifts', authMiddleware, async (req: AuthRequest, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const data = await getShifts(req.user!.orgId)
|
||||
res.json({ success: true, data })
|
||||
} catch (err) { next(err) }
|
||||
})
|
||||
|
||||
router.post('/shifts', authMiddleware, async (req: AuthRequest, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const schema = z.object({
|
||||
name: z.string().min(1),
|
||||
startTime: z.string().regex(/^\d{2}:\d{2}$/),
|
||||
endTime: z.string().regex(/^\d{2}:\d{2}$/),
|
||||
flexibleMinutes: z.number().int().min(0).optional(),
|
||||
restMinutes: z.number().int().min(0).optional(),
|
||||
color: z.string().optional(),
|
||||
})
|
||||
const data = await createShift(req.user!.orgId, req.user!.id, schema.parse(req.body))
|
||||
res.json({ success: true, data })
|
||||
} catch (err) { next(err) }
|
||||
})
|
||||
|
||||
router.put('/shifts/:id', authMiddleware, async (req: AuthRequest, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const schema = z.object({
|
||||
name: z.string().min(1).optional(),
|
||||
startTime: z.string().regex(/^\d{2}:\d{2}$/).optional(),
|
||||
endTime: z.string().regex(/^\d{2}:\d{2}$/).optional(),
|
||||
flexibleMinutes: z.number().int().min(0).optional(),
|
||||
restMinutes: z.number().int().min(0).optional(),
|
||||
color: z.string().optional(),
|
||||
})
|
||||
const data = await updateShift(req.user!.orgId, req.params.id, schema.parse(req.body))
|
||||
res.json({ success: true, data })
|
||||
} catch (err) { next(err) }
|
||||
})
|
||||
|
||||
router.delete('/shifts/:id', authMiddleware, async (req: AuthRequest, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
await deleteShift(req.user!.orgId, req.params.id)
|
||||
res.json({ success: true })
|
||||
} catch (err) { next(err) }
|
||||
})
|
||||
|
||||
// ========== 排班管理 ==========
|
||||
|
||||
router.get('/shift-assignments', authMiddleware, async (req: AuthRequest, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const date = req.query.date as string
|
||||
if (!date) return res.status(400).json({ success: false, error: { code: 'BAD_REQUEST', message: '缺少 date 参数' } })
|
||||
const data = await getShiftAssignments(req.user!.orgId, date)
|
||||
res.json({ success: true, data })
|
||||
} catch (err) { next(err) }
|
||||
})
|
||||
|
||||
router.post('/shift-assignments/batch', authMiddleware, async (req: AuthRequest, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const schema = z.object({
|
||||
items: z.array(z.object({
|
||||
employeeId: z.string(),
|
||||
shiftId: z.string(),
|
||||
date: z.string(),
|
||||
})),
|
||||
})
|
||||
const { items } = schema.parse(req.body)
|
||||
const result = await batchAssignShifts(req.user!.orgId, req.user!.id, items)
|
||||
res.json({ success: true, data: result })
|
||||
} catch (err) { next(err) }
|
||||
})
|
||||
|
||||
router.delete('/shift-assignments/:id', authMiddleware, async (req: AuthRequest, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
await deleteShiftAssignment(req.user!.orgId, req.params.id)
|
||||
res.json({ success: true })
|
||||
} catch (err) { next(err) }
|
||||
})
|
||||
|
||||
// ========== 每日出勤 ==========
|
||||
|
||||
router.get('/daily', authMiddleware, async (req: AuthRequest, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const date = req.query.date as string
|
||||
if (!date) return res.status(400).json({ success: false, error: { code: 'BAD_REQUEST', message: '缺少 date 参数' } })
|
||||
const data = await getDailyAttendance(req.user!.orgId, date)
|
||||
res.json({ success: true, data })
|
||||
} catch (err) { next(err) }
|
||||
})
|
||||
|
||||
// ========== 月度出勤报表 ==========
|
||||
|
||||
router.get('/monthly-report', 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 getMonthlyReport(req.user!.orgId, month)
|
||||
res.json({ success: true, data })
|
||||
} catch (err) { next(err) }
|
||||
})
|
||||
|
||||
// ========== 休假记录 ==========
|
||||
|
||||
router.get('/leaves', authMiddleware, async (req: AuthRequest, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const employeeId = req.query.employeeId as string | undefined
|
||||
const data = await getLeaveRecords(req.user!.orgId, employeeId)
|
||||
res.json({ success: true, data })
|
||||
} catch (err) { next(err) }
|
||||
})
|
||||
|
||||
router.post('/leaves', authMiddleware, async (req: AuthRequest, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const schema = z.object({
|
||||
employeeId: z.string(),
|
||||
leaveType: z.enum(['SICK', 'PERSONAL', 'ANNUAL', 'MATERNITY', 'OTHER']),
|
||||
startDate: z.string(),
|
||||
endDate: z.string(),
|
||||
days: z.number().min(0),
|
||||
reason: z.string().optional(),
|
||||
remark: z.string().optional(),
|
||||
})
|
||||
const data = await createLeaveRecord(req.user!.orgId, req.user!.id, schema.parse(req.body))
|
||||
res.json({ success: true, data })
|
||||
} catch (err) { next(err) }
|
||||
})
|
||||
|
||||
router.delete('/leaves/:id', authMiddleware, async (req: AuthRequest, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
await deleteLeaveRecord(req.user!.orgId, req.params.id)
|
||||
res.json({ success: true })
|
||||
} catch (err) { next(err) }
|
||||
})
|
||||
|
||||
export default router
|
||||
|
||||
Reference in New Issue
Block a user