summaryrefslogtreecommitdiff
path: root/lib/vendors/table/request-pq-dialog.tsx
diff options
context:
space:
mode:
authordujinkim <dujin.kim@dtsolution.co.kr>2025-08-07 05:04:39 +0000
committerdujinkim <dujin.kim@dtsolution.co.kr>2025-08-07 05:04:39 +0000
commite270e477f362dd68249bb4a013c66eab293bba82 (patch)
treeef29ec732fc62c220ca2f6ab12aa282b0db7c9ab /lib/vendors/table/request-pq-dialog.tsx
parenta5cecbef039e29beaa616e3a36e7f15b8b35623c (diff)
(최겸) PQ요청+기본계약 로직 수정(한글화 미적용)
Diffstat (limited to 'lib/vendors/table/request-pq-dialog.tsx')
-rw-r--r--lib/vendors/table/request-pq-dialog.tsx193
1 files changed, 182 insertions, 11 deletions
diff --git a/lib/vendors/table/request-pq-dialog.tsx b/lib/vendors/table/request-pq-dialog.tsx
index 6d477d9f..1df2d72c 100644
--- a/lib/vendors/table/request-pq-dialog.tsx
+++ b/lib/vendors/table/request-pq-dialog.tsx
@@ -36,11 +36,13 @@ import {
import { Checkbox } from "@/components/ui/checkbox"
import { Label } from "@/components/ui/label"
import { Vendor } from "@/db/schema/vendors"
-import { requestPQVendors } from "../service"
+import { requestBasicContractInfo, requestPQVendors } from "../service"
import { getProjectsWithPQList } from "@/lib/pq/service"
import type { Project } from "@/lib/pq/service"
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"
interface RequestPQDialogProps extends React.ComponentPropsWithoutRef<typeof Dialog> {
vendors: Row<Vendor>["original"][]
@@ -72,6 +74,9 @@ export function RequestPQDialog({ vendors, showTrigger = true, onSuccess, ...pro
const [extraNote, setExtraNote] = React.useState<string>("")
const [pqItems, setPqItems] = React.useState<string>("")
const [isLoadingProjects, setIsLoadingProjects] = React.useState(false)
+ const [basicContractTemplates, setBasicContractTemplates] = React.useState<BasicContractTemplate[]>([])
+ const [selectedTemplateIds, setSelectedTemplateIds] = React.useState<number[]>([])
+ const [isLoadingTemplates, setIsLoadingTemplates] = React.useState(false)
React.useEffect(() => {
if (type === "PROJECT") {
@@ -81,6 +86,15 @@ export function RequestPQDialog({ vendors, showTrigger = true, onSuccess, ...pro
}
}, [type])
+ // 기본계약서 템플릿 로딩
+ React.useEffect(() => {
+ setIsLoadingTemplates(true)
+ getALLBasicContractTemplates()
+ .then(setBasicContractTemplates)
+ .catch(() => toast.error("기본계약서 템플릿 로딩 실패"))
+ .finally(() => setIsLoadingTemplates(false))
+ }, [])
+
React.useEffect(() => {
if (!props.open) {
setType(null)
@@ -89,6 +103,7 @@ export function RequestPQDialog({ vendors, showTrigger = true, onSuccess, ...pro
setDueDate(null)
setPqItems("")
setExtraNote("")
+ setSelectedTemplateIds([])
}
}, [props.open])
@@ -99,7 +114,10 @@ export function RequestPQDialog({ vendors, showTrigger = true, onSuccess, ...pro
if (!session?.user?.id) return toast.error("인증 실패")
startApproveTransition(async () => {
- const { error } = await requestPQVendors({
+ try {
+ // 1단계: PQ 생성
+ console.log("🚀 1단계: PQ 생성 시작")
+ const { error: pqError } = await requestPQVendors({
ids: vendors.map((v) => v.id),
userId: Number(session.user.id),
agreements,
@@ -108,16 +126,132 @@ export function RequestPQDialog({ vendors, showTrigger = true, onSuccess, ...pro
type: type || "GENERAL",
extraNote,
pqItems,
+ templateId: selectedTemplateIds.length > 0 ? selectedTemplateIds[0] : null,
})
-
- if (error) {
- toast.error(error)
- return
+
+ if (pqError) {
+ toast.error(`PQ 생성 실패: ${pqError}`)
+ return
+ }
+ console.log("✅ 1단계: PQ 생성 완료")
+
+ // 2단계 & 3단계: 기본계약서 템플릿이 선택된 경우에만 실행 (여러 템플릿 처리)
+ if (selectedTemplateIds.length > 0) {
+ console.log(`🚀 2단계 & 3단계: ${selectedTemplateIds.length}개 템플릿 처리 시작`)
+
+ 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가 성공적으로 요청되었습니다")
+ }
+
+ props.onOpenChange?.(false)
+ onSuccess?.()
+ } catch (error) {
+ console.error('전체 프로세스 오류:', error)
+ toast.error(`처리 중 오류가 발생했습니다: ${error instanceof Error ? error.message : '알 수 없는 오류'}`)
}
-
- props.onOpenChange?.(false)
- toast.success("PQ가 성공적으로 요청되었습니다")
- onSuccess?.()
})
}
@@ -204,7 +338,44 @@ export function RequestPQDialog({ vendors, showTrigger = true, onSuccess, ...pro
/>
</div>
+ {/* 기본계약서 템플릿 선택 (다중 선택) */}
<div className="space-y-2">
+ <Label>기본계약서 템플릿 (선택사항, 복수 선택 가능)</Label>
+ {isLoadingTemplates ? (
+ <div className="text-sm text-muted-foreground">템플릿 로딩 중...</div>
+ ) : (
+ <div className="space-y-2 max-h-40 overflow-y-auto border rounded-md p-3">
+ {basicContractTemplates.map((template) => (
+ <div key={template.id} className="flex items-center gap-2">
+ <Checkbox
+ id={`template-${template.id}`}
+ checked={selectedTemplateIds.includes(template.id)}
+ onCheckedChange={(checked) => {
+ if (checked) {
+ setSelectedTemplateIds(prev => [...prev, template.id])
+ } else {
+ setSelectedTemplateIds(prev => prev.filter(id => id !== template.id))
+ }
+ }}
+ />
+ <Label htmlFor={`template-${template.id}`} className="text-sm">
+ {template.templateName}
+ </Label>
+ </div>
+ ))}
+ {basicContractTemplates.length === 0 && (
+ <div className="text-sm text-muted-foreground">사용 가능한 템플릿이 없습니다.</div>
+ )}
+ </div>
+ )}
+ {selectedTemplateIds.length > 0 && (
+ <div className="text-xs text-muted-foreground">
+ {selectedTemplateIds.length}개 템플릿이 선택되었습니다.
+ </div>
+ )}
+ </div>
+
+ {/* <div className="space-y-2">
<Label>계약 항목 선택</Label>
{AGREEMENT_LIST.map((label) => (
<div key={label} className="flex items-center gap-2">
@@ -218,7 +389,7 @@ export function RequestPQDialog({ vendors, showTrigger = true, onSuccess, ...pro
<Label htmlFor={label}>{label}</Label>
</div>
))}
- </div>
+ </div> */}
</div>
)