diff options
Diffstat (limited to 'lib/vendors')
| -rw-r--r-- | lib/vendors/table/request-pq-dialog.tsx | 267 |
1 files changed, 155 insertions, 112 deletions
diff --git a/lib/vendors/table/request-pq-dialog.tsx b/lib/vendors/table/request-pq-dialog.tsx index 1df2d72c..226a053f 100644 --- a/lib/vendors/table/request-pq-dialog.tsx +++ b/lib/vendors/table/request-pq-dialog.tsx @@ -43,6 +43,7 @@ import { useSession } from "next-auth/react" import { DatePicker } from "@/components/ui/date-picker"
import { getALLBasicContractTemplates } from "@/lib/basic-contract/service"
import type { BasicContractTemplate } from "@/db/schema"
+// import { PQContractViewer } from "../pq-contract-viewer" // 더 이상 사용하지 않음
interface RequestPQDialogProps extends React.ComponentPropsWithoutRef<typeof Dialog> {
vendors: Row<Vendor>["original"][]
@@ -78,6 +79,7 @@ export function RequestPQDialog({ vendors, showTrigger = true, onSuccess, ...pro const [selectedTemplateIds, setSelectedTemplateIds] = React.useState<number[]>([])
const [isLoadingTemplates, setIsLoadingTemplates] = React.useState(false)
+
React.useEffect(() => {
if (type === "PROJECT") {
setIsLoadingProjects(true)
@@ -104,6 +106,7 @@ export function RequestPQDialog({ vendors, showTrigger = true, onSuccess, ...pro setPqItems("")
setExtraNote("")
setSelectedTemplateIds([])
+
}
}, [props.open])
@@ -116,7 +119,7 @@ export function RequestPQDialog({ vendors, showTrigger = true, onSuccess, ...pro startApproveTransition(async () => {
try {
// 1단계: PQ 생성
- console.log("🚀 1단계: PQ 생성 시작")
+ console.log("🚀 PQ 생성 시작")
const { error: pqError } = await requestPQVendors({
ids: vendors.map((v) => v.id),
userId: Number(session.user.id),
@@ -133,128 +136,156 @@ export function RequestPQDialog({ vendors, showTrigger = true, onSuccess, ...pro toast.error(`PQ 생성 실패: ${pqError}`)
return
}
- console.log("✅ 1단계: PQ 생성 완료")
+ console.log("✅ PQ 생성 완료")
+ toast.success("PQ가 성공적으로 요청되었습니다")
- // 2단계 & 3단계: 기본계약서 템플릿이 선택된 경우에만 실행 (여러 템플릿 처리)
+ // 2단계: 기본계약서 템플릿이 선택된 경우 백그라운드에서 처리
if (selectedTemplateIds.length > 0) {
- console.log(`🚀 2단계 & 3단계: ${selectedTemplateIds.length}개 템플릿 처리 시작`)
+ const templates = basicContractTemplates.filter(t =>
+ selectedTemplateIds.includes(t.id)
+ )
- let successCount = 0
- let errorCount = 0
- const errors: string[] = []
-
- // 템플릿별로 반복 처리
- for (let i = 0; i < selectedTemplateIds.length; i++) {
- const templateId = selectedTemplateIds[i]
- const selectedTemplate = basicContractTemplates.find(t => t.id === templateId)
-
- if (!selectedTemplate) {
- console.error(`템플릿 ID ${templateId}를 찾을 수 없습니다`)
- errorCount++
- errors.push(`템플릿 ID ${templateId}를 찾을 수 없습니다`)
- continue
- }
-
- try {
- console.log(`📄 [${i+1}/${selectedTemplateIds.length}] ${selectedTemplate.templateName} - 2단계: DOCX to PDF 변환 시작`)
-
- // 템플릿 파일을 가져와서 PDF로 변환
- const formData = new FormData()
-
- // 템플릿 파일 가져오기 (서버에서 파일 읽기)
- const templateResponse = await fetch('/api/basic-contract/get-template', {
- method: 'POST',
- headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify({ templateId })
- })
-
- if (!templateResponse.ok) {
- throw new Error(`템플릿 파일을 가져올 수 없습니다: ${selectedTemplate.templateName}`)
- }
- console.log(`✅ [${i+1}/${selectedTemplateIds.length}] ${selectedTemplate.templateName} - 템플릿 파일 가져오기 완료`)
-
- const templateBlob = await templateResponse.blob()
- const templateFile = new File([templateBlob], selectedTemplate.fileName || 'template.docx')
-
- // 템플릿 데이터 생성 (첫 번째 협력업체 정보 기반)
- const firstVendor = vendors[0]
- const templateData = {
- // 영문 변수명으로 변경 (PDFTron이 한글 변수명을 지원하지 않음)
- vendor_name: firstVendor?.vendorName || '협력업체명',
- address: firstVendor?.address || '주소',
- representative_name: firstVendor?.representativeName || '대표자명',
- today_date: new Date().toLocaleDateString('ko-KR'),
- }
-
- console.log(`📝 [${i+1}/${selectedTemplateIds.length}] ${selectedTemplate.templateName} - 생성된 템플릿 데이터:`, templateData)
-
- formData.append('templateFile', templateFile)
- formData.append('outputFileName', `${selectedTemplate.templateName}_converted.pdf`)
- formData.append('templateData', JSON.stringify(templateData))
-
- // PDF 변환 호출
- const pdfResponse = await fetch('/api/pdftron/createBasicContractPdf', {
- method: 'POST',
- body: formData,
- })
- console.log(`✅ [${i+1}/${selectedTemplateIds.length}] ${selectedTemplate.templateName} - PDF 변환 호출 완료`)
-
- if (!pdfResponse.ok) {
- const errorText = await pdfResponse.text()
- throw new Error(`PDF 변환 실패 (${selectedTemplate.templateName}): ${errorText}`)
- }
-
- const pdfBuffer = await pdfResponse.arrayBuffer()
- console.log(`✅ [${i+1}/${selectedTemplateIds.length}] ${selectedTemplate.templateName} - PDF 변환 완료`)
-
- // 3단계: 변환된 PDF로 기본계약 생성
- console.log(`📋 [${i+1}/${selectedTemplateIds.length}] ${selectedTemplate.templateName} - 3단계: 기본계약 생성 시작`)
- const { error: contractError } = await requestBasicContractInfo({
- vendorIds: vendors.map((v) => v.id),
- requestedBy: Number(session.user.id),
- templateId,
- pdfBuffer: new Uint8Array(pdfBuffer), // ArrayBuffer를 Uint8Array로 변환하여 전달
- })
-
- if (contractError) {
- console.error(`기본계약 생성 오류 (${selectedTemplate.templateName}):`, contractError)
- errorCount++
- errors.push(`${selectedTemplate.templateName}: ${contractError}`)
- } else {
- console.log(`✅ [${i+1}/${selectedTemplateIds.length}] ${selectedTemplate.templateName} - 3단계: 기본계약 생성 완료`)
- successCount++
- }
- } catch (templateError) {
- console.error(`템플릿 처리 오류 (${selectedTemplate.templateName}):`, templateError)
- errorCount++
- errors.push(`${selectedTemplate.templateName}: ${templateError instanceof Error ? templateError.message : '알 수 없는 오류'}`)
- }
- }
-
- // 결과 토스트 메시지
- if (successCount > 0 && errorCount === 0) {
- toast.success(`PQ 요청 및 ${successCount}개 기본계약서 생성이 모두 완료되었습니다!`)
- } else if (successCount > 0 && errorCount > 0) {
- toast.success(`PQ는 성공적으로 요청되었습니다. ${successCount}개 기본계약서 성공, ${errorCount}개 실패`)
- console.error('기본계약서 생성 오류들:', errors)
- } else if (errorCount > 0) {
- toast.error(`PQ는 성공적으로 요청되었지만, 모든 기본계약서 생성이 실패했습니다`)
- console.error('기본계약서 생성 오류들:', errors)
- }
- } else {
- // 기본계약서 템플릿이 선택되지 않은 경우
- toast.success("PQ가 성공적으로 요청되었습니다")
+ console.log("📋 기본계약서 백그라운드 처리 시작", templates.length, "개 템플릿")
+ await processBasicContractsInBackground(templates, vendors)
}
-
+
+ // 완료 후 다이얼로그 닫기
props.onOpenChange?.(false)
onSuccess?.()
+
} catch (error) {
- console.error('전체 프로세스 오류:', error)
+ console.error('PQ 생성 오류:', error)
toast.error(`처리 중 오류가 발생했습니다: ${error instanceof Error ? error.message : '알 수 없는 오류'}`)
}
})
}
+ // 백그라운드에서 기본계약서 처리
+ const processBasicContractsInBackground = async (templates: BasicContractTemplate[], vendors: any[]) => {
+ if (!session?.user?.id) {
+ toast.error("인증 정보가 없습니다")
+ return
+ }
+
+ try {
+ const totalContracts = templates.length * vendors.length
+ let processedCount = 0
+
+ // 각 벤더별로, 각 템플릿을 처리
+ for (let vendorIndex = 0; vendorIndex < vendors.length; vendorIndex++) {
+ const vendor = vendors[vendorIndex]
+
+ // 벤더별 템플릿 데이터 생성
+ const templateData = {
+ vendor_name: vendor.vendorName || '협력업체명',
+ address: vendor.address || '주소',
+ representative_name: vendor.representativeName || '대표자명',
+ today_date: new Date().toLocaleDateString('ko-KR'),
+ }
+
+ console.log(`🔄 벤더 ${vendorIndex + 1}/${vendors.length} 템플릿 데이터:`, templateData)
+
+ // 해당 벤더에 대해 각 템플릿을 순차적으로 처리
+ for (let templateIndex = 0; templateIndex < templates.length; templateIndex++) {
+ const template = templates[templateIndex]
+ processedCount++
+
+ console.log(`📄 처리 중: ${vendor.vendorName} - ${template.templateName} (${processedCount}/${totalContracts})`)
+
+ // 개별 벤더에 대한 기본계약 생성
+ await processTemplate(template, templateData, [vendor])
+
+ console.log(`✅ 완료: ${vendor.vendorName} - ${template.templateName}`)
+ }
+ }
+
+ toast.success(`총 ${totalContracts}개 기본계약이 모두 생성되었습니다`)
+
+ } catch (error) {
+ console.error('기본계약 처리 중 오류:', error)
+ toast.error(`기본계약 처리 중 오류가 발생했습니다: ${error instanceof Error ? error.message : '알 수 없는 오류'}`)
+ }
+ }
+
+ const processTemplate = async (template: BasicContractTemplate, templateData: any, vendors: any[]) => {
+ try {
+ // 1. 템플릿 파일 가져오기
+ const templateResponse = await fetch('/api/basic-contract/get-template', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ templateId: template.id })
+ })
+
+ if (!templateResponse.ok) {
+ throw new Error(`템플릿 파일을 가져올 수 없습니다: ${template.templateName}`)
+ }
+
+ const templateBlob = await templateResponse.blob()
+
+ // 2. PDFTron을 사용해서 변수 치환 및 PDF 변환
+ // @ts-ignore
+ const WebViewer = await import("@pdftron/webviewer").then(({ default: WebViewer }) => WebViewer)
+
+ // 임시 WebViewer 인스턴스 생성 (DOM에 추가하지 않음)
+ const tempDiv = document.createElement('div')
+ tempDiv.style.display = 'none'
+ document.body.appendChild(tempDiv)
+
+ const instance = await WebViewer(
+ {
+ path: "/pdftronWeb",
+ licenseKey: process.env.NEXT_PUBLIC_PDFTRON_WEBVIEW_KEY,
+ fullAPI: true,
+ },
+ tempDiv
+ )
+
+ try {
+ const { Core } = instance
+ const { createDocument } = Core
+
+ // 3. 템플릿 문서 생성 및 변수 치환
+ const templateDoc = await createDocument(templateBlob, {
+ filename: template.fileName || 'template.docx',
+ extension: 'docx',
+ })
+
+ console.log("🔄 변수 치환 시작:", templateData)
+ await templateDoc.applyTemplateValues(templateData)
+ console.log("✅ 변수 치환 완료")
+
+ // 4. PDF 변환
+ const fileData = await templateDoc.getFileData()
+ const pdfBuffer = await Core.officeToPDFBuffer(fileData, { extension: 'docx' })
+
+ console.log(`✅ PDF 변환 완료: ${template.templateName}`, `크기: ${pdfBuffer.byteLength} bytes`)
+
+ // 5. 기본계약 생성 요청
+ const { error: contractError } = await requestBasicContractInfo({
+ vendorIds: vendors.map((v) => v.id),
+ requestedBy: Number(session!.user.id),
+ templateId: template.id,
+ pdfBuffer: new Uint8Array(pdfBuffer),
+ })
+
+ if (contractError) {
+ throw new Error(contractError)
+ }
+
+ console.log(`✅ 기본계약 생성 완료: ${template.templateName}`)
+
+ } finally {
+ // 임시 WebViewer 정리
+ instance.UI.dispose()
+ document.body.removeChild(tempDiv)
+ }
+
+ } catch (error) {
+ console.error(`❌ 템플릿 처리 실패: ${template.templateName}`, error)
+ throw error
+ }
+ }
+
const dialogContent = (
<div className="space-y-4 py-2">
{/* 선택된 협력업체 정보 */}
@@ -309,7 +340,15 @@ export function RequestPQDialog({ vendors, showTrigger = true, onSuccess, ...pro <Label htmlFor="dueDate">PQ 제출 마감일</Label>
<DatePicker
date={dueDate ? new Date(dueDate) : undefined}
- onSelect={(date?: Date) => setDueDate(date ? date.toISOString().slice(0, 10) : "")}
+ onSelect={(date?: Date) => {
+ if (date) {
+ // 한국 시간대로 날짜 변환 (UTC 변환으로 인한 날짜 변경 방지)
+ const kstDate = new Date(date.getTime() - date.getTimezoneOffset() * 60000)
+ setDueDate(kstDate.toISOString().slice(0, 10))
+ } else {
+ setDueDate("")
+ }
+ }}
placeholder="마감일 선택"
/>
</div>
@@ -421,6 +460,8 @@ export function RequestPQDialog({ vendors, showTrigger = true, onSuccess, ...pro </Button>
</DialogFooter>
</DialogContent>
+
+
</Dialog>
)
}
@@ -452,6 +493,8 @@ export function RequestPQDialog({ vendors, showTrigger = true, onSuccess, ...pro </Button>
</DrawerFooter>
</DrawerContent>
+
+
</Drawer>
)
}
|
