diff options
| author | dujinkim <dujin.kim@dtsolution.co.kr> | 2025-11-19 06:15:43 +0000 |
|---|---|---|
| committer | dujinkim <dujin.kim@dtsolution.co.kr> | 2025-11-19 06:15:43 +0000 |
| commit | c92bd1b8caa6ddabe6acee42018262febd5d91fb (patch) | |
| tree | 833a62c9577894b0f77d3677d4d0274e1cb99385 /lib/basic-contract/sslvw-service.ts | |
| parent | 9bf5b15734cdf87a02c68b2d2a25046a0678a037 (diff) | |
(임수민) 기본계약 코멘트, 법무검토 수정
Diffstat (limited to 'lib/basic-contract/sslvw-service.ts')
| -rw-r--r-- | lib/basic-contract/sslvw-service.ts | 191 |
1 files changed, 189 insertions, 2 deletions
diff --git a/lib/basic-contract/sslvw-service.ts b/lib/basic-contract/sslvw-service.ts index 9650d43a..5a35fb99 100644 --- a/lib/basic-contract/sslvw-service.ts +++ b/lib/basic-contract/sslvw-service.ts @@ -1,6 +1,11 @@ "use server" import { oracleKnex } from '@/lib/oracle-db/db' +import db from '@/db/db' +import { basicContract, agreementComments } from '@/db/schema' +import { eq, inArray, and } from 'drizzle-orm' +import { revalidateTag } from 'next/cache' +import { sendEmail } from '@/lib/mail/sendEmail' // SSLVW_PUR_INQ_REQ 테이블 데이터 타입 (실제 테이블 구조에 맞게 조정 필요) export interface SSLVWPurInqReq { @@ -39,10 +44,28 @@ export async function getSSLVWPurInqReqData(): Promise<{ console.log('📋 [getSSLVWPurInqReqData] SSLVW_PUR_INQ_REQ 테이블 조회 시작...') const result = await oracleKnex.raw(` - SELECT * + SELECT + ID, + PRGS_STAT_DSC, + REQ_DT, + REQ_NO, + REQ_TIT, + REQ_CONT, + VEND_CD, + VEND_NM, + CNTR_CTGR_DSC, + CNTR_AMT, + CNTR_STRT_DT, + CNTR_END_DT, + RPLY_DT, + RPLY_CONT, + RPLY_USER_NM, + RPLY_USER_ID, + CREATED_AT, + UPDATED_AT FROM SSLVW_PUR_INQ_REQ WHERE ROWNUM < 100 - ORDER BY 1 + ORDER BY REQ_DT DESC `) // Oracle raw query의 결과는 rows 배열에 들어있음 @@ -80,3 +103,167 @@ export async function getSSLVWPurInqReqData(): Promise<{ } } } + +/** + * 법무검토 요청 + * @param contractIds 계약서 ID 배열 + * @returns 성공 여부 및 메시지 + */ +export async function requestLegalReview(contractIds: number[]): Promise<{ + success: boolean + message: string + requested: number + skipped: number + errors: string[] +}> { + console.log(`📋 [requestLegalReview] 법무검토 요청 시작: ${contractIds.length}건`) + + if (!contractIds || contractIds.length === 0) { + return { + success: false, + message: '선택된 계약서가 없습니다.', + requested: 0, + skipped: 0, + errors: [] + } + } + + try { + // 계약서 정보 조회 + const contracts = await db + .select() + .from(basicContract) + .where(inArray(basicContract.id, contractIds)) + + if (!contracts || contracts.length === 0) { + return { + success: false, + message: '계약서를 찾을 수 없습니다.', + requested: 0, + skipped: 0, + errors: [] + } + } + + let requestedCount = 0 + let skippedCount = 0 + const errors: string[] = [] + + for (const contract of contracts) { + try { + // 유효성 검사 + if (contract.legalReviewRequestedAt) { + console.log(`⚠️ [requestLegalReview] 계약서 ${contract.id}: 이미 법무검토 요청됨`) + skippedCount++ + errors.push(`${contract.id}: 이미 법무검토 요청됨`) + continue + } + + // 협의 완료 여부 확인 + // 1. 협의 완료됨 (negotiationCompletedAt 있음) → 가능 + // 2. 협의 없음 (코멘트 없음) → 가능 + // 3. 협의 중 (negotiationCompletedAt 없고 코멘트 있음) → 불가 + + if (!contract.negotiationCompletedAt) { + // 협의 완료되지 않은 경우, 코멘트 존재 여부 확인 + // 삭제되지 않은 코멘트가 있으면 협의 중이므로 불가 + const comments = await db + .select() + .from(agreementComments) + .where( + and( + eq(agreementComments.basicContractId, contract.id), + eq(agreementComments.isDeleted, false) + ) + ) + .limit(1); + + // 삭제되지 않은 코멘트가 있으면 협의 중이므로 불가 + if (comments.length > 0) { + console.log(`⚠️ [requestLegalReview] 계약서 ${contract.id}: 협의 진행 중`) + skippedCount++ + errors.push(`${contract.id}: 협의가 진행 중입니다`) + continue + } + + // 코멘트가 없으면 협의 없음으로 간주하고 가능 + console.log(`ℹ️ [requestLegalReview] 계약서 ${contract.id}: 협의 없음, 법무검토 요청 가능`) + } + + // 법무검토 요청 상태로 업데이트 + await db + .update(basicContract) + .set({ + legalReviewRequestedAt: new Date(), + updatedAt: new Date(), + } as any) + .where(eq(basicContract.id, contract.id)) + + requestedCount++ + console.log(`✅ [requestLegalReview] 계약서 ${contract.id}: 법무검토 요청 완료`) + + // 법무팀에 이메일 알림 발송 (선택사항) + try { + // TODO: 법무팀 이메일 주소를 설정에서 가져오기 + const legalTeamEmail = process.env.LEGAL_TEAM_EMAIL || 'legal@example.com' + + await sendEmail({ + to: legalTeamEmail, + subject: `[eVCP] 기본계약서 법무검토 요청 - ${contract.id}`, + template: 'legal-review-request', + context: { + language: 'ko', + contractId: contract.id, + vendorName: contract.vendorId || '업체명 없음', + contractUrl: `${process.env.NEXT_PUBLIC_APP_URL}/evcp/basic-contract/${contract.id}`, + systemUrl: process.env.NEXT_PUBLIC_APP_URL || 'https://evcp.com', + currentYear: new Date().getFullYear(), + }, + }) + } catch (emailError) { + console.error(`⚠️ [requestLegalReview] 이메일 발송 실패 (계약서 ${contract.id}):`, emailError) + // 이메일 실패는 법무검토 요청 성공에 영향을 주지 않음 + } + + } catch (error) { + console.error(`❌ [requestLegalReview] 계약서 ${contract.id} 처리 실패:`, error) + errors.push(`${contract.id}: 처리 중 오류 발생`) + skippedCount++ + } + } + + // 캐시 무효화 + revalidateTag('basic-contracts') + + const totalProcessed = requestedCount + skippedCount + let message = '' + + if (requestedCount === contracts.length) { + message = `${requestedCount}건의 계약서에 대한 법무검토가 요청되었습니다.` + } else if (requestedCount > 0) { + message = `${requestedCount}건 요청 완료, ${skippedCount}건 건너뜀` + } else { + message = `모든 계약서를 건너뛰었습니다. (${skippedCount}건)` + } + + console.log(`✅ [requestLegalReview] 법무검토 요청 완료: ${message}`) + + return { + success: requestedCount > 0, + message, + requested: requestedCount, + skipped: skippedCount, + errors + } + + } catch (error) { + console.error('❌ [requestLegalReview] 법무검토 요청 실패:', error) + return { + success: false, + message: '법무검토 요청 중 오류가 발생했습니다.', + requested: 0, + skipped: contractIds.length, + errors: [error instanceof Error ? error.message : '알 수 없는 오류'] + } + } +} |
