"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; // 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"], } )(); }