diff --git a/backend/src/routes/commercial-insurance.routes.ts b/backend/src/routes/commercial-insurance.routes.ts index d874b7c..b3e1ed0 100644 --- a/backend/src/routes/commercial-insurance.routes.ts +++ b/backend/src/routes/commercial-insurance.routes.ts @@ -129,4 +129,43 @@ router.post('/enrollments/:enrollmentId/terminate', async (req: AuthRequest, res } catch (err) { next(err) } }) +// 员工商险汇总(按员工维度) +router.get('/employee-summary', async (req: AuthRequest, res: Response, next: NextFunction) => { + try { + const enrollments = await prisma.commercialInsuranceEnrollment.findMany({ + where: { orgId: req.user!.orgId, status: 'ACTIVE' }, + include: { + employee: { select: { id: true, name: true, department: true } }, + plan: { select: { id: true, name: true, type: true, provider: true, coverageAmount: true } }, + }, + }) + const summary: Record = {} + for (const e of enrollments) { + if (!summary[e.employeeId]) { + summary[e.employeeId] = { + employeeId: e.employeeId, + name: e.employee.name, + department: e.employee.department, + insurances: [], + totalPremium: 0, + totalCoverage: 0, + } + } + summary[e.employeeId].insurances.push({ + planId: e.planId, + planName: e.plan.name, + type: e.plan.type, + provider: e.plan.provider, + premium: e.premium, + coverageAmount: e.plan.coverageAmount, + effectiveFrom: e.effectiveFrom, + effectiveTo: e.effectiveTo, + }) + summary[e.employeeId].totalPremium += e.premium + summary[e.employeeId].totalCoverage += e.plan.coverageAmount + } + res.json({ success: true, data: Object.values(summary) }) + } catch (err) { next(err) } +}) + export default router diff --git a/frontend/src/lib/api-services.ts b/frontend/src/lib/api-services.ts index 1c57e7b..b68ea48 100644 --- a/frontend/src/lib/api-services.ts +++ b/frontend/src/lib/api-services.ts @@ -578,6 +578,9 @@ export const commercialInsuranceApi = { /** 退保 */ terminateEnrollment: (enrollmentId: string, effectiveTo?: string) => post(`/commercial-insurance/enrollments/${enrollmentId}/terminate`, { effectiveTo }), + /** 员工商险汇总 */ + employeeSummary: () => + get('/commercial-insurance/employee-summary').then(unwrap()), } // ========== 员工福利 ========== diff --git a/frontend/src/pages/CommercialInsurance.tsx b/frontend/src/pages/CommercialInsurance.tsx index 914435a..20f381f 100644 --- a/frontend/src/pages/CommercialInsurance.tsx +++ b/frontend/src/pages/CommercialInsurance.tsx @@ -1,8 +1,33 @@ +import { useState } from 'react' +import { useQuery } from '@tanstack/react-query' import { Shield } from 'lucide-react' import PageGuide from '../components/ui/PageGuide' +import Card from '../components/ui/Card' +import { commercialInsuranceApi } from '../lib/api-services' import CommercialInsuranceTab from './social-insurance/CommercialInsuranceTab' +const fmt = (n: number) => (n || 0).toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 }) + +const INSURANCE_TYPES: Record = { + ACCIDENT: { label: '意外伤害险', color: 'bg-orange-50 text-orange-700 border border-orange-200' }, + SUPPLEMENTARY_MEDICAL: { label: '补充医疗保险', color: 'bg-blue-50 text-blue-700 border border-blue-200' }, + EMPLOYER_LIABILITY: { label: '雇主责任险', color: 'bg-purple-50 text-purple-700 border border-purple-200' }, + CRITICAL_ILLNESS: { label: '重大疾病险', color: 'bg-rose-50 text-rose-700 border border-rose-200' }, + GROUP_LIFE: { label: '团体寿险', color: 'bg-teal-50 text-teal-700 border border-teal-200' }, + OTHER: { label: '其他', color: 'bg-gray-50 text-gray-700 border border-gray-200' }, +} + export default function CommercialInsurance() { + const [tab, setTab] = useState<'plans' | 'summary'>('plans') + + const { data: employeeSummary = [] } = useQuery({ + queryKey: ['commercial-insurance-employee-summary'], + queryFn: async () => { + return await commercialInsuranceApi.employeeSummary() + }, + enabled: tab === 'summary', + }) + return (
@@ -17,7 +42,80 @@ export default function CommercialInsurance() { 点击「新增方案」创建商险计划,选择方案后可查看参保人员列表。 商业保险是系统增值服务模块,支持方案管理、参保人员追踪、保费统计。 - + + {/* Tab 切换 */} +
+ {(['plans', 'summary'] as const).map((t) => ( + + ))} +
+ + {/* ========== 方案管理 Tab ========== */} + {tab === 'plans' && } + + {/* ========== 员工汇总 Tab ========== */} + {tab === 'summary' && ( + + {employeeSummary.length === 0 ? ( +
暂无员工商险数据
+ ) : ( +
+ + + + + + + + + + + + {employeeSummary.map((e: any) => ( + + + + + + + + ))} + + + + + + + + +
姓名部门保险项年保费合计保额合计
{e.name}{e.department} +
+ {e.insurances.map((ins: any, i: number) => { + const typeCfg = INSURANCE_TYPES[ins.type] || INSURANCE_TYPES.OTHER + return ( + + {ins.planName} · {ins.provider} · ¥{fmt(ins.premium)} + + ) + })} +
+
¥{fmt(e.totalPremium)}¥{fmt(e.totalCoverage)}
合计({employeeSummary.length}人) + ¥{fmt(employeeSummary.reduce((sum: number, e: any) => sum + e.totalPremium, 0))} + + ¥{fmt(employeeSummary.reduce((sum: number, e: any) => sum + e.totalCoverage, 0))} +
+
+ )} +
+ )}
) }