diff options
Diffstat (limited to 'lib/general-contracts/detail/general-contract-approval-request-dialog.tsx')
| -rw-r--r-- | lib/general-contracts/detail/general-contract-approval-request-dialog.tsx | 281 |
1 files changed, 81 insertions, 200 deletions
diff --git a/lib/general-contracts/detail/general-contract-approval-request-dialog.tsx b/lib/general-contracts/detail/general-contract-approval-request-dialog.tsx index 25c1fb9a..46251c71 100644 --- a/lib/general-contracts/detail/general-contract-approval-request-dialog.tsx +++ b/lib/general-contracts/detail/general-contract-approval-request-dialog.tsx @@ -29,8 +29,12 @@ import { getContractItems,
getSubcontractChecklist,
uploadContractApprovalFile,
- sendContractApprovalRequest
+ sendContractApprovalRequest,
+ getContractById,
+ getContractTemplateByContractType,
+ getStorageInfo
} from '../service'
+import { mapContractDataToTemplateVariables } from '../utils'
interface ContractApprovalRequestDialogProps {
contract: Record<string, unknown>
@@ -42,6 +46,7 @@ interface ContractSummary { basicInfo: Record<string, unknown>
items: Record<string, unknown>[]
subcontractChecklist: Record<string, unknown> | null
+ storageInfo?: Record<string, unknown>[]
}
export function ContractApprovalRequestDialog({
@@ -70,63 +75,6 @@ export function ContractApprovalRequestDialog({ const contractId = contract.id as number
const userId = session?.user?.id || ''
- // LOI 템플릿용 변수 매핑 함수
- const mapContractSummaryToLOITemplate = (contractSummary: ContractSummary) => {
- const { basicInfo, items } = contractSummary
- const firstItem = items && items.length > 0 ? items[0] : {}
-
- // 날짜 포맷팅 헬퍼 함수
- const formatDate = (date: any) => {
- if (!date) return ''
- try {
- const d = new Date(date)
- return d.toLocaleDateString('ko-KR')
- } catch {
- return ''
- }
- }
-
- return {
- // 날짜 관련 (템플릿에서 {{todayDate}} 형식으로 사용)
- todayDate: new Date().toLocaleDateString('ko-KR'),
-
- // 벤더 정보
- vendorName: basicInfo?.vendorName || '',
- representativeName: '', // 벤더 대표자 이름 - 현재 데이터에 없음, 향후 확장 가능
-
- // 계약 기본 정보
- contractNumber: basicInfo?.contractNumber || '',
-
- // 프로젝트 정보
- projectNumber: '', // 프로젝트 코드 - 현재 데이터에 없음, 향후 확장 가능
- projectName: basicInfo?.projectName || '',
- project: basicInfo?.projectName || '',
-
- // 아이템 정보
- item: firstItem?.itemInfo || '',
-
- // 무역 조건
- incoterms: basicInfo?.deliveryTerm || '', // Incoterms 대신 deliveryTerm 사용
- shippingLocation: basicInfo?.shippingLocation || '',
-
- // 금액 및 통화
- contractCurrency: basicInfo?.currency || '',
- contractAmount: basicInfo?.contractAmount || '',
- totalAmount: basicInfo?.contractAmount || '', // totalAmount가 없으면 contractAmount 사용
-
- // 수량
- quantity: firstItem?.quantity || '',
-
- // 납기일
- contractDeliveryDate: formatDate(basicInfo?.contractDeliveryDate),
-
- // 지급 조건
- paymentTerm: basicInfo?.paymentTerm || '',
-
- // 유효기간
- validityEndDate: formatDate(basicInfo?.endDate), // validityEndDate 대신 endDate 사용
- }
- }
// 기본계약 생성 함수 (최종 전송 시점에 호출)
const generateBasicContractPdf = async (
@@ -312,6 +260,18 @@ export function ContractApprovalRequestDialog({ } catch {
console.log('Subcontract Checklist 데이터 없음')
}
+
+ // 임치(물품보관) 계약 정보 확인 (SG)
+ try {
+ if (summary.basicInfo?.contractType === 'SG') {
+ const storageData = await getStorageInfo(contractId)
+ if (storageData && storageData.length > 0) {
+ summary.storageInfo = storageData
+ }
+ }
+ } catch {
+ console.log('임치계약 정보 없음')
+ }
console.log('contractSummary 구조:', summary)
console.log('basicInfo 내용:', summary.basicInfo)
@@ -324,55 +284,42 @@ export function ContractApprovalRequestDialog({ }
}, [contractId])
- // 3단계: 파일 업로드 처리
- const handleFileUpload = async (file: File) => {
- // 파일 확장자 검증
- const allowedExtensions = ['.doc', '.docx']
- const fileExtension = file.name.toLowerCase().substring(file.name.lastIndexOf('.'))
-
- if (!allowedExtensions.includes(fileExtension)) {
- toast.error('Word 문서(.doc, .docx) 파일만 업로드 가능합니다.')
- return
- }
-
- if (!userId) {
- toast.error('로그인이 필요합니다.')
+ // 3단계: PDF 생성 및 미리보기 (PDFTron 사용) - 템플릿 자동 로드
+ const generatePdf = async () => {
+ if (!contractSummary) {
+ toast.error('계약 정보가 필요합니다.')
return
}
setIsLoading(true)
try {
- // 서버액션을 사용하여 파일 저장 (본 계약문서로 고정)
- const result = await uploadContractApprovalFile(
- contractId,
- file,
- userId
- )
+ // 1. 계약 유형에 맞는 템플릿 조회
+ const contractType = contractSummary.basicInfo.contractType as string
+ const templateResult = await getContractTemplateByContractType(contractType)
- if (result.success) {
- setUploadedFile(file)
- toast.success('파일이 업로드되었습니다.')
- } else {
- throw new Error(result.error || '파일 업로드 실패')
+ if (!templateResult.success || !templateResult.template) {
+ throw new Error(templateResult.error || '템플릿을 찾을 수 없습니다.')
}
- } catch (error) {
- console.error('Error uploading file:', error)
- toast.error('파일 업로드 중 오류가 발생했습니다.')
- } finally {
- setIsLoading(false)
- }
- }
- // 4단계: PDF 생성 및 미리보기 (PDFTron 사용)
- const generatePdf = async () => {
- if (!uploadedFile || !contractSummary) {
- toast.error('업로드된 파일과 계약 정보가 필요합니다.')
- return
- }
+ const template = templateResult.template
- setIsLoading(true)
- try {
- // PDFTron을 사용해서 변수 치환 및 PDF 변환
+ // 2. 템플릿 파일 다운로드
+ const templateResponse = await fetch("/api/contracts/get-template", {
+ method: "POST",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify({ templatePath: template.filePath }),
+ })
+
+ if (!templateResponse.ok) {
+ throw new Error("템플릿 파일을 다운로드할 수 없습니다.")
+ }
+
+ const templateBlob = await templateResponse.blob()
+ const templateFile = new File([templateBlob], template.fileName || "template.docx", {
+ type: "application/vnd.openxmlformats-officedocument.wordprocessingml.document"
+ })
+
+ // 3. PDFTron을 사용해서 변수 치환 및 PDF 변환
// @ts-ignore
const WebViewer = await import("@pdftron/webviewer").then(({ default: WebViewer }) => WebViewer)
@@ -394,34 +341,34 @@ export function ContractApprovalRequestDialog({ const { Core } = instance
const { createDocument } = Core
- // 템플릿 문서 생성 및 변수 치환
- const templateDoc = await createDocument(uploadedFile, {
- filename: uploadedFile.name,
- extension: 'docx',
- })
+ // 템플릿 문서 생성 및 변수 치환
+ const templateDoc = await createDocument(templateFile, {
+ filename: templateFile.name,
+ extension: 'docx',
+ })
- // LOI 템플릿용 변수 매핑
- const mappedTemplateData = mapContractSummaryToLOITemplate(contractSummary)
+ // 템플릿 변수 매핑
+ const mappedTemplateData = mapContractDataToTemplateVariables(contractSummary)
- console.log("🔄 변수 치환 시작:", mappedTemplateData)
- await templateDoc.applyTemplateValues(mappedTemplateData as any)
- console.log("✅ 변수 치환 완료")
+ console.log("🔄 변수 치환 시작:", mappedTemplateData)
+ await templateDoc.applyTemplateValues(mappedTemplateData as any)
+ console.log("✅ 변수 치환 완료")
- // PDF 변환
- const fileData = await templateDoc.getFileData()
+ // PDF 변환
+ const fileData = await templateDoc.getFileData()
const pdfBuffer = await (Core as any).officeToPDFBuffer(fileData, { extension: 'docx' })
- console.log(`✅ PDF 변환 완료: ${uploadedFile.name}`, `크기: ${pdfBuffer.byteLength} bytes`)
+ console.log(`✅ PDF 변환 완료: ${templateFile.name}`, `크기: ${pdfBuffer.byteLength} bytes`)
- // PDF 버퍼를 Blob URL로 변환하여 미리보기
- const pdfBlob = new Blob([pdfBuffer], { type: 'application/pdf' })
- const pdfUrl = URL.createObjectURL(pdfBlob)
- setGeneratedPdfUrl(pdfUrl)
-
- // PDF 버퍼를 상태에 저장 (최종 전송 시 사용)
- setGeneratedPdfBuffer(new Uint8Array(pdfBuffer))
-
- toast.success('PDF가 생성되었습니다.')
+ // PDF 버퍼를 Blob URL로 변환하여 미리보기
+ const pdfBlob = new Blob([pdfBuffer], { type: 'application/pdf' })
+ const pdfUrl = URL.createObjectURL(pdfBlob)
+ setGeneratedPdfUrl(pdfUrl)
+
+ // PDF 버퍼를 상태에 저장 (최종 전송 시 사용)
+ setGeneratedPdfBuffer(new Uint8Array(pdfBuffer))
+
+ toast.success('PDF가 생성되었습니다.')
} finally {
// 임시 WebViewer 정리
@@ -429,9 +376,10 @@ export function ContractApprovalRequestDialog({ document.body.removeChild(tempDiv)
}
- } catch (error) {
+ } catch (error: any) {
console.error('❌ PDF 생성 실패:', error)
- toast.error('PDF 생성 중 오류가 발생했습니다.')
+ const errorMessage = error instanceof Error ? error.message : (error?.message || '알 수 없는 오류')
+ toast.error(`PDF 생성 중 오류가 발생했습니다: ${errorMessage}`)
} finally {
setIsLoading(false)
}
@@ -498,13 +446,13 @@ export function ContractApprovalRequestDialog({ setPdfViewerInstance(instance)
// PDF 버퍼를 Blob으로 변환
- const pdfBlob = new Blob([generatedPdfBuffer], { type: 'application/pdf' })
+ const pdfBlob = new Blob([generatedPdfBuffer as any], { type: 'application/pdf' })
const pdfUrl = URL.createObjectURL(pdfBlob)
console.log("🔄 PDF Blob URL 생성:", pdfUrl)
// 문서 로드
console.log("🔄 문서 로드 시작")
- const { documentViewer } = instance.Core
+ const { documentViewer } = (instance as any).Core
// 문서 로드 이벤트 대기
await new Promise((resolve, reject) => {
@@ -553,7 +501,7 @@ export function ContractApprovalRequestDialog({ return
}
- const pdfBlob = new Blob([generatedPdfBuffer], { type: 'application/pdf' })
+ const pdfBlob = new Blob([generatedPdfBuffer as any], { type: 'application/pdf' })
const pdfUrl = URL.createObjectURL(pdfBlob)
const link = document.createElement('a')
@@ -715,7 +663,7 @@ export function ContractApprovalRequestDialog({ </DialogHeader>
<Tabs value={currentStep.toString()} className="w-full">
- <TabsList className="grid w-full grid-cols-4">
+ <TabsList className="grid w-full grid-cols-3">
<TabsTrigger value="1" disabled={currentStep < 1}>
1. 계약 현황 정리
</TabsTrigger>
@@ -723,10 +671,7 @@ export function ContractApprovalRequestDialog({ 2. 기본계약 체크
</TabsTrigger>
<TabsTrigger value="3" disabled={currentStep < 3}>
- 3. 문서 업로드
- </TabsTrigger>
- <TabsTrigger value="4" disabled={currentStep < 4}>
- 4. PDF 미리보기
+ 3. PDF 미리보기
</TabsTrigger>
</TabsList>
@@ -843,7 +788,7 @@ export function ContractApprovalRequestDialog({ <div>
<span className="font-medium">계약성립조건:</span>
{contractSummary?.basicInfo?.contractEstablishmentConditions &&
- Object.entries(contractSummary.basicInfo.contractEstablishmentConditions)
+ Object.entries(contractSummary.basicInfo.contractEstablishmentConditions as Record<string, unknown>)
.filter(([, value]) => value === true)
.map(([key]) => key)
.join(', ') || '없음'}
@@ -851,7 +796,7 @@ export function ContractApprovalRequestDialog({ <div>
<span className="font-medium">계약해지조건:</span>
{contractSummary?.basicInfo?.contractTerminationConditions &&
- Object.entries(contractSummary.basicInfo.contractTerminationConditions)
+ Object.entries(contractSummary.basicInfo.contractTerminationConditions as Record<string, unknown>)
.filter(([, value]) => value === true)
.map(([key]) => key)
.join(', ') || '없음'}
@@ -878,9 +823,9 @@ export function ContractApprovalRequestDialog({ <div className="max-h-32 overflow-y-auto">
{contractSummary.items.slice(0, 3).map((item: Record<string, unknown>, index: number) => (
<div key={index} className="text-xs bg-gray-50 p-2 rounded">
- <div className="font-medium">{item.itemInfo || item.description || `품목 ${index + 1}`}</div>
+ <div className="font-medium">{String(item.itemInfo || item.description || `품목 ${index + 1}`)}</div>
<div className="text-muted-foreground">
- 수량: {item.quantity || 0} | 단가: {item.contractUnitPrice || item.unitPrice || 0}
+ 수량: {String(item.quantity || 0)} | 단가: {String(item.contractUnitPrice || item.unitPrice || 0)}
</div>
</div>
))}
@@ -1022,75 +967,11 @@ export function ContractApprovalRequestDialog({ </div>
</TabsContent>
- {/* 3단계: 문서 업로드 */}
+ {/* 3단계: PDF 미리보기 */}
<TabsContent value="3" className="space-y-4">
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
- <Upload className="h-5 w-5 text-blue-600" />
- 계약서 업로드
- </CardTitle>
- </CardHeader>
- <CardContent className="space-y-4">
- <div className="space-y-4">
- <p className="text-lg text-muted-foreground">일반계약 표준문서 관리 페이지에 접속하여, 원하는 양식의 계약서를 다운받아 수정 후 업로드하세요.</p>
- <div>
- <Label htmlFor="file-upload">파일 업로드</Label>
- <Input
- id="file-upload"
- type="file"
- accept=".doc,.docx"
- onChange={(e) => {
- const file = e.target.files?.[0]
- if (file) handleFileUpload(file)
- }}
- />
- <p className="text-sm text-muted-foreground mt-1">
- Word 문서(.doc, .docx) 파일만 업로드 가능합니다.
- </p>
- </div>
-
- {/* ContractDocuments 컴포넌트 사용 */}
- {/* <div className="mt-4">
- <Label>업로드된 문서</Label>
- <ContractDocuments
- contractId={contractId}
- userId={userId}
- readOnly={false}
- />
- </div> */}
-
- {uploadedFile && (
- <div className="border rounded-lg p-4 bg-green-50">
- <div className="flex items-center gap-2">
- <CheckCircle className="h-4 w-4 text-green-600" />
- <span className="font-medium text-green-900">업로드 완료</span>
- </div>
- <p className="text-sm text-green-800 mt-1">{uploadedFile.name}</p>
- </div>
- )}
- </div>
- </CardContent>
- </Card>
-
- <div className="flex justify-between">
- <Button variant="outline" onClick={() => setCurrentStep(2)}>
- 이전 단계
- </Button>
- <Button
- onClick={() => setCurrentStep(4)}
- disabled={!uploadedFile}
- >
- 다음 단계
- </Button>
- </div>
- </TabsContent>
-
- {/* 4단계: PDF 미리보기 */}
- <TabsContent value="4" className="space-y-4">
- <Card>
- <CardHeader>
- <CardTitle className="flex items-center gap-2">
<Eye className="h-5 w-5 text-purple-600" />
PDF 미리보기
</CardTitle>
@@ -1168,7 +1049,7 @@ export function ContractApprovalRequestDialog({ </Card>
<div className="flex justify-between">
- <Button variant="outline" onClick={() => setCurrentStep(3)}>
+ <Button variant="outline" onClick={() => setCurrentStep(2)}>
이전 단계
</Button>
<Button
|
