diff options
| author | dujinkim <dujin.kim@dtsolution.co.kr> | 2025-12-09 05:35:23 +0000 |
|---|---|---|
| committer | dujinkim <dujin.kim@dtsolution.co.kr> | 2025-12-09 05:35:23 +0000 |
| commit | ea8aed1e1d62fb9fa6716347de73e4ef13040929 (patch) | |
| tree | 133eea9c6be513670b7bb9b40e984543e5bdb4b9 /lib/basic-contract/vendor-table/basic-contract-sign-dialog.tsx | |
| parent | 3462d754574e2558c791c7958d3e5da013a7a573 (diff) | |
(임수민) 공동인증서 개발
Diffstat (limited to 'lib/basic-contract/vendor-table/basic-contract-sign-dialog.tsx')
| -rw-r--r-- | lib/basic-contract/vendor-table/basic-contract-sign-dialog.tsx | 138 |
1 files changed, 136 insertions, 2 deletions
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 407a3c4d..3a6dcf9b 100644 --- a/lib/basic-contract/vendor-table/basic-contract-sign-dialog.tsx +++ b/lib/basic-contract/vendor-table/basic-contract-sign-dialog.tsx @@ -31,6 +31,7 @@ import { Separator } from "@/components/ui/separator"; import { useRouter } from "next/navigation" import { BasicContractSignViewer } from "../viewer/basic-contract-sign-viewer"; import { getVendorAttachments, processBuyerSignatureAction } from "../service"; +import { getVendorInfo } from "../service-vendor-info"; // 계약서 상태 타입 정의 interface ContractStatus { @@ -77,6 +78,7 @@ export function BasicContractSignDialog({ // 계약서 상태 관리 const [contractStatuses, setContractStatuses] = React.useState<ContractStatus[]>([]); + const [vendorInfo, setVendorInfo] = React.useState<{taxId?: string | null, country?: string | null} | null>(null); // 서명/설문/GTC 코멘트 완료 상태 관리 const [surveyCompletionStatus, setSurveyCompletionStatus] = React.useState<Record<number, boolean>>({}); @@ -97,7 +99,13 @@ export function BasicContractSignDialog({ // 모드에 따른 텍스트 const isBuyerMode = mode === 'buyer'; const dialogTitle = isBuyerMode ? "구매자 최종승인 서명" : t("basicContracts.dialog.title"); - const signButtonText = isBuyerMode ? "최종승인 완료" : "서명 완료 및 저장"; + + // 서명 버튼 텍스트 (내자/외자 구분) + const signButtonText = React.useMemo(() => { + if (isBuyerMode) return "최종승인 완료"; + if (vendorInfo?.country === 'KR') return "공동인증 서명"; + return "서명 완료 및 저장"; + }, [isBuyerMode, vendorInfo]); // 버튼 비활성화 조건 const isButtonDisabled = !hasSelectedRows || contracts.length === 0; @@ -257,15 +265,26 @@ const canCompleteCurrentContract = React.useMemo(() => { React.useEffect(() => { if (isBuyerMode) { setAdditionalFiles([]); + setVendorInfo(null); return; } const fetchAdditionalFiles = async () => { if (!selectedContract) { setAdditionalFiles([]); + setVendorInfo(null); return; } + // 벤더 정보 가져오기 (사업자번호 등) + if (selectedContract.vendorId) { + getVendorInfo(selectedContract.vendorId).then(res => { + if (res.success && res.data) { + setVendorInfo(res.data); + } + }); + } + // "비밀유지 계약서"인 경우에만 추가 파일 가져오기 if (selectedContract.templateName === "비밀유지 계약서" && selectedContract.vendorId) { setIsLoadingAttachments(true); @@ -329,6 +348,104 @@ const canCompleteCurrentContract = React.useMemo(() => { const completeSign = async () => { if (!instance || !selectedContract) return; + // 🔹 내자인 경우 공동인증 팝업 열기 + if (!isBuyerMode && vendorInfo?.country === 'KR') { + console.log("🔐 내자 협력업체 - 공동인증 서명 시작"); + + // 검증 로직은 동일하게 수행 + const contractId = selectedContract.id; + const isComplianceTemplate = selectedContract.templateName?.includes('준법'); + const isGTCTemplate = selectedContract.templateName?.includes('GTC'); + const surveyCompleted = isComplianceTemplate ? surveyCompletionStatus[contractId] === true : true; + const requiresNegotiationComplete = isComplianceTemplate || isGTCTemplate; + const negotiationStatus = gtcCommentStatus[contractId]; + const negotiationCleared = requiresNegotiationComplete + ? (!negotiationStatus?.hasComments || negotiationStatus?.isComplete === true) + : true; + + if (!surveyCompleted) { + toast({ + title: "준법 설문조사를 먼저 완료해주세요.", + description: "설문조사 탭에서 모든 필수 항목을 완료해주세요.", + variant: "destructive" + }); + return; + } + + if (!negotiationCleared) { + toast({ + title: "코멘트가 있어 서명할 수 없습니다.", + description: "협의 코멘트 탭에서 모든 코멘트를 삭제하거나 협의를 완료해주세요.", + variant: "destructive" + }); + return; + } + + // 공동인증 팝업 열기 + // trustnet은 포트 없이 접근 (<ip>:<port> → <ip>/trustnet) + // 단, API 호출을 위해 부모 창의 origin (포트 포함)을 전달 + const ssn = vendorInfo.taxId || ''; + const ssnParam = ssn ? `&ssn=${encodeURIComponent(ssn)}` : ''; + const baseUrl = `${window.location.protocol}//${window.location.hostname}`; + const apiOrigin = window.location.origin; // 포트 포함 (예: http://60.101.108.100:3001) + const popupUrl = `${baseUrl}/trustnet?contractId=${selectedContract.id}${ssnParam}&autoStart=true&apiOrigin=${encodeURIComponent(apiOrigin)}`; + + console.log('🔐 공동인증 팝업 열기:', { baseUrl, apiOrigin, popupUrl, ssn }); + const popupWidth = 600; + const popupHeight = 700; + const left = (window.screen.width - popupWidth) / 2; + const top = (window.screen.height - popupHeight) / 2; + + const popup = window.open( + popupUrl, + 'ContractSign', + `width=${popupWidth},height=${popupHeight},left=${left},top=${top},resizable=yes,scrollbars=yes` + ); + + if (!popup) { + toast({ + title: "팝업이 차단되었습니다", + description: "브라우저 팝업 차단을 해제해주세요.", + variant: "destructive" + }); + return; + } + + toast({ + title: "공동인증 서명 창이 열렸습니다", + description: "공동인증서로 서명을 진행해주세요.", + duration: 3000 // 3초 후 자동으로 사라짐 + }); + + // 팝업에서 서명 완료 메시지를 받으면 새로고침 + const handleMessage = (event: MessageEvent) => { + console.log('📨 메시지 수신:', event.data); + + if (event.data.type === 'INTERNAL_SIGN_COMPLETE') { + console.log('✅ 공동인증 서명 완료:', event.data.contractId); + + toast({ + title: "서명이 완료되었습니다", + description: "계약서 상태가 업데이트됩니다.", + duration: 3000 // 3초 후 자동으로 사라짐 + }); + + // 팝업 닫힌 후 테이블 데이터만 재조회 (페이지 전체 리로드 대신) + if (onSuccess) { + onSuccess(); + } + router.refresh(); + + // 리스너 제거 + window.removeEventListener('message', handleMessage); + } + }; + + window.addEventListener('message', handleMessage); + + return; + } + // 서명 완료 가능 여부 재확인 if (!canCompleteCurrentContract) { const contractId = selectedContract.id; @@ -345,7 +462,7 @@ const canCompleteCurrentContract = React.useMemo(() => { return; } } else { - // 협력업체 모드의 기존 검증 로직 + // 협력업체 모드의 기존 검증 로직 (외자) const isComplianceTemplate = selectedContract.templateName?.includes('준법'); const isGTCTemplate = selectedContract.templateName?.includes('GTC'); const surveyCompleted = isComplianceTemplate ? surveyCompletionStatus[contractId] === true : true; @@ -882,6 +999,21 @@ const canCompleteCurrentContract = React.useMemo(() => { </Badge> )} + {/* 내자/외자 표시 (협력업체 모드일 때만) */} + {!isBuyerMode && vendorInfo && ( + <Badge + variant="outline" + className={cn( + "ml-2", + vendorInfo.country === 'KR' + ? "bg-blue-50 text-blue-700 border-blue-200" + : "bg-gray-50 text-gray-700 border-gray-200" + )} + > + {vendorInfo.country === 'KR' ? '🔐 내자 (공동인증)' : '🌍 외자'} + </Badge> + )} + {/* 준법/GTC 템플릿 표시 (구매자 모드가 아닐 때만) */} {!isBuyerMode && selectedContract.templateName?.includes('준법') && ( <Badge variant="outline" className="ml-2 bg-amber-50 text-amber-700 border-amber-200"> @@ -932,6 +1064,8 @@ const canCompleteCurrentContract = React.useMemo(() => { mode={mode} t={t} negotiationCompletedAt={(selectedContract as any).negotiationCompletedAt || null} + vendorTaxId={vendorInfo?.taxId || undefined} + vendorCountry={vendorInfo?.country || undefined} /> </div> |
