"use server"; import { unstable_cache } from "next/cache"; import db from "@/db/db"; import { organization } from "@/db/schema/knox/organization"; import { eq, and, asc } from "drizzle-orm"; // 조직 트리 노드 타입 export interface DepartmentNode { companyCode: string; departmentCode: string; departmentName: string; departmentLevel?: string; uprDepartmentCode?: string; lowDepartmentYn?: string; hiddenDepartmentYn?: string; children: DepartmentNode[]; // UI에서 사용할 추가 필드 label: string; value: string; key: string; } // 기본 회사 코드 (환경변수에서 가져오되 폴백 제공) const getCompanyCode = () => { const envCodes = process.env.KNOX_COMPANY_CODES; if (envCodes) { // 쉼표로 구분된 경우 첫 번째 값 사용 return envCodes.split(',')[0].trim(); } return "D60"; // 폴백 값 }; const DEFAULT_COMPANY_CODE = getCompanyCode(); // 조직 데이터 조회 (hiddenDepartmentYn = 'F'만) export async function getVisibleOrganizations() { return unstable_cache( async () => { try { const organizations = await db .select({ companyCode: organization.companyCode, departmentCode: organization.departmentCode, departmentName: organization.departmentName, departmentLevel: organization.departmentLevel, uprDepartmentCode: organization.uprDepartmentCode, lowDepartmentYn: organization.lowDepartmentYn, hiddenDepartmentYn: organization.hiddenDepartmentYn, }) .from(organization) .where(eq(organization.hiddenDepartmentYn, 'F')) .orderBy( asc(organization.companyCode), asc(organization.departmentLevel), asc(organization.departmentCode) ); return organizations; } catch (error) { console.error("조직 데이터 조회 실패:", error); return []; } }, ["visible-organizations"], { revalidate: 3600, // 1시간 캐시 tags: ["knox-organizations"], } )(); } // 기본 회사의 부서 트리 구조 조회 (처음부터 모든 데이터 로드) export async function getAllDepartmentsTree(): Promise { return unstable_cache( async () => { try { const organizations = await db .select({ companyCode: organization.companyCode, departmentCode: organization.departmentCode, departmentName: organization.departmentName, departmentLevel: organization.departmentLevel, uprDepartmentCode: organization.uprDepartmentCode, lowDepartmentYn: organization.lowDepartmentYn, hiddenDepartmentYn: organization.hiddenDepartmentYn, }) .from(organization) .where( and( eq(organization.companyCode, DEFAULT_COMPANY_CODE), eq(organization.hiddenDepartmentYn, 'F') ) ) .orderBy( asc(organization.departmentLevel), asc(organization.departmentCode) ); // 트리 구조 생성 const tree = buildDepartmentTree(organizations); return tree; } catch (error) { console.error("모든 부서 트리 구성 실패:", error); return []; } }, [`all-departments-tree-${DEFAULT_COMPANY_CODE}`], { revalidate: 3600, tags: ["knox-organizations"], } )(); } // 회사별 조직 트리 구조 생성 (기존 호환성 유지) export async function getDepartmentTreeByCompany(companyCode: string): Promise { return unstable_cache( async () => { try { const organizations = await db .select({ companyCode: organization.companyCode, departmentCode: organization.departmentCode, departmentName: organization.departmentName, departmentLevel: organization.departmentLevel, uprDepartmentCode: organization.uprDepartmentCode, lowDepartmentYn: organization.lowDepartmentYn, hiddenDepartmentYn: organization.hiddenDepartmentYn, }) .from(organization) .where( and( eq(organization.companyCode, companyCode), eq(organization.hiddenDepartmentYn, 'F') ) ) .orderBy( asc(organization.departmentLevel), asc(organization.departmentCode) ); // 트리 구조 생성 const tree = buildDepartmentTree(organizations); return tree; } catch (error) { console.error(`회사 ${companyCode} 조직 트리 구성 실패:`, error); return []; } }, [`department-tree-${companyCode}`], { revalidate: 3600, tags: ["knox-organizations", `company-${companyCode}`], } )(); } // 전체 회사의 조직 트리 구조 생성 export async function getAllDepartmentTrees(): Promise> { return unstable_cache( async () => { try { const organizations = await getVisibleOrganizations(); // 회사별로 그룹화 const companiesMap = new Map(); organizations.forEach((org) => { if (!companiesMap.has(org.companyCode)) { companiesMap.set(org.companyCode, []); } companiesMap.get(org.companyCode)!.push(org); }); // 각 회사별로 트리 구조 생성 const result: Record = {}; for (const [companyCode, orgs] of companiesMap) { result[companyCode] = buildDepartmentTree(orgs); } return result; } catch (error) { console.error("전체 조직 트리 구성 실패:", error); return {}; } }, ["all-department-trees"], { revalidate: 3600, tags: ["knox-organizations"], } )(); } // 부서 트리 구조 빌더 헬퍼 함수 (개선) function buildDepartmentTree( organizations: Array<{ companyCode: string; departmentCode: string; departmentName: string | null; departmentLevel?: string | null; uprDepartmentCode?: string | null; lowDepartmentYn?: string | null; hiddenDepartmentYn?: string | null; }> ): DepartmentNode[] { // 맵으로 빠른 조회를 위한 인덱스 생성 const orgMap = new Map(); const rootNodes: DepartmentNode[] = []; // 1단계: 모든 노드를 맵에 추가 organizations.forEach((org) => { const node: DepartmentNode = { companyCode: org.companyCode, departmentCode: org.departmentCode, departmentName: org.departmentName || "", departmentLevel: org.departmentLevel || undefined, uprDepartmentCode: org.uprDepartmentCode || undefined, lowDepartmentYn: org.lowDepartmentYn || undefined, hiddenDepartmentYn: org.hiddenDepartmentYn || undefined, children: [], // UI용 필드 label: org.departmentName || org.departmentCode, value: org.departmentCode, key: `${org.companyCode}-${org.departmentCode}`, }; orgMap.set(org.departmentCode, node); }); // 2단계: 부모-자식 관계 설정 organizations.forEach((org) => { const currentNode = orgMap.get(org.departmentCode); if (!currentNode) return; if (org.uprDepartmentCode && orgMap.has(org.uprDepartmentCode)) { // 부모가 있으면 부모의 children에 추가 const parentNode = orgMap.get(org.uprDepartmentCode); parentNode!.children.push(currentNode); } else { // 부모가 없으면 루트 노드로 처리 // 하지만 상위 부서가 없는 부서들은 depth 1에 배치 rootNodes.push(currentNode); } }); // 3단계: 고립된 부서들 처리 (상위도 하위도 없는 부서들) // lowDepartmentYn이 'F'이거나 null이고, uprDepartmentCode가 없거나 존재하지 않는 부서들을 확인 organizations.forEach((org) => { const currentNode = orgMap.get(org.departmentCode); if (!currentNode) return; // 이미 루트에 추가되었거나 다른 부서의 자식이 된 경우는 스킵 const isAlreadyPlaced = rootNodes.includes(currentNode) || organizations.some(otherOrg => { const otherNode = orgMap.get(otherOrg.departmentCode); return otherNode && otherNode.children.includes(currentNode); }); if (!isAlreadyPlaced) { // 고립된 부서를 루트에 추가 rootNodes.push(currentNode); } }); // 4단계: 각 노드의 children을 정렬 const sortChildren = (node: DepartmentNode) => { node.children.sort((a, b) => { // departmentLevel이 있으면 그걸로 정렬, 없으면 departmentCode로 정렬 const aLevel = parseInt(a.departmentLevel || "999"); const bLevel = parseInt(b.departmentLevel || "999"); if (aLevel !== bLevel) { return aLevel - bLevel; } return a.departmentCode.localeCompare(b.departmentCode); }); // 재귀적으로 자식들도 정렬 node.children.forEach(sortChildren); }; // 5단계: 루트 노드들도 정렬 rootNodes.sort((a, b) => { const aLevel = parseInt(a.departmentLevel || "1"); const bLevel = parseInt(b.departmentLevel || "1"); if (aLevel !== bLevel) { return aLevel - bLevel; } return a.departmentCode.localeCompare(b.departmentCode); }); rootNodes.forEach(sortChildren); return rootNodes; } // 특정 부서의 모든 하위 부서 코드 조회 (재귀) - 기본 회사 대상 export async function getChildDepartmentCodes(departmentCode: string): Promise { const tree = await getAllDepartmentsTree(); const result: string[] = []; const findAndCollectChildren = (nodes: DepartmentNode[], targetCode: string): boolean => { for (const node of nodes) { if (node.departmentCode === targetCode) { // 타겟 노드 발견, 모든 하위 부서 코드 수집 collectAllDepartmentCodes(node, result); return true; } // 자식 노드들에서 재귀 검색 if (findAndCollectChildren(node.children, targetCode)) { return true; } } return false; }; const collectAllDepartmentCodes = (node: DepartmentNode, codes: string[]) => { codes.push(node.departmentCode); node.children.forEach(child => collectAllDepartmentCodes(child, codes)); }; findAndCollectChildren(tree, departmentCode); return result; } // 회사 목록 조회 (호환성 유지용) export async function getCompanies(): Promise> { return unstable_cache( async () => { try { const companies = await db .selectDistinct({ code: organization.companyCode, name: organization.companyName, }) .from(organization) .where(eq(organization.hiddenDepartmentYn, 'F')) .orderBy(asc(organization.companyCode)); return companies .filter(company => company.code && company.name) .map(company => ({ code: company.code, name: company.name!, })); } catch (error) { console.error("회사 목록 조회 실패:", error); return []; } }, ["companies"], { revalidate: 3600, tags: ["knox-organizations"], } )(); } // 현재 사용 중인 회사 코드 반환 export async function getCurrentCompanyCode(): Promise { return DEFAULT_COMPANY_CODE; } // 현재 사용 중인 회사 정보 반환 export async function getCurrentCompanyInfo(): Promise<{ code: string; name: string }> { return { code: DEFAULT_COMPANY_CODE, name: "삼성중공업" }; }