"use server"; import db from "@/db/db"; import { eq, and, desc, isNull, isNotNull, ne, or } from "drizzle-orm"; import { gtcDocuments, gtcVendorDocuments, gtcVendorClauses, gtcNegotiationHistory, projects, BasicContractView } from "@/db/schema"; /** * 템플릿 이름에서 프로젝트 코드 추출 */ function extractProjectCodeFromTemplateName(templateName: string): string | null { // "SN2319 GTC" -> "SN2319" // "General GTC" -> null const words = templateName.trim().split(/\s+/); if (words.length === 0) return null; const firstWord = words[0]; if (firstWord.toLowerCase() === 'general' || firstWord.toLowerCase() === 'gtc') { return null; } // 프로젝트 코드는 보통 첫 번째 단어 if (words.length > 1 && words[words.length - 1].toLowerCase() === 'gtc') { return words[words.length - 1]; } return null; } /** * 단일 contract에 대한 GTC 정보 확인 */ async function checkGTCCommentsForContract( templateName: string, vendorId: number, basicContractId?: number ): Promise<{ gtcDocumentId: number | null; hasComments: boolean }> { try { const projectCode = extractProjectCodeFromTemplateName(templateName); let gtcDocumentId: number | null = null; console.log(projectCode,"projectCode") // 1. GTC Document ID 찾기 if (projectCode && projectCode.trim() !== '') { // Project GTC인 경우 const project = await db .select({ id: projects.id }) .from(projects) .where(eq(projects.code, projectCode.trim())) .limit(1) if (project.length > 0) { const projectGtcDoc = await db .select({ id: gtcDocuments.id }) .from(gtcDocuments) .where( and( eq(gtcDocuments.projectId, project[0].id), eq(gtcDocuments.isActive, true) ) ) .orderBy(desc(gtcDocuments.revision)) .limit(1) if (projectGtcDoc.length > 0) { gtcDocumentId = projectGtcDoc[0].id } } } else { // Standard GTC인 경우 (general 포함하거나 project code가 없는 경우) console.log(`🔍 [checkGTCCommentsForContract] Standard GTC 조회 중...`); const standardGtcDoc = await db .select({ id: gtcDocuments.id }) .from(gtcDocuments) .where( and( eq(gtcDocuments.type, "standard"), eq(gtcDocuments.isActive, true), isNull(gtcDocuments.projectId) ) ) .orderBy(desc(gtcDocuments.revision)) .limit(1) console.log(`📊 [checkGTCCommentsForContract] Standard GTC 조회 결과: ${standardGtcDoc.length}개`); if (standardGtcDoc.length > 0) { gtcDocumentId = standardGtcDoc[0].id console.log(`✅ [checkGTCCommentsForContract] Standard GTC 찾음: ${gtcDocumentId}`); } else { console.log(`❌ [checkGTCCommentsForContract] Standard GTC 없음 - gtc_documents 테이블에 standard 타입의 활성 문서가 없습니다`); } } console.log(`🎯 [checkGTCCommentsForContract] 최종 gtcDocumentId: ${gtcDocumentId}`) // 🔥 중요: basicContractId가 있으면 먼저 agreement_comments 테이블 확인 // gtcDocumentId가 없어도 새로운 코멘트 시스템은 작동해야 함 if (basicContractId) { console.log(`🔍 [checkGTCCommentsForContract] basicContractId: ${basicContractId} 로 코멘트 조회`); const { agreementComments } = await import("@/db/schema"); const newComments = await db .select({ id: agreementComments.id }) .from(agreementComments) .where( and( eq(agreementComments.basicContractId, basicContractId), eq(agreementComments.isDeleted, false) ) ) .limit(1); console.log(`📊 [checkGTCCommentsForContract] basicContractId ${basicContractId}: 코멘트 ${newComments.length}개 발견`); if (newComments.length > 0) { console.log(`✅ [checkGTCCommentsForContract] basicContractId ${basicContractId}: hasComments = true 반환`); return { gtcDocumentId, // null일 수 있음 hasComments: true }; } console.log(`⚠️ [checkGTCCommentsForContract] basicContractId ${basicContractId}: agreementComments 없음`); } // GTC Document를 찾지 못한 경우 (기존 방식도 체크할 수 없음) if (!gtcDocumentId) { console.log(`⚠️ [checkGTCCommentsForContract] gtcDocumentId null - 기존 방식 체크 불가`); return { gtcDocumentId: null, hasComments: false }; } // 2-2. 기존 방식: gtcDocumentId로 해당 벤더의 vendor documents 찾기 const vendorDocuments = await db .select({ id: gtcVendorDocuments.id }) .from(gtcVendorDocuments) .where( and( eq(gtcVendorDocuments.baseDocumentId, gtcDocumentId), eq(gtcVendorDocuments.vendorId, vendorId), eq(gtcVendorDocuments.isActive, true) ) ) .limit(1) if (vendorDocuments.length === 0) { return { gtcDocumentId, hasComments: false }; } // vendor document에 연결된 clauses에서 negotiation history 확인 const commentsExist = await db .select({ count: gtcNegotiationHistory.id }) .from(gtcNegotiationHistory) .innerJoin( gtcVendorClauses, eq(gtcNegotiationHistory.vendorClauseId, gtcVendorClauses.id) ) .where( and( eq(gtcVendorClauses.vendorDocumentId, vendorDocuments[0].id), eq(gtcVendorClauses.isActive, true), isNotNull(gtcNegotiationHistory.comment), ne(gtcNegotiationHistory.comment, '') ) ) .limit(1) return { gtcDocumentId, hasComments: commentsExist.length > 0 }; } catch (error) { console.error('Error checking GTC comments for contract:', error); return { gtcDocumentId: null, hasComments: false }; } } /** * 전체 contract 리스트에 대해 GTC document ID와 comment 정보 수집 */ export async function checkGTCCommentsForContracts( contracts: BasicContractView[] ): Promise> { const gtcData: Record = {}; // GTC가 포함된 contract만 필터링 const gtcContracts = contracts.filter(contract => contract.templateName?.includes('GTC') ); if (gtcContracts.length === 0) { return gtcData; } // Promise.all을 사용해서 병렬 처리 const checkPromises = gtcContracts.map(async (contract) => { try { console.log(`🔄 [checkGTCCommentsForContracts] 계약서 ${contract.id} 체크 시작 - 템플릿: ${contract.templateName}, 벤더: ${contract.vendorName} (${contract.vendorId})`); const result = await checkGTCCommentsForContract( contract.templateName!, contract.vendorId!, contract.id // basicContractId 전달 ); console.log(`📌 [checkGTCCommentsForContracts] 계약서 ${contract.id} 결과 - hasComments: ${result.hasComments}, gtcDocumentId: ${result.gtcDocumentId}`); return { contractId: contract.id, gtcDocumentId: result.gtcDocumentId, hasComments: result.hasComments }; } catch (error) { console.error(`Error checking GTC for contract ${contract.id}:`, error); return { contractId: contract.id, gtcDocumentId: null, hasComments: false }; } }); const results = await Promise.all(checkPromises); // 결과를 Record 형태로 변환 results.forEach(({ contractId, gtcDocumentId, hasComments }) => { gtcData[contractId] = { gtcDocumentId, hasComments }; }); return gtcData; }