R46 A1-A7:落地绩效考核模块首版
这个提交包含在:
@@ -6,6 +6,22 @@ function addDays(date, days) {
|
||||
return next;
|
||||
}
|
||||
|
||||
function toNumber(value) {
|
||||
return value == null ? null : Number(value);
|
||||
}
|
||||
|
||||
function buildPerformanceDistribution(rows) {
|
||||
const countByScore = new Map(rows.map((item) => [toNumber(item.overall_score), item._count._all]));
|
||||
const items = [];
|
||||
for (let score = 1; score <= 5; score += 0.5) {
|
||||
items.push({
|
||||
score: Number(score.toFixed(1)),
|
||||
count: countByScore.get(Number(score.toFixed(1))) ?? 0,
|
||||
});
|
||||
}
|
||||
return items;
|
||||
}
|
||||
|
||||
async function buildEmployeeStats(prisma, options = {}) {
|
||||
const timeZone = options.timeZone ?? "Asia/Shanghai";
|
||||
const todayString = currentDateString(timeZone);
|
||||
@@ -14,7 +30,7 @@ async function buildEmployeeStats(prisma, options = {}) {
|
||||
const next30DateTo = addDays(today, 30);
|
||||
const activeEmployeeWhere = { status: "active" };
|
||||
|
||||
const [employmentStatusRows, departmentRows, departments, recentHires, recentDepartures, expiringContracts, validCertificateHolders, expiringCertificates] = await Promise.all([
|
||||
const [employmentStatusRows, departmentRows, departments, recentHires, recentDepartures, expiringContracts, validCertificateHolders, expiringCertificates, activeCycle, completedCycle] = await Promise.all([
|
||||
prisma.employee.groupBy({
|
||||
by: ["employment_status"],
|
||||
where: activeEmployeeWhere,
|
||||
@@ -78,9 +94,53 @@ async function buildEmployeeStats(prisma, options = {}) {
|
||||
},
|
||||
},
|
||||
}),
|
||||
prisma.performanceReviewCycle.findFirst({
|
||||
where: { status: "active" },
|
||||
orderBy: [{ activated_at: "desc" }, { start_date: "desc" }],
|
||||
}),
|
||||
prisma.performanceReviewCycle.findFirst({
|
||||
where: { status: "completed" },
|
||||
orderBy: [{ completed_at: "desc" }, { end_date: "desc" }],
|
||||
}),
|
||||
]);
|
||||
|
||||
const departmentMap = new Map(departments.map((item) => [item.id, item.name]));
|
||||
const performanceCycle = activeCycle ?? completedCycle ?? null;
|
||||
let performanceSummary = {
|
||||
cycle: null,
|
||||
averageScore: null,
|
||||
reviewedCount: 0,
|
||||
distribution: [],
|
||||
};
|
||||
|
||||
if (performanceCycle) {
|
||||
const [reviewAggregate, reviewDistribution] = await Promise.all([
|
||||
prisma.performanceReview.aggregate({
|
||||
where: { cycle_id: performanceCycle.id },
|
||||
_avg: { overall_score: true },
|
||||
_count: { _all: true },
|
||||
}),
|
||||
prisma.performanceReview.groupBy({
|
||||
by: ["overall_score"],
|
||||
where: { cycle_id: performanceCycle.id },
|
||||
_count: { _all: true },
|
||||
}),
|
||||
]);
|
||||
|
||||
performanceSummary = {
|
||||
cycle: {
|
||||
id: performanceCycle.id,
|
||||
name: performanceCycle.name,
|
||||
cycleType: performanceCycle.cycle_type,
|
||||
status: performanceCycle.status,
|
||||
startDate: performanceCycle.start_date?.toISOString?.()?.slice(0, 10) ?? null,
|
||||
endDate: performanceCycle.end_date?.toISOString?.()?.slice(0, 10) ?? null,
|
||||
},
|
||||
averageScore: toNumber(reviewAggregate._avg.overall_score),
|
||||
reviewedCount: reviewAggregate._count._all,
|
||||
distribution: buildPerformanceDistribution(reviewDistribution),
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
asOfDate: todayString,
|
||||
@@ -117,6 +177,7 @@ async function buildEmployeeStats(prisma, options = {}) {
|
||||
validHolderCount: validCertificateHolders.length,
|
||||
expiringSoonCount: expiringCertificates,
|
||||
},
|
||||
performanceSummary,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
在新工单中引用
屏蔽一个用户