"use server" import { resolveRedFlag, type ContractSummary } from "./red-flag-resolution" import { revalidatePath } from "next/cache" import { debugLog, debugError, debugSuccess } from "@/lib/debug-utils" import { htmlListConverter, htmlTableConverter } from "@/lib/approval/template-utils" import db from "@/db/db" import { eq } from "drizzle-orm" import { redFlagManagers } from "@/db/schema/compliance" import { users } from "@/db/schema" /** * RED FLAG 해소 결재 승인 핸들러 * * 결재 승인 후 자동으로 호출되어 RED FLAG를 해제합니다. * * @param payload - 결재 상신 시 저장한 actionPayload */ export async function resolveRedFlagAfterApproval(payload: { contractIds: number[] requestedBy: number requestedAt: string }) { debugLog("[RedFlagResolutionHandler] RED FLAG 해소 결재 승인 핸들러 시작", payload) try { if (!payload?.contractIds || payload.contractIds.length === 0) { debugError("[RedFlagResolutionHandler] 계약서 ID가 없습니다", payload) return { success: false, message: "처리할 계약서가 없습니다.", } } const uniqueContractIds = Array.from(new Set(payload.contractIds)) debugLog("[RedFlagResolutionHandler] 처리할 계약서 수", { count: uniqueContractIds.length }) // 각 계약서에 대해 RED FLAG 해소 처리 // approvalId는 resolveRedFlag 내부에서 조회하므로 여기서는 전달하지 않음 const results = await Promise.allSettled( uniqueContractIds.map(async (contractId) => { const result = await resolveRedFlag(contractId, { revalidate: false, }) return { contractId, result } }) ) const successful = results.filter((r) => r.status === "fulfilled").length const failed = results.filter((r) => r.status === "rejected").length debugLog("[RedFlagResolutionHandler] 처리 결과", { total: uniqueContractIds.length, successful, failed, }) if (failed > 0) { const errors = results .filter((r) => r.status === "rejected") .map((r) => (r as PromiseRejectedResult).reason) debugError("[RedFlagResolutionHandler] 일부 계약서 처리 실패", errors) } await revalidatePath("/evcp/basic-contract") await revalidatePath("/evcp/compliance") debugSuccess("[RedFlagResolutionHandler] RED FLAG 해소 완료", { successful, failed, }) return { success: true, message: `${successful}개 계약서의 RED FLAG가 해제되었습니다.`, updated: successful, } } catch (error) { debugError("[RedFlagResolutionHandler] RED FLAG 해소 처리 중 오류 발생", error) throw error } } /** * 구매기획 담당자 EP ID 조회 */ export async function getPurchasingManagerEpId(): Promise { const [manager] = await db .select({ purchasingManagerId: redFlagManagers.purchasingManagerId, }) .from(redFlagManagers) .orderBy(redFlagManagers.createdAt) .limit(1) if (!manager?.purchasingManagerId) { return null } const [user] = await db .select({ epId: users.epId, }) .from(users) .where(eq(users.id, manager.purchasingManagerId)) .limit(1) return user?.epId ?? null } /** * RED FLAG 해소요청 데이터를 결재 템플릿 변수로 매핑 * * @param contracts - RED FLAG가 있는 계약서 목록 * @param meta - 요청자 정보 * @returns 템플릿 변수 객체 (Record) */ export async function mapRedFlagResolutionToTemplateVariables( contracts: ContractSummary[], meta: { requesterName: string; requestedAt: Date } ): Promise> { const summaryRows = contracts.map((contract) => ({ contractId: contract.contractId, vendorName: contract.vendorName ?? "-", templateName: contract.templateName ?? "-", redFlagCount: contract.triggeredFlags.length, })) const summaryTable = await htmlTableConverter(summaryRows, [ { key: "contractId", label: "계약 ID" }, { key: "vendorName", label: "업체명" }, { key: "templateName", label: "템플릿" }, { key: "redFlagCount", label: "RED FLAG 수" }, ]) const detailSections = await Promise.all( contracts.map(async (contract) => { const questionList = contract.triggeredFlags.map((flag, index) => { const prefix = flag.questionNumber || `${index + 1}` return `${prefix}. ${flag.questionText}` }) const listHtml = await htmlListConverter(questionList) return `
계약 ID: ${contract.contractId} / ${contract.vendorName ?? "-"}
${listHtml}
` }) ) const detailHtml = detailSections.join("") const formattedDate = new Intl.DateTimeFormat("ko-KR", { dateStyle: "medium", timeStyle: "short", }).format(meta.requestedAt) return { 요청자이름: meta.requesterName, 요청일시: formattedDate, 요청사유: "컴플라이언스 Red Flag 해소를 위해 구매기획 합의를 요청드립니다.", RedFlag요약테이블: summaryTable, RedFlag상세내역: detailHtml, } }