567 lines
22 KiB
C#
567 lines
22 KiB
C#
using Indotalent.ConfigBackEnd.Extensions;
|
|
using Indotalent.Infrastructure.Database;
|
|
using MediatR;
|
|
using Microsoft.EntityFrameworkCore;
|
|
|
|
namespace Indotalent.Features.Root.Home.Cqrs;
|
|
|
|
public class SalaryReviewDto
|
|
{
|
|
public string? EmployeeName { get; set; }
|
|
public string? Increment { get; set; }
|
|
public string? Status { get; set; }
|
|
}
|
|
|
|
public class CareerPathDto
|
|
{
|
|
public string? EmployeeName { get; set; }
|
|
public string? ToGrade { get; set; }
|
|
public DateTime? Date { get; set; }
|
|
}
|
|
|
|
public class StaffPerformanceDto
|
|
{
|
|
public string? Name { get; set; }
|
|
public string? Dept { get; set; }
|
|
public string? LoS { get; set; }
|
|
public int KpiScore { get; set; }
|
|
public string? Grade { get; set; }
|
|
public int ReviewJourney { get; set; }
|
|
}
|
|
|
|
public class ActiveTransferDto
|
|
{
|
|
public string? EmployeeName { get; set; }
|
|
public string? ToLocation { get; set; }
|
|
public int ImpactLevel { get; set; }
|
|
}
|
|
|
|
public class LeaveTicketDto
|
|
{
|
|
public string? TicketId { get; set; }
|
|
public string? Status { get; set; }
|
|
public string? DueInfo { get; set; }
|
|
}
|
|
|
|
public class CompStatsDto
|
|
{
|
|
public string? Label { get; set; }
|
|
public string? Value { get; set; }
|
|
public double Percentage { get; set; }
|
|
public string? Color { get; set; }
|
|
}
|
|
|
|
public class CareerBenchmarkDto
|
|
{
|
|
public string? EmployeeName { get; set; }
|
|
public string? Position { get; set; }
|
|
public double SalaryRatio { get; set; }
|
|
public string? Status { get; set; }
|
|
public int StarRating { get; set; }
|
|
}
|
|
|
|
// Chart data DTOs
|
|
public class HeadcountTrendDto
|
|
{
|
|
public string? Month { get; set; }
|
|
public int Count { get; set; }
|
|
public int NewHires { get; set; }
|
|
}
|
|
|
|
public class DeptDistributionDto
|
|
{
|
|
public string? Department { get; set; }
|
|
public int Count { get; set; }
|
|
}
|
|
|
|
public class LeaveTypeBreakdownDto
|
|
{
|
|
public string? Type { get; set; }
|
|
public int Count { get; set; }
|
|
}
|
|
|
|
public class RecentDealDto
|
|
{
|
|
public string? Date { get; set; }
|
|
public string? Ref { get; set; }
|
|
public string? Description { get; set; }
|
|
public string? Value { get; set; }
|
|
public string? Status { get; set; }
|
|
}
|
|
|
|
public class ActivityFeedDto
|
|
{
|
|
public string? Initials { get; set; }
|
|
public string? AvatarColor { get; set; }
|
|
public string? Title { get; set; }
|
|
public string? Subtitle { get; set; }
|
|
public string? TimeAgo { get; set; }
|
|
public string? ChipLabel { get; set; }
|
|
public string? ChipClass { get; set; }
|
|
}
|
|
|
|
public class DashboardDataResponse
|
|
{
|
|
public int TotalStaff { get; set; }
|
|
public double FteValue { get; set; }
|
|
public decimal AvgCostPerHire { get; set; }
|
|
public int HeadcountForecast { get; set; }
|
|
public double RetentionRate { get; set; }
|
|
public double LeaversRate { get; set; }
|
|
public double EngagementRate { get; set; }
|
|
public double EmployeeExperienceNps { get; set; }
|
|
public double EmployeeExperienceSat { get; set; }
|
|
public double RoiPercent { get; set; }
|
|
public double CertifiedPercent { get; set; }
|
|
public int PipelineSourced { get; set; }
|
|
public int PipelineInterviewed { get; set; }
|
|
public int PipelineOffered { get; set; }
|
|
public int TotalBranches { get; set; }
|
|
public int TotalDepartments { get; set; }
|
|
public int TopTalentCount { get; set; }
|
|
public int CorePlayerCount { get; set; }
|
|
public int LowPerfCount { get; set; }
|
|
|
|
public List<StaffPerformanceDto> TopPerformers { get; set; } = new();
|
|
public List<ActiveTransferDto> RecentTransfers { get; set; } = new();
|
|
public List<LeaveTicketDto> PendingLeaves { get; set; } = new();
|
|
public List<SalaryReviewDto> RecentAppraisals { get; set; } = new();
|
|
public List<CareerPathDto> UpcomingPromotions { get; set; } = new();
|
|
public List<CompStatsDto> CompensationStats { get; set; } = new();
|
|
public List<CareerBenchmarkDto> CareerBenchmarks { get; set; } = new();
|
|
|
|
// Chart data
|
|
public List<HeadcountTrendDto> HeadcountTrends { get; set; } = new();
|
|
public List<DeptDistributionDto> DeptDistributions { get; set; } = new();
|
|
public List<LeaveTypeBreakdownDto> LeaveTypeBreakdowns { get; set; } = new();
|
|
public List<RecentDealDto> RecentDeals { get; set; } = new();
|
|
public List<ActivityFeedDto> ActivityFeeds { get; set; } = new();
|
|
|
|
// Additional KPIs
|
|
public int TotalLeaveRequests { get; set; }
|
|
public int TotalAppraisals { get; set; }
|
|
public int TotalTransferRequests { get; set; }
|
|
public int ActivePromotions { get; set; }
|
|
public decimal TotalPayrollAmount { get; set; }
|
|
|
|
public string FormattedAvgCost
|
|
{
|
|
get
|
|
{
|
|
if (AvgCostPerHire >= 1000000)
|
|
return (AvgCostPerHire / 1000000m).ToString("F1") + "M";
|
|
if (AvgCostPerHire >= 1000)
|
|
return (AvgCostPerHire / 1000m).ToString("F1") + "K";
|
|
return AvgCostPerHire.ToString("C0");
|
|
}
|
|
}
|
|
|
|
public string FormattedTotalPayroll
|
|
{
|
|
get
|
|
{
|
|
if (TotalPayrollAmount >= 1000000)
|
|
return (TotalPayrollAmount / 1000000m).ToString("F1") + "M";
|
|
if (TotalPayrollAmount >= 1000)
|
|
return (TotalPayrollAmount / 1000m).ToString("F1") + "K";
|
|
return TotalPayrollAmount.ToString("N0");
|
|
}
|
|
}
|
|
}
|
|
|
|
public record GetDashboardQuery() : IRequest<DashboardDataResponse>;
|
|
|
|
public class DashboardHandler : IRequestHandler<GetDashboardQuery, DashboardDataResponse>
|
|
{
|
|
private readonly AppDbContext _context;
|
|
public DashboardHandler(AppDbContext context) => _context = context;
|
|
|
|
public async Task<DashboardDataResponse> Handle(GetDashboardQuery request, CancellationToken ct)
|
|
{
|
|
var today = DateTime.Today;
|
|
|
|
// --- Employee counts ---
|
|
var totalStaff = await _context.Employee.NotDeletedOnly().CountAsync(ct);
|
|
var fullTimeStaff = await _context.Employee.NotDeletedOnly().CountAsync(x => x.EmploymentType == "Full-Time", ct);
|
|
double fteValue = totalStaff > 0 ? (double)fullTimeStaff / totalStaff : 0;
|
|
|
|
// --- Resigned / Leavers ---
|
|
var resignedCount = await _context.Employee.NotDeletedOnly().CountAsync(x => x.ResignedDate != null && x.ResignedDate <= today, ct);
|
|
double retentionRate = totalStaff > 0 ? Math.Round((double)(totalStaff - resignedCount) / totalStaff * 100, 1) : 0;
|
|
double leaversRate = totalStaff > 0 ? Math.Round((double)resignedCount / totalStaff * 100, 1) : 0;
|
|
|
|
// --- Average Cost per Hire (from appraisals) ---
|
|
var avgSalary = await _context.Appraisal.NotDeletedOnly().AverageAsync(x => (decimal?)x.NewSalary, ct) ?? 0;
|
|
var forecastCount = await _context.Employee.NotDeletedOnly().CountAsync(x => x.JoinedDate >= today.AddMonths(-3), ct);
|
|
|
|
// --- Branches & Departments ---
|
|
var totalBranches = await _context.Branch.NotDeletedOnly().CountAsync(ct);
|
|
var totalDepts = await _context.Department.NotDeletedOnly().CountAsync(ct);
|
|
|
|
// --- Totals ---
|
|
var totalLeaveRequests = await _context.LeaveRequest.NotDeletedOnly().CountAsync(ct);
|
|
var totalAppraisals = await _context.Appraisal.NotDeletedOnly().CountAsync(ct);
|
|
var totalTransfers = await _context.Transfer.NotDeletedOnly().CountAsync(ct);
|
|
var activePromotions = await _context.Promotion.NotDeletedOnly()
|
|
.CountAsync(x => x.Status == "Active" || x.Status == "Pending", ct);
|
|
|
|
// --- Evaluation / Performance data ---
|
|
var allEvaluations = await _context.Evaluation
|
|
.Include(x => x.Employee).ThenInclude(e => e!.Department)
|
|
.Include(x => x.Employee).ThenInclude(e => e!.Grade)
|
|
.NotDeletedOnly()
|
|
.ToListAsync(ct);
|
|
|
|
var rawEvaluationData = allEvaluations
|
|
.OrderByDescending(x => x.CreatedAt)
|
|
.Take(10)
|
|
.ToList();
|
|
|
|
var performers = rawEvaluationData.Select(x =>
|
|
{
|
|
int.TryParse(x.FinalScore, out var s);
|
|
int.TryParse(x.Rating, out var r);
|
|
return new StaffPerformanceDto
|
|
{
|
|
Name = $"{x.Employee?.FirstName} {x.Employee?.LastName}",
|
|
Dept = x.Employee?.Department?.Name ?? "N/A",
|
|
Grade = x.Employee?.Grade?.Name ?? "-",
|
|
KpiScore = s,
|
|
ReviewJourney = r > 0 ? r : 1,
|
|
LoS = x.Employee?.JoinedDate.HasValue == true ? $"{(today.Year - x.Employee.JoinedDate.Value.Year)}y" : "0y"
|
|
};
|
|
}).ToList();
|
|
|
|
// --- Engagement Rate: average final score from all evaluations ---
|
|
var scoredEvaluations = allEvaluations
|
|
.Select(x => { int.TryParse(x.FinalScore, out var s); return s; })
|
|
.Where(s => s > 0)
|
|
.ToList();
|
|
double engagementRate = scoredEvaluations.Any() ? Math.Round(scoredEvaluations.Average(), 1) : 0;
|
|
|
|
// --- Employee Experience (EX): NPS & SAT ---
|
|
double employeeExperienceNps = 0;
|
|
double employeeExperienceSat = 0;
|
|
if (allEvaluations.Any())
|
|
{
|
|
var ratingsWithValue = allEvaluations
|
|
.Select(x => { int.TryParse(x.Rating, out var r); return r; })
|
|
.Where(r => r > 0)
|
|
.ToList();
|
|
employeeExperienceNps = ratingsWithValue.Any()
|
|
? Math.Round(ratingsWithValue.Average() / 10.0 * 10.0, 1)
|
|
: 0;
|
|
employeeExperienceSat = scoredEvaluations.Any() ? Math.Round(scoredEvaluations.Average(), 0) : 0;
|
|
}
|
|
|
|
// --- ROI: derived from appraisal increment percentage average ---
|
|
var avgIncrementPct = await _context.Appraisal
|
|
.NotDeletedOnly()
|
|
.AverageAsync(x => (double?)x.IncrementPercentage, ct) ?? 0;
|
|
double roiPercent = Math.Round(avgIncrementPct, 1);
|
|
|
|
// --- Certified % (Talent Scoring) ---
|
|
int totalScoredEmployees = scoredEvaluations.Count;
|
|
int certifiedCount = scoredEvaluations.Count(s => s >= 80);
|
|
double certifiedPercent = totalScoredEmployees > 0
|
|
? Math.Round((double)certifiedCount / totalScoredEmployees * 100, 0)
|
|
: 0;
|
|
|
|
// --- Recruitment Pipeline ---
|
|
var employeesJoinedThisYear = await _context.Employee
|
|
.NotDeletedOnly()
|
|
.CountAsync(x => x.JoinedDate >= new DateTime(today.Year, 1, 1), ct);
|
|
var totalEvaluationsThisYear = allEvaluations.Count(x => x.EvaluationDate >= new DateTime(today.Year, 1, 1));
|
|
var totalPromotionsThisYear = await _context.Promotion
|
|
.NotDeletedOnly()
|
|
.CountAsync(x => x.EffectiveDate >= new DateTime(today.Year, 1, 1), ct);
|
|
|
|
int pipelineSourced = employeesJoinedThisYear + totalEvaluationsThisYear;
|
|
int pipelineInterviewed = totalEvaluationsThisYear;
|
|
int pipelineOffered = totalPromotionsThisYear;
|
|
|
|
// --- Transfers ---
|
|
var transfers = await _context.Transfer
|
|
.Include(x => x.Employee).Include(x => x.ToBranch)
|
|
.NotDeletedOnly()
|
|
.OrderByDescending(x => x.CreatedAt)
|
|
.Take(5)
|
|
.Select(x => new ActiveTransferDto
|
|
{
|
|
EmployeeName = x.Employee != null ? x.Employee.FirstName : "User",
|
|
ToLocation = x.ToBranch != null ? x.ToBranch.Name : "Other",
|
|
ImpactLevel = x.Status == "Processed" ? 5 : 3
|
|
}).ToListAsync(ct);
|
|
|
|
// --- Leave requests (latest 5, any status) ---
|
|
var leaves = await _context.LeaveRequest
|
|
.NotDeletedOnly()
|
|
.OrderByDescending(x => x.CreatedAt)
|
|
.Take(5)
|
|
.Select(x => new LeaveTicketDto
|
|
{
|
|
TicketId = x.AutoNumber,
|
|
Status = x.Status ?? "Submitted",
|
|
DueInfo = x.StartDate.ToString("MMM dd")
|
|
}).ToListAsync(ct);
|
|
|
|
// --- Appraisals (materialize first for safe memory projection) ---
|
|
var recentAppraisals = await _context.Appraisal
|
|
.Include(x => x.Employee)
|
|
.NotDeletedOnly()
|
|
.OrderByDescending(x => x.CreatedAt)
|
|
.Take(3)
|
|
.ToListAsync(ct);
|
|
|
|
var salaryReviews = recentAppraisals.Select(x => new SalaryReviewDto
|
|
{
|
|
EmployeeName = x.Employee != null ? x.Employee.FirstName : "Staff",
|
|
Increment = x.IncrementPercentage.ToString("F1") + "%",
|
|
Status = x.Status
|
|
}).ToList();
|
|
|
|
// --- Promotions (materialize first) ---
|
|
var upcomingPromotionsRaw = await _context.Promotion
|
|
.Include(x => x.Employee)
|
|
.NotDeletedOnly()
|
|
.OrderByDescending(x => x.CreatedAt)
|
|
.Take(3)
|
|
.ToListAsync(ct);
|
|
|
|
var promoDtos = upcomingPromotionsRaw.Select(x => new CareerPathDto
|
|
{
|
|
EmployeeName = x.Employee != null ? x.Employee.FirstName : "Staff",
|
|
ToGrade = x.ToGrade,
|
|
Date = x.EffectiveDate
|
|
}).ToList();
|
|
|
|
// --- Payroll compensation stats ---
|
|
var latestPayroll = await _context.PayrollProcess
|
|
.NotDeletedOnly()
|
|
.OrderByDescending(x => x.ToDate)
|
|
.FirstOrDefaultAsync(ct);
|
|
|
|
var compStats = new List<CompStatsDto>();
|
|
if (latestPayroll != null)
|
|
{
|
|
var totalGross = latestPayroll.TotalGross > 0 ? latestPayroll.TotalGross : 1;
|
|
compStats.Add(new CompStatsDto
|
|
{
|
|
Label = "Take Home Pay Net",
|
|
Value = latestPayroll.TotalTakeHomePay.ToString("N0"),
|
|
Percentage = (double)(latestPayroll.TotalTakeHomePay / totalGross) * 100,
|
|
Color = "#3b82f6"
|
|
});
|
|
compStats.Add(new CompStatsDto
|
|
{
|
|
Label = "Tax & Deductions",
|
|
Value = latestPayroll.TotalDeduction.ToString("N0"),
|
|
Percentage = (double)(latestPayroll.TotalDeduction / totalGross) * 100,
|
|
Color = "#ef4444"
|
|
});
|
|
compStats.Add(new CompStatsDto
|
|
{
|
|
Label = "Incomes/Allowances",
|
|
Value = latestPayroll.TotalIncome.ToString("N0"),
|
|
Percentage = (double)(latestPayroll.TotalIncome / totalGross) * 100,
|
|
Color = "#10b981"
|
|
});
|
|
}
|
|
|
|
// --- Career benchmarks (materialize first) ---
|
|
var benchmarks = await _context.Employee
|
|
.Include(x => x.Grade)
|
|
.Include(x => x.Designation)
|
|
.NotDeletedOnly()
|
|
.Where(x => x.Grade != null)
|
|
.OrderByDescending(x => x.CreatedAt)
|
|
.Take(5)
|
|
.ToListAsync(ct);
|
|
|
|
var careerBenchmarks = benchmarks.Select(x =>
|
|
{
|
|
var ratio = x.Grade!.SalaryTo > 0 ? (double)(x.BasicSalary / x.Grade.SalaryTo) * 100 : 0;
|
|
return new CareerBenchmarkDto
|
|
{
|
|
EmployeeName = x.FirstName,
|
|
Position = x.Designation?.Name ?? "Staff",
|
|
SalaryRatio = ratio,
|
|
Status = ratio > 90 ? "Top Range" : ratio > 60 ? "Mid Range" : "Entry",
|
|
StarRating = ratio > 80 ? 3 : ratio > 40 ? 2 : 1
|
|
};
|
|
}).ToList();
|
|
|
|
// --- CHART: Headcount Trend (last 6 months) ---
|
|
var headcountTrends = new List<HeadcountTrendDto>();
|
|
for (int i = 5; i >= 0; i--)
|
|
{
|
|
var monthStart = new DateTime(today.Year, today.Month, 1).AddMonths(-i);
|
|
var monthEnd = monthStart.AddMonths(1);
|
|
var count = await _context.Employee.NotDeletedOnly()
|
|
.CountAsync(x => x.JoinedDate < monthEnd
|
|
&& (x.ResignedDate == null || x.ResignedDate >= monthStart), ct);
|
|
var newHires = await _context.Employee.NotDeletedOnly()
|
|
.CountAsync(x => x.JoinedDate >= monthStart && x.JoinedDate < monthEnd, ct);
|
|
headcountTrends.Add(new HeadcountTrendDto
|
|
{
|
|
Month = monthStart.ToString("MMM"),
|
|
Count = count,
|
|
NewHires = newHires
|
|
});
|
|
}
|
|
|
|
// --- CHART: Department Distribution ---
|
|
var allDepartments = await _context.Department
|
|
.NotDeletedOnly()
|
|
.Select(x => x.Name)
|
|
.ToListAsync(ct);
|
|
|
|
var employeeDeptCounts = await _context.Employee
|
|
.Include(x => x.Department)
|
|
.NotDeletedOnly()
|
|
.Where(x => x.Department != null)
|
|
.Select(x => new { DeptName = x.Department!.Name })
|
|
.ToListAsync(ct);
|
|
|
|
var deptDistributions = allDepartments
|
|
.Select(dept => new DeptDistributionDto
|
|
{
|
|
Department = dept ?? "Other",
|
|
Count = employeeDeptCounts.Count(e => e.DeptName == dept)
|
|
})
|
|
.OrderByDescending(x => x.Count)
|
|
.ThenBy(x => x.Department)
|
|
.ToList();
|
|
|
|
// --- CHART: Leave Type Breakdown (materialize first, group in memory) ---
|
|
var leaveTypeRawNames = await _context.LeaveRequest
|
|
.Include(x => x.LeaveCategory)
|
|
.NotDeletedOnly()
|
|
.Where(x => x.LeaveCategory != null)
|
|
.Select(x => new { CatName = x.LeaveCategory!.Name })
|
|
.ToListAsync(ct);
|
|
|
|
var leaveTypeBreakdowns = leaveTypeRawNames
|
|
.GroupBy(x => x.CatName)
|
|
.Select(g => new LeaveTypeBreakdownDto
|
|
{
|
|
Type = g.Key ?? "Other",
|
|
Count = g.Count()
|
|
})
|
|
.OrderByDescending(x => x.Count)
|
|
.ToList();
|
|
|
|
// --- Recent Deals (appraisals as deals) ---
|
|
var recentDeals = recentAppraisals.Take(5).Select(x => new RecentDealDto
|
|
{
|
|
Date = x.CreatedAt?.ToString("MMM dd") ?? "-",
|
|
Ref = x.AutoNumber ?? "-",
|
|
Description = (x.Employee?.FirstName ?? "Staff") + " appraisal review",
|
|
Value = x.NewSalary.ToString("N0"),
|
|
Status = x.Status ?? "Pending"
|
|
}).ToList();
|
|
|
|
// --- Activity Feed ---
|
|
var activityFeeds = new List<ActivityFeedDto>();
|
|
|
|
if (recentAppraisals.Any())
|
|
{
|
|
var a = recentAppraisals.First();
|
|
activityFeeds.Add(new ActivityFeedDto
|
|
{
|
|
Initials = (a.Employee?.FirstName?.Length > 0 ? a.Employee.FirstName[..1] : "S").ToUpper(),
|
|
AvatarColor = "#10b981",
|
|
Title = $"<b>Appraisal Completed</b> — {a.Employee?.FirstName ?? "Staff"}",
|
|
Subtitle = $"📋 Salary: {a.NewSalary:N0} · Increment: {a.IncrementPercentage:F1}%",
|
|
TimeAgo = "1 hour ago",
|
|
ChipLabel = a.Status ?? "Completed",
|
|
ChipClass = "chip-green"
|
|
});
|
|
}
|
|
|
|
if (upcomingPromotionsRaw.Any())
|
|
{
|
|
var p = upcomingPromotionsRaw.First();
|
|
activityFeeds.Add(new ActivityFeedDto
|
|
{
|
|
Initials = (p.Employee?.FirstName?.Length > 0 ? p.Employee.FirstName[..1] : "P").ToUpper(),
|
|
AvatarColor = "#6366f1",
|
|
Title = $"<b>Promotion Scheduled</b> — {p.Employee?.FirstName ?? "Staff"}",
|
|
Subtitle = $"🏷️ To Grade: {p.ToGrade} · Effective: {p.EffectiveDate:dd MMM yyyy}",
|
|
TimeAgo = "2 hours ago",
|
|
ChipLabel = "Scheduled",
|
|
ChipClass = "chip-indigo"
|
|
});
|
|
}
|
|
|
|
if (leaves.Any())
|
|
{
|
|
var l = leaves.First();
|
|
activityFeeds.Add(new ActivityFeedDto
|
|
{
|
|
Initials = "LR",
|
|
AvatarColor = "#f97316",
|
|
Title = $"<b>Leave Request Pending</b> — #{l.TicketId}",
|
|
Subtitle = $"📅 Status: {l.Status} · Due: {l.DueInfo}",
|
|
TimeAgo = "3 hours ago",
|
|
ChipLabel = "Pending",
|
|
ChipClass = "chip-amber"
|
|
});
|
|
}
|
|
|
|
if (transfers.Any())
|
|
{
|
|
var t = transfers.First();
|
|
activityFeeds.Add(new ActivityFeedDto
|
|
{
|
|
Initials = (t.EmployeeName?.Length > 0 ? t.EmployeeName[..1] : "T").ToUpper(),
|
|
AvatarColor = "#2563eb",
|
|
Title = $"<b>Transfer Request</b> — {t.EmployeeName}",
|
|
Subtitle = $"📍 To: {t.ToLocation} · Impact Level: {t.ImpactLevel}/5",
|
|
TimeAgo = "5 hours ago",
|
|
ChipLabel = t.ImpactLevel > 3 ? "Processing" : "Pending",
|
|
ChipClass = t.ImpactLevel > 3 ? "chip-blue" : "chip-indigo"
|
|
});
|
|
}
|
|
|
|
return new DashboardDataResponse
|
|
{
|
|
TotalStaff = totalStaff,
|
|
FteValue = Math.Round(fteValue, 2),
|
|
AvgCostPerHire = avgSalary,
|
|
HeadcountForecast = forecastCount + 5,
|
|
RetentionRate = retentionRate,
|
|
LeaversRate = leaversRate,
|
|
EngagementRate = engagementRate,
|
|
EmployeeExperienceNps = employeeExperienceNps,
|
|
EmployeeExperienceSat = employeeExperienceSat,
|
|
RoiPercent = roiPercent,
|
|
CertifiedPercent = certifiedPercent,
|
|
PipelineSourced = pipelineSourced,
|
|
PipelineInterviewed = pipelineInterviewed,
|
|
PipelineOffered = pipelineOffered,
|
|
TotalBranches = totalBranches,
|
|
TotalDepartments = totalDepts,
|
|
TotalLeaveRequests = totalLeaveRequests,
|
|
TotalAppraisals = totalAppraisals,
|
|
TotalTransferRequests = totalTransfers,
|
|
ActivePromotions = activePromotions,
|
|
TotalPayrollAmount = latestPayroll?.TotalGross ?? 0,
|
|
TopPerformers = performers,
|
|
RecentTransfers = transfers,
|
|
PendingLeaves = leaves,
|
|
RecentAppraisals = salaryReviews,
|
|
UpcomingPromotions = promoDtos,
|
|
CompensationStats = compStats,
|
|
CareerBenchmarks = careerBenchmarks,
|
|
HeadcountTrends = headcountTrends,
|
|
DeptDistributions = deptDistributions,
|
|
LeaveTypeBreakdowns = leaveTypeBreakdowns,
|
|
RecentDeals = recentDeals,
|
|
ActivityFeeds = activityFeeds,
|
|
TopTalentCount = performers.Count(x => x.KpiScore >= 85),
|
|
CorePlayerCount = performers.Count(x => x.KpiScore < 85 && x.KpiScore >= 60),
|
|
LowPerfCount = performers.Count(x => x.KpiScore < 60)
|
|
};
|
|
}
|
|
} |