Files
TurboHR/backend/src/routes/calendar.routes.ts
T
freedakgmail fb36b10402 feat: 工作日历/考勤管理重构/AI人力报告/工作台员工分布/筛选优化/导入导出增强
- 新增工作日历页面(月历视图、事件管理、自定义事件)
- 考勤管理重构为6 Tab模块(班次/排班/每日出勤/月度报表/休假记录)
- AI顾问新增人力报告Tab,支持流式生成+Word导出
- 工作台总览新增员工分布统计(性别/年龄/学历/司龄饼图)+部门成本拆分
- 花名册/合同/解聘补偿新增部门和状态筛选
- 薪税管理新增工资表导入模板下载、银行代发CSV导出
- 社保公积金支持多公积金账户类型显示
- 数据导出新增花名册/解聘记录导出,中文文件名编码修复
- 数据导入新增模板下载(员工/增减员/工资表)+错误日志导出
- 移除工作台日历卡片(已迁移至独立工作日历页面)
- 新增20260728/20260729更新测试指导文档
2026-07-29 08:35:29 +08:00

121 lines
3.9 KiB
TypeScript

import { Router, Response, NextFunction } from 'express'
import prisma from '../lib/prisma'
import { authMiddleware, AuthRequest } from '../middleware/auth'
import { z } from 'zod'
const router = Router()
const createEventSchema = z.object({
title: z.string().min(1).max(100),
date: z.string(), // ISO date string
endDate: z.string().optional(),
type: z.enum(['CUSTOM', 'MEETING', 'TEAM_BUILDING', 'TRAINING', 'INTERVIEW']).default('CUSTOM'),
priority: z.enum(['high', 'medium', 'low']).default('medium'),
location: z.string().optional(),
description: z.string().optional(),
employeeId: z.string().optional(),
})
// 获取当月日历事件(自定义 + 系统自动)
router.get('/', authMiddleware, async (req: AuthRequest, res: Response, next: NextFunction) => {
try {
const orgId = req.user!.orgId
const month = (req.query.month as string) || new Date().toISOString().slice(0, 7)
const typeFilter = req.query.type as string | undefined
const [year, mon] = month.split('-').map(Number)
const monthStart = new Date(year, mon - 1, 1)
const monthEnd = new Date(year, mon, 0, 23, 59, 59)
const where: any = {
orgId,
date: { gte: monthStart, lte: monthEnd },
}
if (typeFilter) where.type = typeFilter
const events = await prisma.calendarEvent.findMany({
where,
include: { employee: { select: { id: true, name: true } } },
orderBy: { date: 'asc' },
})
res.json({ success: true, data: events })
} catch (err) {
next(err)
}
})
// 获取事件详情
router.get('/:id', authMiddleware, async (req: AuthRequest, res: Response, next: NextFunction) => {
try {
const event = await prisma.calendarEvent.findFirst({
where: { id: req.params.id, orgId: req.user!.orgId },
include: { employee: { select: { id: true, name: true } } },
})
if (!event) return res.status(404).json({ success: false, message: '事件不存在' })
res.json({ success: true, data: event })
} catch (err) {
next(err)
}
})
// 创建日历事件
router.post('/', authMiddleware, async (req: AuthRequest, res: Response, next: NextFunction) => {
try {
const data = createEventSchema.parse(req.body)
const event = await prisma.calendarEvent.create({
data: {
orgId: req.user!.orgId,
title: data.title,
date: new Date(data.date),
endDate: data.endDate ? new Date(data.endDate) : null,
type: data.type,
priority: data.priority,
location: data.location || null,
description: data.description || null,
employeeId: data.employeeId || null,
createdBy: req.user!.id,
},
})
res.json({ success: true, data: event })
} catch (err) {
next(err)
}
})
// 更新日历事件
router.patch('/:id', authMiddleware, async (req: AuthRequest, res: Response, next: NextFunction) => {
try {
const updateSchema = createEventSchema.partial()
const data = updateSchema.parse(req.body)
const updateData: any = { ...data }
if (data.date) updateData.date = new Date(data.date)
if (data.endDate) updateData.endDate = new Date(data.endDate)
if (data.endDate === undefined) delete updateData.endDate
const event = await prisma.calendarEvent.updateMany({
where: { id: req.params.id, orgId: req.user!.orgId },
data: updateData,
})
if (event.count === 0) return res.status(404).json({ success: false, message: '事件不存在' })
res.json({ success: true })
} catch (err) {
next(err)
}
})
// 删除日历事件
router.delete('/:id', authMiddleware, async (req: AuthRequest, res: Response, next: NextFunction) => {
try {
const result = await prisma.calendarEvent.deleteMany({
where: { id: req.params.id, orgId: req.user!.orgId },
})
if (result.count === 0) return res.status(404).json({ success: false, message: '事件不存在' })
res.json({ success: true })
} catch (err) {
next(err)
}
})
export default router