summaryrefslogtreecommitdiff
path: root/lib/basic-contract/vendor-table/basic-contract-sign-dialog.tsx
diff options
context:
space:
mode:
authordujinkim <dujin.kim@dtsolution.co.kr>2025-12-09 05:35:23 +0000
committerdujinkim <dujin.kim@dtsolution.co.kr>2025-12-09 05:35:23 +0000
commitea8aed1e1d62fb9fa6716347de73e4ef13040929 (patch)
tree133eea9c6be513670b7bb9b40e984543e5bdb4b9 /lib/basic-contract/vendor-table/basic-contract-sign-dialog.tsx
parent3462d754574e2558c791c7958d3e5da013a7a573 (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.tsx138
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>