R44 A2-A8 完成员工高级筛选批量操作与统计页
这个提交包含在:
+29
-52
@@ -2,6 +2,8 @@ const sharp = require("sharp");
|
||||
const { z } = require("zod");
|
||||
|
||||
const usersRoutes = require("./users");
|
||||
const { buildDefaultChecklist, resolveEffectiveDate, applyEmployeeTransfer } = require("../services/employeeLifecycle");
|
||||
const { buildEmployeeStats } = require("../services/employeeStats");
|
||||
const { currentDateString } = require("../utils/date");
|
||||
const { AppError } = require("../utils/AppError");
|
||||
const { sendData } = require("../utils/reply");
|
||||
@@ -103,10 +105,6 @@ async function ensureFeatureEnabled(fastify, request) {
|
||||
}
|
||||
}
|
||||
|
||||
function resolveEffectiveDate(rawValue) {
|
||||
return rawValue ? new Date(`${rawValue}T00:00:00.000Z`) : new Date();
|
||||
}
|
||||
|
||||
function canManageProfile(fastify, request, employeeId) {
|
||||
return request.employee.id === employeeId || fastify.hasPermission(request.employee, "system.users") || fastify.hasPermission(request.employee, "report.dashboard.view");
|
||||
}
|
||||
@@ -242,26 +240,6 @@ async function ensureEmployeeContractExists(prisma, employeeId, contractId) {
|
||||
return contract;
|
||||
}
|
||||
|
||||
function buildDefaultChecklist(scene, employeeId, actorId) {
|
||||
const common = { employee_id: employeeId };
|
||||
if (scene === "onboarding") {
|
||||
return [
|
||||
{ ...common, scene, task_type: "account_setup", title: "完成账号与权限初始化", status: "pending" },
|
||||
{ ...common, scene, task_type: "asset_issue", title: "确认办公资产发放", status: "pending" },
|
||||
{ ...common, scene, task_type: "policy_ack", title: "确认制度与入职须知", status: "pending" },
|
||||
];
|
||||
}
|
||||
if (scene === "transfer") {
|
||||
return [{ ...common, scene, task_type: "handover_note", title: "确认调岗交接说明", status: "pending" }];
|
||||
}
|
||||
return [
|
||||
{ ...common, scene, task_type: "asset_return", title: "确认资产归还/转交", status: "pending" },
|
||||
{ ...common, scene, task_type: "access_revoke", title: "确认账号和权限回收", status: "pending" },
|
||||
{ ...common, scene, task_type: "expense_clearance", title: "确认费用与审批待办已清理", status: "pending" },
|
||||
{ ...common, scene, task_type: "handover_note", title: "确认离职交接说明", status: "pending" },
|
||||
];
|
||||
}
|
||||
|
||||
async function maybeAdvanceLifecycle(tx, employeeId) {
|
||||
const employee = await tx.employee.findUnique({ where: { id: employeeId } });
|
||||
if (!employee) {
|
||||
@@ -449,7 +427,12 @@ async function buildEmployeeProfile(prisma, employeeId) {
|
||||
}
|
||||
|
||||
async function employeesRoutes(fastify) {
|
||||
await usersRoutes(fastify, { basePath: "/employees", allowDepartmentFilter: true });
|
||||
await usersRoutes(fastify, {
|
||||
basePath: "/employees",
|
||||
allowDepartmentFilter: true,
|
||||
listPermissionCodes: ["system.users", "report.dashboard.view"],
|
||||
enableBatchActions: false,
|
||||
});
|
||||
|
||||
fastify.get("/avatars/:employeeId", async (request, reply) => {
|
||||
const employeeId = parseId(request.params?.employeeId);
|
||||
@@ -549,6 +532,17 @@ async function employeesRoutes(fastify) {
|
||||
return sendData(reply, request, { items });
|
||||
});
|
||||
|
||||
fastify.get("/employees/stats", { preHandler: fastify.authenticate }, async (request, reply) => {
|
||||
if (!canViewPeopleDirectory(fastify, request)) {
|
||||
throw new AppError({ code: "PERM_001", message: "无权限", statusCode: 403, details: { permission: "employee.stats.view" } });
|
||||
}
|
||||
|
||||
const stats = await buildEmployeeStats(fastify.prisma, {
|
||||
timeZone: fastify.config.appTimezone,
|
||||
});
|
||||
return sendData(reply, request, stats);
|
||||
});
|
||||
|
||||
fastify.get("/employees/:id/profile", { preHandler: fastify.authenticate }, async (request, reply) => {
|
||||
await ensureFeatureEnabled(fastify, request);
|
||||
const employeeId = parseId(request.params?.id);
|
||||
@@ -726,7 +720,7 @@ async function employeesRoutes(fastify) {
|
||||
created_by: request.employee.id,
|
||||
},
|
||||
});
|
||||
const checklist = buildDefaultChecklist("onboarding", employeeId, request.employee.id).map((item) => ({ ...item, due_date: body.probationEndDate ? new Date(`${body.probationEndDate}T00:00:00.000Z`) : null }));
|
||||
const checklist = buildDefaultChecklist("onboarding", employeeId).map((item) => ({ ...item, due_date: body.probationEndDate ? new Date(`${body.probationEndDate}T00:00:00.000Z`) : null }));
|
||||
await tx.employeeChecklistTask.createMany({ data: checklist, skipDuplicates: false });
|
||||
});
|
||||
const profile = await buildEmployeeProfile(fastify.prisma, employeeId);
|
||||
@@ -766,31 +760,14 @@ async function employeesRoutes(fastify) {
|
||||
const body = parseBody(TRANSFER_SCHEMA, request.body);
|
||||
await fastify.prisma.$transaction(async (tx) => {
|
||||
await ensureEmployeeExists(tx, employeeId);
|
||||
await tx.employee.update({
|
||||
where: { id: employeeId },
|
||||
data: {
|
||||
...(body.departmentId !== undefined ? { department_id: body.departmentId } : {}),
|
||||
...(body.directManagerId !== undefined ? { direct_manager_id: body.directManagerId } : {}),
|
||||
...(body.title !== undefined ? { title: body.title } : {}),
|
||||
updated_by: request.employee.id,
|
||||
},
|
||||
});
|
||||
await tx.employeeLifecycleEvent.create({
|
||||
data: {
|
||||
employee_id: employeeId,
|
||||
event_type: "transfer",
|
||||
effective_date: resolveEffectiveDate(body.effectiveDate),
|
||||
payload_json: {
|
||||
departmentId: body.departmentId ?? null,
|
||||
directManagerId: body.directManagerId ?? null,
|
||||
title: body.title ?? null,
|
||||
notes: body.notes ?? null,
|
||||
},
|
||||
created_by: request.employee.id,
|
||||
},
|
||||
});
|
||||
await tx.employeeChecklistTask.createMany({
|
||||
data: buildDefaultChecklist("transfer", employeeId, request.employee.id),
|
||||
await applyEmployeeTransfer(tx, {
|
||||
employeeId,
|
||||
actorId: request.employee.id,
|
||||
departmentId: body.departmentId,
|
||||
directManagerId: body.directManagerId,
|
||||
title: body.title,
|
||||
effectiveDate: body.effectiveDate,
|
||||
notes: body.notes,
|
||||
});
|
||||
});
|
||||
const profile = await buildEmployeeProfile(fastify.prisma, employeeId);
|
||||
@@ -823,7 +800,7 @@ async function employeesRoutes(fastify) {
|
||||
created_by: request.employee.id,
|
||||
},
|
||||
});
|
||||
await tx.employeeChecklistTask.createMany({ data: buildDefaultChecklist("offboarding", employeeId, request.employee.id) });
|
||||
await tx.employeeChecklistTask.createMany({ data: buildDefaultChecklist("offboarding", employeeId) });
|
||||
});
|
||||
const profile = await buildEmployeeProfile(fastify.prisma, employeeId);
|
||||
return sendData(reply, request, profile);
|
||||
|
||||
在新工单中引用
屏蔽一个用户