feat: 商业保险/员工福利独立页面+易签宝电子签署框架
- 商业保险:从社公商保中拆出为独立页面,侧边栏新增「福利保障」分组 - 员工福利:新建完整模块(方案管理+批量参保+员工汇总),Prisma模型+后端路由+前端页面 - 电子签署:搭建易签宝对接框架(ESignRecord模型+创建/查询/取消/回调接口+前端签署管理页面) - 侧边栏新增「福利保障」分组:商业保险、员工福利、电子签署 - Prisma schema 新增6个模型:CommercialInsurancePlan/Enrollment, EmployeeBenefitPlan/Enrollment, ESignRecord
This commit is contained in:
@@ -0,0 +1,132 @@
|
||||
import { Router, Response, NextFunction } from 'express'
|
||||
import { z } from 'zod'
|
||||
import prisma from '../lib/prisma'
|
||||
import { authMiddleware, AuthRequest } from '../middleware/auth'
|
||||
|
||||
const router = Router()
|
||||
router.use(authMiddleware)
|
||||
|
||||
const planSchema = z.object({
|
||||
name: z.string().min(1),
|
||||
type: z.string(),
|
||||
provider: z.string().min(1),
|
||||
policyNo: z.string().optional(),
|
||||
premium: z.number().default(0),
|
||||
coverageAmount: z.number().default(0),
|
||||
effectiveFrom: z.string(),
|
||||
effectiveTo: z.string().optional(),
|
||||
description: z.string().optional(),
|
||||
})
|
||||
|
||||
// 方案列表
|
||||
router.get('/plans', async (req: AuthRequest, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const plans = await prisma.commercialInsurancePlan.findMany({
|
||||
where: { orgId: req.user!.orgId },
|
||||
orderBy: { createdAt: 'desc' },
|
||||
include: { _count: { select: { enrollments: { where: { status: 'ACTIVE' } } } } },
|
||||
})
|
||||
res.json({ success: true, data: plans })
|
||||
} catch (err) { next(err) }
|
||||
})
|
||||
|
||||
// 方案详情(含参保人员)
|
||||
router.get('/plans/:planId/enrollments', async (req: AuthRequest, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const enrollments = await prisma.commercialInsuranceEnrollment.findMany({
|
||||
where: { orgId: req.user!.orgId, planId: req.params.planId },
|
||||
include: {
|
||||
employee: { select: { id: true, name: true, department: true, idCardNumber: true } },
|
||||
},
|
||||
orderBy: { createdAt: 'desc' },
|
||||
})
|
||||
const data = enrollments.map((e: any) => ({
|
||||
id: e.id,
|
||||
employeeId: e.employeeId,
|
||||
name: e.employee.name,
|
||||
department: e.employee.department,
|
||||
idCardMasked: e.employee.idCardNumber ? e.employee.idCardNumber.slice(0, 3) + '****' + e.employee.idCardNumber.slice(-4) : null,
|
||||
premium: e.premium,
|
||||
effectiveFrom: e.effectiveFrom,
|
||||
effectiveTo: e.effectiveTo,
|
||||
status: e.status,
|
||||
}))
|
||||
res.json({ success: true, data })
|
||||
} catch (err) { next(err) }
|
||||
})
|
||||
|
||||
// 创建方案
|
||||
router.post('/plans', async (req: AuthRequest, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const data = planSchema.parse(req.body)
|
||||
const plan = await prisma.commercialInsurancePlan.create({
|
||||
data: { ...data, orgId: req.user!.orgId, createdBy: req.user!.id },
|
||||
})
|
||||
res.json({ success: true, data: plan })
|
||||
} catch (err) { next(err) }
|
||||
})
|
||||
|
||||
// 更新方案
|
||||
router.put('/plans/:planId', async (req: AuthRequest, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const data = planSchema.partial().parse(req.body)
|
||||
const plan = await prisma.commercialInsurancePlan.update({
|
||||
where: { id: req.params.planId, orgId: req.user!.orgId },
|
||||
data,
|
||||
})
|
||||
res.json({ success: true, data: plan })
|
||||
} catch (err) { next(err) }
|
||||
})
|
||||
|
||||
// 删除方案
|
||||
router.delete('/plans/:planId', async (req: AuthRequest, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
await prisma.commercialInsurancePlan.delete({
|
||||
where: { id: req.params.planId, orgId: req.user!.orgId },
|
||||
})
|
||||
res.json({ success: true })
|
||||
} catch (err) { next(err) }
|
||||
})
|
||||
|
||||
// 批量参保
|
||||
router.post('/plans/:planId/enroll', async (req: AuthRequest, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const { employeeIds, premium, effectiveFrom } = req.body as { employeeIds: string[]; premium: number; effectiveFrom: string }
|
||||
const plan = await prisma.commercialInsurancePlan.findFirst({ where: { id: req.params.planId, orgId: req.user!.orgId } })
|
||||
if (!plan) return res.status(404).json({ success: false, error: { message: '方案不存在' } })
|
||||
|
||||
const existing = await prisma.commercialInsuranceEnrollment.findMany({
|
||||
where: { planId: plan.id, employeeId: { in: employeeIds }, status: 'ACTIVE' },
|
||||
select: { employeeId: true },
|
||||
})
|
||||
const existingIds = new Set(existing.map((e: any) => e.employeeId))
|
||||
const newIds = employeeIds.filter((id) => !existingIds.has(id))
|
||||
|
||||
if (newIds.length > 0) {
|
||||
await prisma.commercialInsuranceEnrollment.createMany({
|
||||
data: newIds.map((empId) => ({
|
||||
orgId: req.user!.orgId,
|
||||
planId: plan.id,
|
||||
employeeId: empId,
|
||||
premium: premium || plan.premium,
|
||||
effectiveFrom: effectiveFrom || plan.effectiveFrom,
|
||||
})),
|
||||
})
|
||||
}
|
||||
res.json({ success: true, data: { enrolled: newIds.length, skipped: existingIds.size } })
|
||||
} catch (err) { next(err) }
|
||||
})
|
||||
|
||||
// 退保
|
||||
router.post('/enrollments/:enrollmentId/terminate', async (req: AuthRequest, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const { effectiveTo } = req.body as { effectiveTo: string }
|
||||
const enrollment = await prisma.commercialInsuranceEnrollment.update({
|
||||
where: { id: req.params.enrollmentId, orgId: req.user!.orgId },
|
||||
data: { status: 'TERMINATED', effectiveTo: effectiveTo || new Date().toISOString().slice(0, 10) },
|
||||
})
|
||||
res.json({ success: true, data: enrollment })
|
||||
} catch (err) { next(err) }
|
||||
})
|
||||
|
||||
export default router
|
||||
Reference in New Issue
Block a user