feat: 工作台概览优化 - 人力成本展示、解聘记录完善、数据准确性修复
- 解聘列表显示所有状态记录(含已撤销/已完成) - 月度解聘人数排除CANCELLED和DRAFT状态 - 工作台新增当月人力成本和年度累计成本卡片 - 年度累计成本包含当前月未归档数据 - 企业总成本公式加入加班费 - 统计卡片月加班费改为工资条数量,避免与动态区重复 - 本月工作动态与风险分布改为左右两列布局 - 人力成本区域优化:2列网格+突出总成本
This commit is contained in:
@@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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({
|
||||
|
||||
Reference in New Issue
Block a user