summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/users/department-domain/service.ts439
-rw-r--r--lib/users/knox-service.ts377
2 files changed, 816 insertions, 0 deletions
diff --git a/lib/users/department-domain/service.ts b/lib/users/department-domain/service.ts
new file mode 100644
index 00000000..570ef2cf
--- /dev/null
+++ b/lib/users/department-domain/service.ts
@@ -0,0 +1,439 @@
+"use server";
+
+import { revalidatePath, revalidateTag } from "next/cache";
+import { unstable_cache, unstable_noStore } from "next/cache";
+import db from "@/db/db";
+import {
+ departmentDomainAssignments,
+ departmentDomainAssignmentHistory
+} from "@/db/schema/departmentDomainAssignments";
+import { and, eq, inArray, desc } from "drizzle-orm";
+import { getServerSession } from "next-auth/next";
+import { authOptions } from "@/app/api/auth/[...nextauth]/route";
+import { getErrorMessage } from "@/lib/handle-error";
+import { getCurrentCompanyCode } from "@/lib/users/knox-service";
+
+// 도메인 타입
+export type UserDomain = "pending" | "evcp" | "procurement" | "sales" | "engineering" | "partners";
+
+// 부서별 도메인 할당 정보 조회
+export async function getDepartmentDomainAssignments() {
+ return unstable_cache(
+ async () => {
+ try {
+ const assignments = await db
+ .select({
+ id: departmentDomainAssignments.id,
+ companyCode: departmentDomainAssignments.companyCode,
+ departmentCode: departmentDomainAssignments.departmentCode,
+ departmentName: departmentDomainAssignments.departmentName,
+ assignedDomain: departmentDomainAssignments.assignedDomain,
+ isActive: departmentDomainAssignments.isActive,
+ description: departmentDomainAssignments.description,
+ createdAt: departmentDomainAssignments.createdAt,
+ updatedAt: departmentDomainAssignments.updatedAt,
+ })
+ .from(departmentDomainAssignments)
+ .where(eq(departmentDomainAssignments.isActive, true))
+ .orderBy(
+ desc(departmentDomainAssignments.updatedAt)
+ );
+
+ return assignments;
+ } catch (error) {
+ console.error("부서별 도메인 할당 정보 조회 실패:", error);
+ return [];
+ }
+ },
+ ["department-domain-assignments"],
+ {
+ revalidate: 3600, // 1시간 캐시
+ tags: ["department-domain-assignments"],
+ }
+ )();
+}
+
+// 특정 부서들의 도메인 할당 정보 조회
+export async function getDepartmentDomainAssignmentsByDepartments(departmentCodes: string[]) {
+ return unstable_cache(
+ async () => {
+ try {
+ if (departmentCodes.length === 0) return [];
+
+ const assignments = await db
+ .select()
+ .from(departmentDomainAssignments)
+ .where(
+ and(
+ inArray(departmentDomainAssignments.departmentCode, departmentCodes),
+ eq(departmentDomainAssignments.isActive, true)
+ )
+ );
+
+ return assignments;
+ } catch (error) {
+ console.error("부서별 도메인 할당 정보 조회 실패:", error);
+ return [];
+ }
+ },
+ [`department-assignments-${departmentCodes.sort().join(',')}`],
+ {
+ revalidate: 3600,
+ tags: ["department-domain-assignments"],
+ }
+ )();
+}
+
+// 부서별 도메인 할당
+export async function assignDomainToDepartments(params: {
+ departmentCodes: string[];
+ domain: UserDomain;
+ description?: string;
+ departmentNames?: Record<string, string>; // departmentCode -> departmentName 매핑
+}) {
+ unstable_noStore();
+
+ try {
+ // 세션 확인
+ const session = await getServerSession(authOptions);
+ if (!session?.user?.id) {
+ return {
+ success: false,
+ message: "인증이 필요합니다.",
+ };
+ }
+
+ const { departmentCodes, domain, description, departmentNames = {} } = params;
+ const userId = parseInt(session.user.id);
+
+ if (!departmentCodes.length || !domain) {
+ return {
+ success: false,
+ message: "부서 코드와 도메인이 필요합니다.",
+ };
+ }
+
+ // 현재 회사 코드 가져오기
+ const companyCode = await getCurrentCompanyCode();
+
+ await db.transaction(async (tx) => {
+ // 기존 할당 정보를 비활성화 (soft delete)
+ const existingAssignments = await tx
+ .select()
+ .from(departmentDomainAssignments)
+ .where(
+ and(
+ inArray(departmentDomainAssignments.departmentCode, departmentCodes),
+ eq(departmentDomainAssignments.isActive, true)
+ )
+ );
+
+ // 기존 할당이 있으면 비활성화하고 히스토리 기록
+ for (const existing of existingAssignments) {
+ // 히스토리 기록
+ await tx.insert(departmentDomainAssignmentHistory).values({
+ assignmentId: existing.id,
+ action: "deactivated",
+ previousValues: JSON.stringify({
+ assignedDomain: existing.assignedDomain,
+ isActive: true,
+ description: existing.description,
+ }),
+ newValues: JSON.stringify({
+ isActive: false,
+ }),
+ changedBy: userId,
+ changeReason: `새로운 도메인 할당으로 인한 기존 할당 비활성화: ${domain}`,
+ });
+
+ // 기존 할당 비활성화
+ await tx
+ .update(departmentDomainAssignments)
+ .set({
+ isActive: false,
+ updatedBy: userId,
+ updatedAt: new Date(),
+ })
+ .where(eq(departmentDomainAssignments.id, existing.id));
+ }
+
+ // 새로운 할당 생성
+ const newAssignments = departmentCodes.map(departmentCode => {
+ return {
+ companyCode,
+ departmentCode,
+ departmentName: departmentNames[departmentCode] || departmentCode,
+ assignedDomain: domain,
+ description,
+ isActive: true,
+ createdBy: userId,
+ updatedBy: userId,
+ };
+ });
+
+ const insertedAssignments = await tx
+ .insert(departmentDomainAssignments)
+ .values(newAssignments)
+ .returning();
+
+ // 신규 생성 히스토리 기록
+ for (let i = 0; i < insertedAssignments.length; i++) {
+ const assignment = insertedAssignments[i];
+ await tx.insert(departmentDomainAssignmentHistory).values({
+ assignmentId: assignment.id,
+ action: "created",
+ newValues: JSON.stringify({
+ companyCode: assignment.companyCode,
+ departmentCode: assignment.departmentCode,
+ assignedDomain: assignment.assignedDomain,
+ description: assignment.description,
+ }),
+ changedBy: userId,
+ changeReason: description || "부서별 도메인 할당",
+ });
+ }
+ });
+
+ // 캐시 무효화
+ revalidateTag("department-domain-assignments");
+ revalidatePath("/evcp/menu-access-dept");
+
+ return {
+ success: true,
+ message: `${departmentCodes.length}개 부서에 ${domain} 도메인이 성공적으로 할당되었습니다.`,
+ };
+
+ } catch (error) {
+ console.error("부서별 도메인 할당 실패:", error);
+ return {
+ success: false,
+ message: getErrorMessage(error),
+ };
+ }
+}
+
+// 부서별 도메인 할당 수정
+export async function updateDepartmentDomainAssignment(params: {
+ assignmentId: number;
+ domain: UserDomain;
+ description?: string;
+ isActive?: boolean;
+}) {
+ unstable_noStore();
+
+ try {
+ const session = await getServerSession(authOptions);
+ if (!session?.user?.id) {
+ return {
+ success: false,
+ message: "인증이 필요합니다.",
+ };
+ }
+
+ const { assignmentId, domain, description, isActive = true } = params;
+ const userId = parseInt(session.user.id);
+
+ await db.transaction(async (tx) => {
+ // 기존 할당 정보 조회
+ const existing = await tx
+ .select()
+ .from(departmentDomainAssignments)
+ .where(eq(departmentDomainAssignments.id, assignmentId))
+ .limit(1);
+
+ if (existing.length === 0) {
+ throw new Error("존재하지 않는 할당 정보입니다.");
+ }
+
+ const currentAssignment = existing[0];
+
+ // 히스토리 기록
+ await tx.insert(departmentDomainAssignmentHistory).values({
+ assignmentId,
+ action: "updated",
+ previousValues: JSON.stringify({
+ assignedDomain: currentAssignment.assignedDomain,
+ description: currentAssignment.description,
+ isActive: currentAssignment.isActive,
+ }),
+ newValues: JSON.stringify({
+ assignedDomain: domain,
+ description,
+ isActive,
+ }),
+ changedBy: userId,
+ changeReason: description || "부서별 도메인 할당 수정",
+ });
+
+ // 할당 정보 업데이트
+ await tx
+ .update(departmentDomainAssignments)
+ .set({
+ assignedDomain: domain,
+ description,
+ isActive,
+ updatedBy: userId,
+ updatedAt: new Date(),
+ })
+ .where(eq(departmentDomainAssignments.id, assignmentId));
+ });
+
+ // 캐시 무효화
+ revalidateTag("department-domain-assignments");
+ revalidatePath("/evcp/menu-access-dept");
+
+ return {
+ success: true,
+ message: "도메인 할당 정보가 성공적으로 수정되었습니다.",
+ };
+
+ } catch (error) {
+ console.error("부서별 도메인 할당 수정 실패:", error);
+ return {
+ success: false,
+ message: getErrorMessage(error),
+ };
+ }
+}
+
+// 부서별 도메인 할당 삭제 (soft delete)
+export async function deleteDepartmentDomainAssignment(assignmentId: number) {
+ unstable_noStore();
+
+ try {
+ const session = await getServerSession(authOptions);
+ if (!session?.user?.id) {
+ return {
+ success: false,
+ message: "인증이 필요합니다.",
+ };
+ }
+
+ const userId = parseInt(session.user.id);
+
+ await db.transaction(async (tx) => {
+ // 기존 할당 정보 조회
+ const existing = await tx
+ .select()
+ .from(departmentDomainAssignments)
+ .where(eq(departmentDomainAssignments.id, assignmentId))
+ .limit(1);
+
+ if (existing.length === 0) {
+ throw new Error("존재하지 않는 할당 정보입니다.");
+ }
+
+ const currentAssignment = existing[0];
+
+ // 히스토리 기록
+ await tx.insert(departmentDomainAssignmentHistory).values({
+ assignmentId,
+ action: "deleted",
+ previousValues: JSON.stringify({
+ assignedDomain: currentAssignment.assignedDomain,
+ description: currentAssignment.description,
+ isActive: currentAssignment.isActive,
+ }),
+ newValues: JSON.stringify({
+ isActive: false,
+ }),
+ changedBy: userId,
+ changeReason: "부서별 도메인 할당 삭제",
+ });
+
+ // 할당 정보 비활성화
+ await tx
+ .update(departmentDomainAssignments)
+ .set({
+ isActive: false,
+ updatedBy: userId,
+ updatedAt: new Date(),
+ })
+ .where(eq(departmentDomainAssignments.id, assignmentId));
+ });
+
+ // 캐시 무효화
+ revalidateTag("department-domain-assignments");
+ revalidatePath("/evcp/menu-access-dept");
+
+ return {
+ success: true,
+ message: "도메인 할당 정보가 성공적으로 삭제되었습니다.",
+ };
+
+ } catch (error) {
+ console.error("부서별 도메인 할당 삭제 실패:", error);
+ return {
+ success: false,
+ message: getErrorMessage(error),
+ };
+ }
+}
+
+// 부서별 도메인 할당 히스토리 조회
+export async function getDepartmentDomainAssignmentHistory(assignmentId?: number) {
+ return unstable_cache(
+ async () => {
+ try {
+ const query = db
+ .select({
+ id: departmentDomainAssignmentHistory.id,
+ assignmentId: departmentDomainAssignmentHistory.assignmentId,
+ action: departmentDomainAssignmentHistory.action,
+ previousValues: departmentDomainAssignmentHistory.previousValues,
+ newValues: departmentDomainAssignmentHistory.newValues,
+ changedBy: departmentDomainAssignmentHistory.changedBy,
+ changeReason: departmentDomainAssignmentHistory.changeReason,
+ createdAt: departmentDomainAssignmentHistory.createdAt,
+ })
+ .from(departmentDomainAssignmentHistory);
+
+ if (assignmentId) {
+ query.where(eq(departmentDomainAssignmentHistory.assignmentId, assignmentId));
+ }
+
+ const history = await query
+ .orderBy(desc(departmentDomainAssignmentHistory.createdAt))
+ .limit(100); // 최근 100개 제한
+
+ return history;
+ } catch (error) {
+ console.error("부서별 도메인 할당 히스토리 조회 실패:", error);
+ return [];
+ }
+ },
+ [`department-domain-assignment-history-${assignmentId || 'all'}`],
+ {
+ revalidate: 1800, // 30분 캐시
+ tags: ["department-domain-assignment-history"],
+ }
+ )();
+}
+
+// 도메인별 통계 조회
+export async function getDepartmentDomainStats() {
+ return unstable_cache(
+ async () => {
+ try {
+ const stats = await db
+ .select({
+ domain: departmentDomainAssignments.assignedDomain,
+ count: db.$count(departmentDomainAssignments),
+ })
+ .from(departmentDomainAssignments)
+ .where(eq(departmentDomainAssignments.isActive, true))
+ .groupBy(departmentDomainAssignments.assignedDomain);
+
+ return stats;
+ } catch (error) {
+ console.error("도메인별 통계 조회 실패:", error);
+ return [];
+ }
+ },
+ ["department-domain-stats"],
+ {
+ revalidate: 3600,
+ tags: ["department-domain-assignments"],
+ }
+ )();
+} \ No newline at end of file
diff --git a/lib/users/knox-service.ts b/lib/users/knox-service.ts
new file mode 100644
index 00000000..d5453072
--- /dev/null
+++ b/lib/users/knox-service.ts
@@ -0,0 +1,377 @@
+"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<DepartmentNode[]> {
+ 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<DepartmentNode[]> {
+ 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<Record<string, DepartmentNode[]>> {
+ return unstable_cache(
+ async () => {
+ try {
+ const organizations = await getVisibleOrganizations();
+
+ // 회사별로 그룹화
+ const companiesMap = new Map<string, typeof organizations>();
+
+ organizations.forEach((org) => {
+ if (!companiesMap.has(org.companyCode)) {
+ companiesMap.set(org.companyCode, []);
+ }
+ companiesMap.get(org.companyCode)!.push(org);
+ });
+
+ // 각 회사별로 트리 구조 생성
+ const result: Record<string, DepartmentNode[]> = {};
+
+ 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<string, DepartmentNode>();
+ 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<string[]> {
+ 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<Array<{ code: string; name: string }>> {
+ 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<string> {
+ return DEFAULT_COMPANY_CODE;
+}
+
+// 현재 사용 중인 회사 정보 반환
+export async function getCurrentCompanyInfo(): Promise<{ code: string; name: string }> {
+ return {
+ code: DEFAULT_COMPANY_CODE,
+ name: "삼성중공업"
+ };
+}