diff options
| author | joonhoekim <26rote@gmail.com> | 2025-07-23 06:06:27 +0000 |
|---|---|---|
| committer | joonhoekim <26rote@gmail.com> | 2025-07-23 06:06:27 +0000 |
| commit | f9bfc82880212e1a13f6bbb28ecfc87b89346f26 (patch) | |
| tree | ee792f340ebfa7eaf30d2e79f99f41213e5c5cf3 /lib/users/department-domain/service.ts | |
| parent | edc0eabc8f5fc44408c28023ca155bd73ddf8183 (diff) | |
(김준회) 메뉴접근제어(부서별) 메뉴 구현
Diffstat (limited to 'lib/users/department-domain/service.ts')
| -rw-r--r-- | lib/users/department-domain/service.ts | 439 |
1 files changed, 439 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 |
