diff --git a/backend/src/services/risk.service.ts b/backend/src/services/risk.service.ts index 6f5b512..5a60ebd 100644 --- a/backend/src/services/risk.service.ts +++ b/backend/src/services/risk.service.ts @@ -278,12 +278,15 @@ export async function getDashboardData(orgId: string) { const currentMonth = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}` const monthStart = new Date(now.getFullYear(), now.getMonth(), 1) const monthEnd = new Date(now.getFullYear(), now.getMonth() + 1, 0, 23, 59, 59) + const yearStart = new Date(now.getFullYear(), 0, 1) + const yearEnd = new Date(now.getFullYear(), 11, 31, 23, 59, 59) const [ employeeCount, highRisks, pendingRisks, riskItems, resolvedItems, overtimeRecords, payslips, batchEntries, socialConfig, housingConfig, monthContracts, monthTerminations, monthDisciplinary, monthAttendance, monthSeverancePay, + yearBatchEntries, yearOvertimeRecords, yearSeverancePay, ] = await Promise.all([ prisma.employee.count({ where: { orgId, status: 'ACTIVE' } }), prisma.riskItem.count({ where: { orgId, status: 'PENDING', level: 'HIGH', type: { in: ['CONTRACT', 'TERMINATION'] } } }), @@ -319,7 +322,7 @@ export async function getDashboardData(orgId: string) { where: { orgId, createdAt: { gte: monthStart, lte: monthEnd } }, }), prisma.terminationRecord.count({ - where: { orgId, createdAt: { gte: monthStart, lte: monthEnd } }, + where: { orgId, createdAt: { gte: monthStart, lte: monthEnd }, status: { notIn: ['CANCELLED', 'DRAFT'] } }, }), prisma.disciplinaryRecord.count({ where: { orgId, violationDate: { gte: monthStart, lte: monthEnd } }, @@ -328,7 +331,22 @@ export async function getDashboardData(orgId: string) { where: { orgId, date: { gte: monthStart, lte: monthEnd } }, }), prisma.terminationRecord.aggregate({ - where: { orgId, createdAt: { gte: monthStart, lte: monthEnd } }, + where: { orgId, createdAt: { gte: monthStart, lte: monthEnd }, status: { notIn: ['CANCELLED', 'DRAFT'] } }, + _sum: { compensation: true }, + }), + // 年度累计:已归档批次条目 + prisma.batchEntry.findMany({ + where: { orgId, batch: { month: { startsWith: `${now.getFullYear()}-` }, status: 'ARCHIVED' } }, + select: { baseSalary: true, overtimePay: true, allowance: true, deduction: true, bonus: true, totalPay: true, socialEmp: true, socialOrg: true, housingEmp: true, housingOrg: true, tax: true, netPay: true, employeeId: true }, + }), + // 年度累计:加班记录 + prisma.overtimeRecord.findMany({ + where: { orgId, month: { startsWith: `${now.getFullYear()}-` } }, + select: { totalPay: true, weekdayHours: true, weekendHours: true, holidayHours: true }, + }), + // 年度累计:补偿金 + prisma.terminationRecord.aggregate({ + where: { orgId, createdAt: { gte: yearStart, lte: yearEnd }, status: { notIn: ['CANCELLED', 'DRAFT'] } }, _sum: { compensation: true }, }), ]) @@ -444,8 +462,8 @@ export async function getDashboardData(orgId: string) { housingEmp: housingEmpTotal, estimatedTax, severancePay: monthSeverancePay._sum.compensation || 0, - // 企业总成本 = 工资总额 + 企业社保 + 企业公积金 + 经济补偿金 - orgTotalCost: totalPay + socialOrgTotal + housingOrgTotal + (monthSeverancePay._sum.compensation || 0), + // 企业总成本 = 工资总额 + 企业社保 + 企业公积金 + 加班费 + 经济补偿金 + orgTotalCost: totalPay + socialOrgTotal + housingOrgTotal + monthlyOvertimePay + (monthSeverancePay._sum.compensation || 0), // 员工实发 = 工资总额 - 个人社保 - 个人公积金 - 个税 empNetPay: useArchivedData ? totalNetPay : totalPay - socialEmpTotal - housingEmpTotal - estimatedTax, } @@ -461,6 +479,29 @@ export async function getDashboardData(orgId: string) { overtimePay: monthlyOvertimePay, } + // 年度累计人力资源成本 = 已归档批次汇总 + 当前月数据(当前月可能未归档) + const yearArchivedPay = yearBatchEntries.reduce((s: number, e: typeof yearBatchEntries[number]) => s + e.totalPay, 0) + const yearArchivedSocialOrg = yearBatchEntries.reduce((s: number, e: typeof yearBatchEntries[number]) => s + e.socialOrg, 0) + const yearArchivedHousingOrg = yearBatchEntries.reduce((s: number, e: typeof yearBatchEntries[number]) => s + e.housingOrg, 0) + const yearOvertimePay = yearOvertimeRecords.reduce((s: number, r: typeof yearOvertimeRecords[number]) => s + r.totalPay, 0) + const yearSeverance = yearSeverancePay._sum.compensation || 0 + + // 当前月是否已包含在归档批次中 + const currentMonthArchived = batchEntries.length > 0 + const yearTotalPay = yearArchivedPay + (currentMonthArchived ? 0 : totalPay) + const yearSocialOrg = yearArchivedSocialOrg + (currentMonthArchived ? 0 : socialOrgTotal) + const yearHousingOrg = yearArchivedHousingOrg + (currentMonthArchived ? 0 : housingOrgTotal) + + const yearCostSummary = { + year: now.getFullYear().toString(), + totalPay: yearTotalPay, + socialOrg: yearSocialOrg, + housingOrg: yearHousingOrg, + overtimePay: yearOvertimePay, + severancePay: yearSeverance, + orgTotalCost: yearTotalPay + yearSocialOrg + yearHousingOrg + yearOvertimePay + yearSeverance, + } + const riskDistribution = { contract: riskItems.filter((r: typeof riskItems[number]) => r.type === 'CONTRACT').length, salary: riskItems.filter((r: typeof riskItems[number]) => r.type === 'SALARY').length, @@ -521,5 +562,6 @@ export async function getDashboardData(orgId: string) { aiPrediction: null, payrollSummary, monthlyActivities, + yearCostSummary, } } diff --git a/backend/src/services/termination.service.ts b/backend/src/services/termination.service.ts index 7dead09..ee78379 100644 --- a/backend/src/services/termination.service.ts +++ b/backend/src/services/termination.service.ts @@ -743,7 +743,7 @@ export async function getDrafts(orgId: string, status?: string) { if (status) { where.status = status } else { - where.status = { in: ['DRAFT', 'PENDING_APPROVAL', 'APPROVED', 'REJECTED'] } + where.status = { in: ['DRAFT', 'PENDING_APPROVAL', 'APPROVED', 'REJECTED', 'EXECUTING', 'COMPLETED', 'CANCELLED'] } } const records = await prisma.terminationRecord.findMany({ diff --git a/frontend/src/pages/Dashboard.tsx b/frontend/src/pages/Dashboard.tsx index 7ec3b9c..8fffefd 100644 --- a/frontend/src/pages/Dashboard.tsx +++ b/frontend/src/pages/Dashboard.tsx @@ -116,15 +116,36 @@ export default function Dashboard() { if (!data) return null + const payroll = data.payrollSummary + const activities = data.monthlyActivities + const yearCost = data.yearCostSummary + const stats = [ { label: '在管员工', value: data.stats.employeeCount, icon: Users, color: 'text-primary' }, { label: '高风险', value: data.stats.highRiskCount, icon: AlertTriangle, color: 'text-danger' }, { label: '待办事项', value: data.stats.todoCount, icon: CheckSquare, color: 'text-warning' }, - { label: '月加班费', value: fmt(data.stats.monthlyOvertimePay), icon: DollarSign, color: 'text-safe' }, + { label: '工资条', value: payroll?.payslipCount ?? 0, icon: Receipt, color: 'text-safe' }, ] - const payroll = data.payrollSummary - const activities = data.monthlyActivities + // 当月人力成本 + const monthCostItems = [ + { label: '工资总额', value: payroll?.totalPay ?? 0, color: 'text-primary' }, + { label: '企业社保', value: payroll?.socialOrg ?? 0, color: 'text-blue-600' }, + { label: '企业公积金', value: payroll?.housingOrg ?? 0, color: 'text-purple-600' }, + { label: '加班费', value: payroll?.overtimePay ?? 0, color: 'text-safe' }, + { label: '补偿金', value: payroll?.severancePay ?? 0, color: 'text-orange-600' }, + ] + const monthTotalCost = payroll?.orgTotalCost ?? 0 + + // 年度累计人力成本 + const yearCostItems = [ + { label: '工资总额', value: yearCost?.totalPay ?? 0, color: 'text-primary' }, + { label: '企业社保', value: yearCost?.socialOrg ?? 0, color: 'text-blue-600' }, + { label: '企业公积金', value: yearCost?.housingOrg ?? 0, color: 'text-purple-600' }, + { label: '加班费', value: yearCost?.overtimePay ?? 0, color: 'text-safe' }, + { label: '补偿金', value: yearCost?.severancePay ?? 0, color: 'text-orange-600' }, + ] + const yearTotalCost = yearCost?.orgTotalCost ?? 0 const activityItems = [ { label: '新签合同', value: activities?.newContracts ?? 0, icon: FileText, color: 'text-primary' }, @@ -213,6 +234,49 @@ export default function Dashboard() { })} + {/* 人力成本概览:当月 + 年度累计 */} +