function buildAvatarUrl(employeeId, avatarPath) { return avatarPath ? `/api/avatars/${employeeId}` : null; } function compareByEmployeeNo(left, right) { return left.employee_no.localeCompare(right.employee_no, "en"); } function mapDepartmentSummary(department) { if (!department) { return null; } return { id: department.id, name: department.name, }; } function mapExternalManager(employee, departmentId, activeEmployeeById) { if (!employee.direct_manager_id) { return null; } const manager = activeEmployeeById.get(employee.direct_manager_id) ?? null; if (!manager) { return null; } if (manager.department_id === departmentId) { return null; } return { id: manager.id, employeeNo: manager.employee_no, name: manager.name, department: mapDepartmentSummary(manager.department), }; } function mapOrganizationNode(employee, departmentId, activeEmployeeById, childrenByManagerId, stack) { const nextStack = new Set(stack); nextStack.add(employee.id); const children = (childrenByManagerId.get(employee.id) ?? []) .slice() .sort(compareByEmployeeNo) .filter((item) => !nextStack.has(item.id)) .map((item) => mapOrganizationNode(item, departmentId, activeEmployeeById, childrenByManagerId, nextStack)); return { id: employee.id, employeeNo: employee.employee_no, name: employee.name, title: employee.title, status: employee.status, avatarUrl: buildAvatarUrl(employee.id, employee.avatar_path), department: mapDepartmentSummary(employee.department), externalManager: mapExternalManager(employee, departmentId, activeEmployeeById), children, }; } function buildDepartmentSection(department, employees, activeEmployeeById) { const departmentId = department.id; const departmentEmployeeById = new Map(employees.map((item) => [item.id, item])); const childrenByManagerId = new Map(); for (const employee of employees) { childrenByManagerId.set(employee.id, []); } for (const employee of employees) { if (!employee.direct_manager_id) { continue; } const manager = activeEmployeeById.get(employee.direct_manager_id); if (!manager || manager.department_id !== departmentId) { continue; } if (!departmentEmployeeById.has(manager.id)) { continue; } childrenByManagerId.get(manager.id).push(employee); } const roots = employees .filter((employee) => { if (!employee.direct_manager_id) { return true; } const manager = activeEmployeeById.get(employee.direct_manager_id); return !manager || manager.department_id !== departmentId; }) .slice() .sort(compareByEmployeeNo) .map((employee) => mapOrganizationNode(employee, departmentId, activeEmployeeById, childrenByManagerId, new Set())); return { department: mapDepartmentSummary(department), roots, }; } async function listOrganizationDepartments(prisma) { const employees = await prisma.employee.findMany({ where: { status: "active" }, select: { id: true, employee_no: true, name: true, title: true, status: true, avatar_path: true, department_id: true, direct_manager_id: true, department: { select: { id: true, name: true, }, }, }, }); const activeEmployeeById = new Map(employees.map((item) => [item.id, item])); const departmentGroups = new Map(); for (const employee of employees) { const departmentId = employee.department?.id ?? employee.department_id; if (!departmentId || !employee.department) { continue; } if (!departmentGroups.has(departmentId)) { departmentGroups.set(departmentId, { department: employee.department, employees: [], }); } departmentGroups.get(departmentId).employees.push(employee); } return [...departmentGroups.values()] .sort((left, right) => left.department.name.localeCompare(right.department.name, "zh-CN")) .map((group) => buildDepartmentSection(group.department, group.employees, activeEmployeeById)); } module.exports = { listOrganizationDepartments, __testables: { buildDepartmentSection, mapOrganizationNode, }, };