feat: Sprint 2 — Stepper/InlineAlert组件 + 首页TaskCenter任务中心 + /workspace/next-actions API + Money.tsx BatchDetail集成工作流步骤条和质量门禁

This commit is contained in:
selfrelease
2026-07-31 17:52:02 +08:00
parent c181d3fa45
commit 55b6867c9c
6 changed files with 484 additions and 0 deletions
+116
View File
@@ -239,6 +239,122 @@ router.get('/workforce-stats', authMiddleware, async (req: AuthRequest, res: Res
}
})
// 工作台下一步行动 — 聚合待办任务、草稿批次、到期合同、特殊状态
router.get('/workspace/next-actions', authMiddleware, async (req: AuthRequest, res: Response, next: NextFunction) => {
try {
const orgId = req.user!.orgId
const now = new Date()
const in30Days = new Date(now.getTime() + 30 * 24 * 60 * 60 * 1000)
// 1. 待办风险项
const riskItems = await prisma.riskItem.findMany({
where: { orgId, status: 'PENDING' },
orderBy: { createdAt: 'desc' },
take: 10,
select: { id: true, title: true, type: true, level: true, deadline: true, actionUrl: true, employeeId: true },
})
// 2. 草稿发薪批次
const draftBatches = await prisma.payrollBatch.findMany({
where: { orgId, status: 'DRAFT' },
orderBy: { createdAt: 'desc' },
take: 5,
select: { id: true, name: true, month: true, type: true, employeeCount: true, totalPay: true },
})
// 3. 即将到期合同(30天内)
const expiringContracts = await prisma.laborContract.findMany({
where: {
orgId,
endDate: { gte: now, lte: in30Days },
employee: { status: 'ACTIVE' },
},
include: { employee: { select: { id: true, name: true, department: true } } },
orderBy: { endDate: 'asc' },
take: 10,
})
// 4. 特殊状态员工
const specialStatusEmployees = await prisma.employee.findMany({
where: {
orgId,
status: 'ACTIVE',
OR: [
{ isPregnant: true },
{ isInMedicalPeriod: true },
{ isWorkInjured: true },
],
},
select: { id: true, name: true, department: true, isPregnant: true, isInMedicalPeriod: true, isWorkInjured: true },
take: 10,
})
// 按优先级分组
const actions: Array<{ category: string; priority: 'high' | 'medium' | 'low'; items: any[] }> = [
{
category: '待办事项',
priority: 'high',
items: riskItems.map(r => ({
id: r.id,
title: r.title,
type: r.type,
level: r.level,
dueDate: r.deadline?.toISOString().slice(0, 10),
link: r.actionUrl || '/',
})),
},
{
category: '发薪批次',
priority: 'high',
items: draftBatches.map(b => ({
id: b.id,
title: `${b.name}${b.month}`,
subtitle: `${b.employeeCount}人 · 应发 ¥${(b.totalPay || 0).toLocaleString()}`,
link: '/money',
})),
},
{
category: '合同到期',
priority: 'medium',
items: expiringContracts.map(c => ({
id: c.id,
title: `${c.employee?.name} 的合同将于 ${c.endDate?.toISOString().slice(0, 10)} 到期`,
subtitle: c.employee?.department || '',
link: '/roster',
})),
},
{
category: '特殊状态',
priority: 'medium',
items: specialStatusEmployees.map(e => ({
id: e.id,
title: e.name,
subtitle: [
e.isPregnant ? '孕期' : '',
e.isInMedicalPeriod ? '医疗期' : '',
e.isWorkInjured ? '工伤' : '',
].filter(Boolean).join('、'),
link: '/special-status',
})),
},
]
// 过滤空分类
const filteredActions = actions.filter(a => a.items.length > 0)
const totalCount = filteredActions.reduce((s, a) => s + a.items.length, 0)
res.json({
success: true,
data: {
actions: filteredActions,
totalCount,
},
})
} catch (err) {
next(err)
}
})
// 入离职统计看板 — 按月聚合入职和离职人数
router.get('/turnover-stats', authMiddleware, async (req: AuthRequest, res: Response, next: NextFunction) => {
try {