R39: 完成员工档案汇报链查询

这个提交包含在:
2026-03-28 17:42:09 +08:00
父节点 f33431fdb8
当前提交 1cac1447b1
共修改 6 个文件,包含 347 行新增和 9 行删除
+74
查看文件
@@ -8,6 +8,7 @@ const { FEATURE_FLAG_KEYS, evaluateFeatureFlag } = require("../services/featureF
const UUID_SCHEMA = z.string().uuid();
const DATE_SCHEMA = z.string().regex(/^\d{4}-\d{2}-\d{2}$/);
const REPORTING_CHAIN_MAX_DEPTH = 12;
const PROFILE_PATCH_SCHEMA = z
.object({
departmentId: UUID_SCHEMA.optional(),
@@ -121,6 +122,17 @@ function mapLifecycleEvent(item) {
};
}
function mapOrganizationNode(item) {
return {
id: item.id,
employeeNo: item.employee_no,
name: item.name,
title: item.title,
status: item.status,
department: item.department ? { id: item.department.id, name: item.department.name } : null,
};
}
async function getLifecycleBlockers(prisma, employeeId) {
const [openAssetCount, pendingWorkflowTaskCount] = await Promise.all([
prisma.assetCard.count({ where: { assignee_employee_id: employeeId } }),
@@ -199,6 +211,59 @@ async function maybeAdvanceLifecycle(tx, employeeId) {
return { autoChanged: false, completedOffboarding: false, blockers: null };
}
async function buildEmployeeOrganization(prisma, employee) {
const reportingChain = [];
const seenEmployeeIds = new Set([employee.id]);
let currentManagerId = employee.direct_manager_id ?? null;
let depth = 0;
while (currentManagerId && depth < REPORTING_CHAIN_MAX_DEPTH) {
if (seenEmployeeIds.has(currentManagerId)) {
break;
}
const manager = await prisma.employee.findUnique({
where: { id: currentManagerId },
select: {
id: true,
employee_no: true,
name: true,
title: true,
status: true,
direct_manager_id: true,
department: { select: { id: true, name: true } },
},
});
if (!manager) {
break;
}
reportingChain.push(mapOrganizationNode(manager));
seenEmployeeIds.add(manager.id);
currentManagerId = manager.direct_manager_id ?? null;
depth += 1;
}
const directReports = await prisma.employee.findMany({
where: { direct_manager_id: employee.id },
orderBy: [{ status: "asc" }, { name: "asc" }],
select: {
id: true,
employee_no: true,
name: true,
title: true,
status: true,
department: { select: { id: true, name: true } },
},
});
return {
reportingChain: [...reportingChain.reverse(), mapOrganizationNode(employee)],
directReports: directReports.map(mapOrganizationNode),
};
}
async function buildEmployeeProfile(prisma, employeeId) {
const [employee, checklistTasks, lifecycleEvents, assignedAssets, blockers] = await Promise.all([
prisma.employee.findUnique({
@@ -241,6 +306,8 @@ async function buildEmployeeProfile(prisma, employeeId) {
throw new AppError({ code: "SYS_404", message: "Not Found", statusCode: 404 });
}
const organization = await buildEmployeeOrganization(prisma, employee);
return {
employee: {
id: employee.id,
@@ -270,6 +337,7 @@ async function buildEmployeeProfile(prisma, employeeId) {
checklistTasks: checklistTasks.map(mapChecklistTask),
blockers,
},
organization,
assignedAssets: assignedAssets.map((item) => ({
id: item.id,
assetNo: item.asset_no,
@@ -521,3 +589,9 @@ async function employeesRoutes(fastify) {
}
module.exports = employeesRoutes;
module.exports.__testables = {
buildEmployeeOrganization,
buildEmployeeProfile,
mapOrganizationNode,
};