From 6160e8bd61360ada9e8e0574671c38292eaba9e7 Mon Sep 17 00:00:00 2001 From: dujinkim Date: Tue, 25 Nov 2025 11:48:21 +0000 Subject: (임수민) 준법/gtc 코멘트 수정 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- db/schema/basicContractDocumnet.ts | 3 +- lib/basic-contract/actions/check-gtc-comments.ts | 62 ++++----------------- lib/basic-contract/agreement-comments/actions.ts | 6 ++- .../basic-contracts-detail-columns.tsx | 46 ++++++---------- .../status-detail/basic-contracts-detail-table.tsx | 35 +++++++++--- .../vendor-table/basic-contract-sign-dialog.tsx | 63 +++++++++++++--------- .../basicContract-table-toolbar-actions.tsx | 9 +++- lib/compliance/compliance-response-detail.tsx | 25 ++++++++- 8 files changed, 128 insertions(+), 121 deletions(-) diff --git a/db/schema/basicContractDocumnet.ts b/db/schema/basicContractDocumnet.ts index ba1f7856..6a73b82d 100644 --- a/db/schema/basicContractDocumnet.ts +++ b/db/schema/basicContractDocumnet.ts @@ -140,8 +140,7 @@ export const basicContractView = pgView('basic_contract_view').as((qb) => { .from(basicContract) .leftJoin(vendors, eq(basicContract.vendorId, vendors.id)) .leftJoin(users, eq(basicContract.requestedBy, users.id)) - .leftJoin(basicContractTemplates, eq(basicContract.templateId, basicContractTemplates.id)) - .leftJoin(legalWorks, eq(basicContract.id, legalWorks.basicContractId)); + .leftJoin(basicContractTemplates, eq(basicContract.templateId, basicContractTemplates.id)); }); // 타입 정의 diff --git a/lib/basic-contract/actions/check-gtc-comments.ts b/lib/basic-contract/actions/check-gtc-comments.ts index 1ce998bc..6d523cb0 100644 --- a/lib/basic-contract/actions/check-gtc-comments.ts +++ b/lib/basic-contract/actions/check-gtc-comments.ts @@ -4,11 +4,9 @@ import db from "@/db/db"; import { eq, and, desc, isNull, isNotNull, ne, or } from "drizzle-orm"; import { gtcDocuments, - gtcVendorDocuments, - gtcVendorClauses, - gtcNegotiationHistory, projects, - BasicContractView + BasicContractView, + agreementComments } from "@/db/schema"; /** @@ -27,7 +25,7 @@ function extractProjectCodeFromTemplateName(templateName: string): string | null // 프로젝트 코드는 보통 첫 번째 단어 if (words.length > 1 && words[words.length - 1].toLowerCase() === 'gtc') { - return words[words.length - 1]; + return words[0]; // 첫 번째 단어가 프로젝트 코드 } return null; @@ -105,14 +103,16 @@ async function checkGTCCommentsForContract( // 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) + eq(agreementComments.isDeleted, false), + isNotNull(agreementComments.comment), + ne(agreementComments.comment, '') ) ) .limit(1); @@ -130,51 +130,9 @@ async function checkGTCCommentsForContract( 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 - }; + // GTC 조항 기능은 더 이상 사용하지 않으므로, agreement_comments만 체크 + // agreement_comments에 코멘트가 없으면 hasComments = false 반환 + return { gtcDocumentId, hasComments: false }; } catch (error) { console.error('Error checking GTC comments for contract:', error); diff --git a/lib/basic-contract/agreement-comments/actions.ts b/lib/basic-contract/agreement-comments/actions.ts index 2f60c3d8..32e9ce4c 100644 --- a/lib/basic-contract/agreement-comments/actions.ts +++ b/lib/basic-contract/agreement-comments/actions.ts @@ -2,7 +2,7 @@ import { revalidateTag } from "next/cache"; import db from "@/db/db"; -import { eq, and, desc, inArray, sql } from "drizzle-orm"; +import { eq, and, desc, inArray, sql, isNotNull, ne } from "drizzle-orm"; import { agreementComments, basicContract, vendors, users } from "@/db/schema"; import { saveFile, deleteFile } from "@/lib/file-stroage"; import { sendEmail } from "@/lib/mail/sendEmail"; @@ -62,7 +62,9 @@ export async function getAgreementComments( .where( and( eq(agreementComments.basicContractId, basicContractId), - eq(agreementComments.isDeleted, false) + eq(agreementComments.isDeleted, false), + isNotNull(agreementComments.comment), + ne(agreementComments.comment, '') ) ) .orderBy(desc(agreementComments.createdAt)); diff --git a/lib/basic-contract/status-detail/basic-contracts-detail-columns.tsx b/lib/basic-contract/status-detail/basic-contracts-detail-columns.tsx index d03d0720..047866f7 100644 --- a/lib/basic-contract/status-detail/basic-contracts-detail-columns.tsx +++ b/lib/basic-contract/status-detail/basic-contracts-detail-columns.tsx @@ -172,25 +172,8 @@ export function getDetailColumns({ 재발송 - { - // 준법서약 템플릿인 경우 compliance 응답 페이지로 이동 - if (contract.templateName?.includes('준법')) { - try { - const response = await getComplianceResponseByBasicContractId(contract.id); - - if (response) { - router.push(`/evcp/compliance/${response.templateId}/responses/${response.id}`); - } else { - toast.error("준법서약 응답을 찾을 수 없습니다."); - setRowAction({ type: "view", row }); - } - } catch (error) { - console.error("Error fetching compliance response:", error); - toast.error("응답 정보를 가져오는데 실패했습니다."); - } - } else { - setRowAction({ type: "view", row }); - } + { + setRowAction({ type: "view", row }); }}> 상세 정보 @@ -327,19 +310,20 @@ export function getDetailColumns({ const handleOpenGTC = (e: React.MouseEvent) => { e.stopPropagation() - // gtcDocumentId가 있으면 그걸 사용, 없으면 templateId 사용 - const documentIdToUse = contractGtcData?.gtcDocumentId || contract.templateId - - if (documentIdToUse && contract.vendorId) { - const gtcUrl = `/evcp/basic-contract/vendor-gtc/${documentIdToUse}?vendorId=${contract.vendorId}&vendorName=${encodeURIComponent(contract.vendorName || '')}&contractId=${contract.id}&templateId=${contract.templateId}` - window.open(gtcUrl, '_blank') - } else { - console.error('GTC 페이지를 열 수 없습니다:', { - gtcDocumentId: contractGtcData?.gtcDocumentId, - templateId: contract.templateId, - vendorId: contract.vendorId - }) + // 상세보기와 동일하게 contract.id를 경로 파라미터로 사용 + const params = new URLSearchParams(); + if (contract.templateId) { + params.set("templateId", contract.templateId.toString()); + } + if (contract.vendorId) { + params.set("vendorId", contract.vendorId.toString()); + } + if (contract.vendorName) { + params.set("vendorName", contract.vendorName); } + const query = params.toString(); + const gtcUrl = `/evcp/basic-contract/vendor-gtc/${contract.id}${query ? `?${query}` : ""}`; + window.open(gtcUrl, '_blank'); } return ( diff --git a/lib/basic-contract/status-detail/basic-contracts-detail-table.tsx b/lib/basic-contract/status-detail/basic-contracts-detail-table.tsx index a2e1c5e4..9c5da894 100644 --- a/lib/basic-contract/status-detail/basic-contracts-detail-table.tsx +++ b/lib/basic-contract/status-detail/basic-contracts-detail-table.tsx @@ -235,13 +235,36 @@ type RedFlagResolutionState = { // GTC 템플릿인 경우 GTC 협의 페이지로 이동 const contract = rowAction.row.original; if (contract.templateName?.includes('GTC')) { - const contractGtcData = gtcData[contract.id]; - if (contractGtcData?.gtcDocumentId) { - const gtcUrl = `/evcp/basic-contract/vendor-gtc/${contractGtcData.gtcDocumentId}?vendorId=${contract.vendorId}&vendorName=${encodeURIComponent(contract.vendorName || '')}&contractId=${contract.id}&templateId=${contract.templateId}`; - router.push(gtcUrl); - } else { - toast.error("GTC 문서 정보를 찾을 수 없습니다."); + // GTC 템플릿인 경우 GTC 협의 페이지로 이동 + // 준법과 동일하게 contract.id를 경로 파라미터로 사용 + const params = new URLSearchParams(); + if (contract.templateId) { + params.set("templateId", contract.templateId.toString()); } + if (contract.vendorId) { + params.set("vendorId", contract.vendorId.toString()); + } + if (contract.vendorName) { + params.set("vendorName", contract.vendorName); + } + const query = params.toString(); + const gtcUrl = `/evcp/basic-contract/vendor-gtc/${contract.id}${query ? `?${query}` : ""}`; + router.push(gtcUrl); + } else if (contract.templateName?.includes('준법')) { + // 준법 템플릿인 경우 준법 코멘트 페이지로 이동 + const params = new URLSearchParams(); + if (contract.templateId) { + params.set("templateId", contract.templateId.toString()); + } + if (contract.vendorId) { + params.set("vendorId", contract.vendorId.toString()); + } + if (contract.vendorName) { + params.set("vendorName", contract.vendorName); + } + const query = params.toString(); + const complianceUrl = `/evcp/basic-contract/compliance-comments/${contract.id}${query ? `?${query}` : ""}`; + router.push(complianceUrl); } else { // 일반 계약서인 경우: 상세 정보를 보여주는 기능 // 현재는 준비 중이지만, 향후 다이얼로그나 시트를 열 수 있음 diff --git a/lib/basic-contract/vendor-table/basic-contract-sign-dialog.tsx b/lib/basic-contract/vendor-table/basic-contract-sign-dialog.tsx index e5aab10d..407a3c4d 100644 --- a/lib/basic-contract/vendor-table/basic-contract-sign-dialog.tsx +++ b/lib/basic-contract/vendor-table/basic-contract-sign-dialog.tsx @@ -142,13 +142,20 @@ const canCompleteCurrentContract = React.useMemo(() => { // 계약서별 상태 초기화 + // Vendor signed 상태의 계약서도 포함하여 초기화 React.useEffect(() => { if (contracts.length > 0 && contractStatuses.length === 0) { setContractStatuses( - contracts.map(contract => ({ - id: contract.id, - status: 'pending' as const - })) + contracts.map(contract => { + // 이미 서명된 계약서는 vendor_signed 상태로 초기화 + const isSigned = contract.vendorSignedAt || + contract.status === "COMPLETED" || + contract.status === "VENDOR_SIGNED"; + return { + id: contract.id, + status: isSigned ? ('vendor_signed' as const) : ('pending' as const) + }; + }) ); } }, [contracts, contractStatuses.length]); @@ -705,7 +712,7 @@ const canCompleteCurrentContract = React.useMemo(() => {
{filteredContracts.map((contract) => { const contractStatus = contractStatuses.find(status => status.id === contract.id); - const isCompleted = contractStatus?.status === 'completed'; + const isCompleted = contractStatus?.status === 'completed' || contractStatus?.status === 'vendor_signed'; const hasError = contractStatus?.status === 'error'; // 계약서별 완료 상태 확인 @@ -732,7 +739,8 @@ const canCompleteCurrentContract = React.useMemo(() => { !isCompleted && !hasError && "hover:bg-blue-50 hover:border-blue-200" )} onClick={() => handleSelectContract(contract)} - disabled={isCompleted} + // Vendor signed 상태에서도 코멘트를 볼 수 있도록 비활성화하지 않음 + disabled={false} >
{/* 첫 번째 줄: 제목 + 상태 */} @@ -851,7 +859,7 @@ const canCompleteCurrentContract = React.useMemo(() => { {selectedContract.templateName || t("basicContracts.dialog.document")} {/* 현재 계약서 상태 표시 */} - {currentContractStatus?.status === 'completed' ? ( + {(currentContractStatus?.status === 'completed' || currentContractStatus?.status === 'vendor_signed') ? ( {isBuyerMode ? "승인 완료" : "서명 완료"} @@ -931,10 +939,10 @@ const canCompleteCurrentContract = React.useMemo(() => {
{/* 현재 계약서가 완료된 경우 */} - {currentContractStatus?.status === 'completed' ? ( + {currentContractStatus?.status === 'completed' || currentContractStatus?.status === 'vendor_signed' ? (

- 이 계약서는 이미 {isBuyerMode ? "승인이" : "서명이"} 완료되었습니다 + 이 계약서는 이미 {isBuyerMode ? "승인이" : "서명이"} 완료되었습니다. 코멘트를 확인할 수 있습니다.

) : currentContractStatus?.status === 'error' ? (

@@ -1015,22 +1023,27 @@ const canCompleteCurrentContract = React.useMemo(() => { 모든 {isBuyerMode ? "승인" : "서명"} 완료 - ) : currentContractStatus?.status === 'completed' ? ( - // 현재 계약서가 완료된 경우 - + ) : (currentContractStatus?.status === 'completed' || currentContractStatus?.status === 'vendor_signed') ? ( + // 현재 계약서가 완료된 경우 - 코멘트 확인만 가능 +

+ +

+ 서명 완료 - 코멘트 확인 가능 +

+
) : ( // 현재 계약서를 서명해야 하는 경우
) } -- cgit v1.2.3