R44 A2-A8 完成员工高级筛选批量操作与统计页
这个提交包含在:
@@ -0,0 +1,96 @@
|
||||
const { currentDateString, parseDateOnlyString } = require("../utils/date");
|
||||
|
||||
function addDays(date, days) {
|
||||
const next = new Date(date);
|
||||
next.setUTCDate(next.getUTCDate() + days);
|
||||
return next;
|
||||
}
|
||||
|
||||
async function buildEmployeeStats(prisma, options = {}) {
|
||||
const timeZone = options.timeZone ?? "Asia/Shanghai";
|
||||
const todayString = currentDateString(timeZone);
|
||||
const today = parseDateOnlyString(todayString);
|
||||
const last30DateFrom = addDays(today, -29);
|
||||
const next30DateTo = addDays(today, 30);
|
||||
const activeEmployeeWhere = { status: "active" };
|
||||
|
||||
const [employmentStatusRows, departmentRows, departments, recentHires, recentDepartures, expiringContracts] = await Promise.all([
|
||||
prisma.employee.groupBy({
|
||||
by: ["employment_status"],
|
||||
where: activeEmployeeWhere,
|
||||
_count: { _all: true },
|
||||
}),
|
||||
prisma.employee.groupBy({
|
||||
by: ["department_id"],
|
||||
where: activeEmployeeWhere,
|
||||
_count: { _all: true },
|
||||
}),
|
||||
prisma.department.findMany({
|
||||
where: { archived_at: null },
|
||||
select: { id: true, name: true },
|
||||
}),
|
||||
prisma.employee.count({
|
||||
where: {
|
||||
hire_date: {
|
||||
gte: last30DateFrom,
|
||||
lte: new Date(`${todayString}T23:59:59.999Z`),
|
||||
},
|
||||
},
|
||||
}),
|
||||
prisma.employee.count({
|
||||
where: {
|
||||
last_working_date: {
|
||||
gte: last30DateFrom,
|
||||
lte: new Date(`${todayString}T23:59:59.999Z`),
|
||||
},
|
||||
},
|
||||
}),
|
||||
prisma.employeeContract.count({
|
||||
where: {
|
||||
terminated_date: null,
|
||||
end_date: {
|
||||
gte: today,
|
||||
lte: new Date(`${next30DateTo.toISOString().slice(0, 10)}T23:59:59.999Z`),
|
||||
},
|
||||
employee: activeEmployeeWhere,
|
||||
},
|
||||
}),
|
||||
]);
|
||||
|
||||
const departmentMap = new Map(departments.map((item) => [item.id, item.name]));
|
||||
|
||||
return {
|
||||
asOfDate: todayString,
|
||||
employmentStatusSummary: {
|
||||
total: employmentStatusRows.reduce((sum, item) => sum + item._count._all, 0),
|
||||
items: employmentStatusRows
|
||||
.map((item) => ({
|
||||
status: item.employment_status,
|
||||
count: item._count._all,
|
||||
}))
|
||||
.sort((left, right) => right.count - left.count || left.status.localeCompare(right.status)),
|
||||
},
|
||||
departmentHeadcount: departmentRows
|
||||
.map((item) => ({
|
||||
departmentId: item.department_id,
|
||||
departmentName: departmentMap.get(item.department_id) ?? "未知部门",
|
||||
count: item._count._all,
|
||||
}))
|
||||
.sort((left, right) => right.count - left.count || left.departmentName.localeCompare(right.departmentName)),
|
||||
recentMovements: {
|
||||
dateFrom: last30DateFrom.toISOString().slice(0, 10),
|
||||
dateTo: todayString,
|
||||
hires: recentHires,
|
||||
departures: recentDepartures,
|
||||
},
|
||||
expiringContracts: {
|
||||
dateFrom: todayString,
|
||||
dateTo: next30DateTo.toISOString().slice(0, 10),
|
||||
count: expiringContracts,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
buildEmployeeStats,
|
||||
};
|
||||
在新工单中引用
屏蔽一个用户