feat: 培训/绩效/违纪员工端签收+管理端签字状态只读化

- 后端: portal新增培训/绩效/违纪列表查看+签收接口,签收时创建证据链
- 管理端: 弹窗去掉HR手动签字勾选,改为只读显示签字状态
- 绩效列表新增签字状态列
- 员工端: 新增MyRecords页面,支持培训签收/拒绝、绩效签字、违纪签字
- 员工端导航新增'我的记录'入口
- EvidenceCategory新增TRAINING/PERFORMANCE类型
This commit is contained in:
freedakgmail
2026-08-05 08:39:04 +08:00
parent 15dde27701
commit 3afb383172
9 changed files with 437 additions and 18 deletions
+138
View File
@@ -1007,4 +1007,142 @@ router.post('/esign/:id/sign', portalAuth, async (req: any, res, next) => {
} catch (err) { next(err) }
})
// ========== 员工端:培训签收 ==========
// 查看自己的培训记录
router.get('/training', portalAuth, async (req: any, res, next) => {
try {
const { id: employeeId, orgId } = req.employee
const records = await prisma.trainingRecord.findMany({
where: { employeeId, orgId },
orderBy: { trainingDate: 'desc' },
})
res.json({ success: true, data: records })
} catch (err) { next(err) }
})
// 培训签收
router.post('/training/:id/sign', portalAuth, async (req: any, res, next) => {
try {
const { id: employeeId, orgId } = req.employee
const record = await prisma.trainingRecord.findFirst({
where: { id: req.params.id, employeeId, orgId, ackStatus: 'PENDING' },
})
if (!record) return res.status(404).json({ success: false, error: { message: '记录不存在或已签收' } })
const updated = await prisma.trainingRecord.update({
where: { id: record.id },
data: { ackStatus: 'SIGNED', ackDate: new Date() },
})
await createEvidence({
orgId,
category: 'TRAINING',
refId: record.id,
employeeId,
events: [{ action: `培训签收:${record.topic}`, timestamp: new Date().toISOString(), ip: req.ip, userAgent: req.headers['user-agent'] }],
createdBy: employeeId,
}).catch(() => {})
res.json({ success: true, data: updated, message: '签收成功' })
} catch (err) { next(err) }
})
// 培训拒绝签收
router.post('/training/:id/refuse', portalAuth, async (req: any, res, next) => {
try {
const { id: employeeId, orgId } = req.employee
const record = await prisma.trainingRecord.findFirst({
where: { id: req.params.id, employeeId, orgId, ackStatus: 'PENDING' },
})
if (!record) return res.status(404).json({ success: false, error: { message: '记录不存在或已处理' } })
const updated = await prisma.trainingRecord.update({
where: { id: record.id },
data: { ackStatus: 'REFUSED', ackDate: new Date() },
})
res.json({ success: true, data: updated, message: '已拒绝签收' })
} catch (err) { next(err) }
})
// ========== 员工端:绩效签字 ==========
// 查看自己的绩效记录
router.get('/performance', portalAuth, async (req: any, res, next) => {
try {
const { id: employeeId, orgId } = req.employee
const records = await prisma.performanceRecord.findMany({
where: { employeeId, orgId },
orderBy: { period: 'desc' },
})
res.json({ success: true, data: records })
} catch (err) { next(err) }
})
// 绩效签字确认
router.post('/performance/:id/sign', portalAuth, async (req: any, res, next) => {
try {
const { id: employeeId, orgId } = req.employee
const record = await prisma.performanceRecord.findFirst({
where: { id: req.params.id, employeeId, orgId, employeeAck: false },
})
if (!record) return res.status(404).json({ success: false, error: { message: '记录不存在或已签字' } })
const updated = await prisma.performanceRecord.update({
where: { id: record.id },
data: { employeeAck: true, ackDate: new Date() },
})
await createEvidence({
orgId,
category: 'PERFORMANCE',
refId: record.id,
employeeId,
events: [{ action: `绩效签字确认:${record.period}`, timestamp: new Date().toISOString(), ip: req.ip, userAgent: req.headers['user-agent'] }],
createdBy: employeeId,
}).catch(() => {})
res.json({ success: true, data: updated, message: '签字成功' })
} catch (err) { next(err) }
})
// ========== 员工端:违纪签字 ==========
// 查看自己的违纪记录
router.get('/disciplinary', portalAuth, async (req: any, res, next) => {
try {
const { id: employeeId, orgId } = req.employee
const records = await prisma.disciplinaryRecord.findMany({
where: { employeeId, orgId },
orderBy: { violationDate: 'desc' },
})
res.json({ success: true, data: records })
} catch (err) { next(err) }
})
// 违纪签字确认
router.post('/disciplinary/:id/sign', portalAuth, async (req: any, res, next) => {
try {
const { id: employeeId, orgId } = req.employee
const record = await prisma.disciplinaryRecord.findFirst({
where: { id: req.params.id, employeeId, orgId, employeeAck: false },
})
if (!record) return res.status(404).json({ success: false, error: { message: '记录不存在或已签字' } })
const updated = await prisma.disciplinaryRecord.update({
where: { id: record.id },
data: { employeeAck: true, ackDate: new Date(), ackMethod: 'SIGN' },
})
await createEvidence({
orgId,
category: 'DISCIPLINARY',
refId: record.id,
employeeId,
events: [{ action: `违纪签字确认:${record.violationType}`, timestamp: new Date().toISOString(), ip: req.ip, userAgent: req.headers['user-agent'] }],
createdBy: employeeId,
}).catch(() => {})
res.json({ success: true, data: updated, message: '签字成功' })
} catch (err) { next(err) }
})
export default router
+2
View File
@@ -12,6 +12,8 @@ export type EvidenceCategory =
| 'DISCIPLINARY'
| 'ATTENDANCE'
| 'TERMINATION'
| 'TRAINING'
| 'PERFORMANCE'
/**
* 创建证据链记录