From 935fd22e17afc034a472bc2d159de7b9f5e5dcae Mon Sep 17 00:00:00 2001
From: joonhoekim <26rote@gmail.com>
Date: Thu, 20 Nov 2025 19:36:01 +0900
Subject: (김준회) PO, POS, swp - PO: 발주서출력기능 초안 - 벤더측 POS 다운로드
 기능 추가 - Contract 생성시 Status 설정 (mapper) - swp document registration
 table 로직 리팩터링 - swp: 입력가능 문서번호 validation 추가 (리스트 메뉴에서
 Completed 된 건)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 lib/po/vendor-table/vendor-po-items-dialog.tsx | 131 ++++++++++++++++++++++++-
 1 file changed, 130 insertions(+), 1 deletion(-)

(limited to 'lib/po/vendor-table/vendor-po-items-dialog.tsx')

diff --git a/lib/po/vendor-table/vendor-po-items-dialog.tsx b/lib/po/vendor-table/vendor-po-items-dialog.tsx
index d88d88d1..8b984812 100644
--- a/lib/po/vendor-table/vendor-po-items-dialog.tsx
+++ b/lib/po/vendor-table/vendor-po-items-dialog.tsx
@@ -16,22 +16,122 @@ import {
   TableHeader,
   TableRow,
 } from "@/components/ui/table"
+import { Button } from "@/components/ui/button"
 import { Skeleton } from "@/components/ui/skeleton"
+import { Download, FileText } from "lucide-react"
+import { toast } from "sonner"
 import { VendorPO, VendorPOItem } from "./types"
 import { getVendorPOItemsByContractNo } from "./service"
 import { formatNumber } from "@/lib/utils"
+import { getDownloadUrlByMaterialCode, checkPosFileExists } from "@/lib/pos"
+import { PosFileSelectionDialog } from "@/lib/pos/components/pos-file-selection-dialog"
 
 interface VendorPOItemsDialogProps {
   open: boolean
   onOpenChange: (open: boolean) => void
   po: VendorPO | null
+  /**
+   * 뷰어 타입
+   * - 'evcp': EVCP 사용자 (암호화된 파일 직접 다운로드)
+   * - 'partners': 협력사 사용자 (복호화된 파일 다운로드)
+   */
+  viewerType?: 'evcp' | 'partners'
 }
 
-export function VendorPOItemsDialog({ open, onOpenChange, po }: VendorPOItemsDialogProps) {
+export function VendorPOItemsDialog({ open, onOpenChange, po, viewerType = 'partners' }: VendorPOItemsDialogProps) {
   const [items, setItems] = React.useState<VendorPOItem[]>([])
   const [loading, setLoading] = React.useState(false)
   const [error, setError] = React.useState<string | null>(null)
 
+  // POS 파일 선택 다이얼로그 상태
+  const [posDialogOpen, setPosDialogOpen] = React.useState(false)
+  const [selectedMaterialCode, setSelectedMaterialCode] = React.useState<string>("")
+  const [posFiles, setPosFiles] = React.useState<Array<{
+    fileName: string
+    dcmtmId: string
+    projNo: string
+    posNo: string
+    posRevNo: string
+    fileSer: string
+  }>>([])
+  const [loadingPosFiles, setLoadingPosFiles] = React.useState(false)
+  const [downloadingFileIndex, setDownloadingFileIndex] = React.useState<number | null>(null)
+
+  // POS 파일 목록 조회 및 다이얼로그 열기
+  const handleOpenPosDialog = async (materialCode: string) => {
+    if (!materialCode) {
+      toast.error("자재코드가 없습니다")
+      return
+    }
+
+    setLoadingPosFiles(true)
+    setSelectedMaterialCode(materialCode)
+
+    try {
+      toast.loading(`POS 파일 목록 조회 중... (${materialCode})`, { id: `pos-check-${materialCode}` })
+      
+      const result = await checkPosFileExists(materialCode)
+      
+      if (result.exists && result.files && result.files.length > 0) {
+        // 파일 정보를 상세하게 가져오기 위해 getDownloadUrlByMaterialCode 사용
+        const detailResult = await getDownloadUrlByMaterialCode(materialCode)
+        
+        if (detailResult.success && detailResult.availableFiles) {
+          setPosFiles(detailResult.availableFiles)
+          setPosDialogOpen(true)
+          toast.success(`${result.fileCount}개의 POS 파일을 찾았습니다`, { id: `pos-check-${materialCode}` })
+        } else {
+          toast.error('POS 파일 정보를 가져올 수 없습니다', { id: `pos-check-${materialCode}` })
+        }
+      } else {
+        toast.error(result.error || 'POS 파일을 찾을 수 없습니다', { id: `pos-check-${materialCode}` })
+      }
+    } catch (error) {
+      console.error("POS 파일 조회 오류:", error)
+      toast.error("POS 파일 조회에 실패했습니다", { id: `pos-check-${materialCode}` })
+    } finally {
+      setLoadingPosFiles(false)
+    }
+  }
+
+  // POS 파일 다운로드 실행
+  const handleDownloadPosFile = async (fileIndex: number, fileName: string) => {
+    if (!selectedMaterialCode) return
+
+    setDownloadingFileIndex(fileIndex)
+
+    try {
+      toast.loading(`POS 파일 다운로드 준비 중...`, { id: `download-${fileIndex}` })
+      
+      // viewerType에 따라 다른 엔드포인트 사용
+      const endpoint = viewerType === 'partners' 
+        ? `/api/pos/download-on-demand-partners` // 복호화 포함
+        : `/api/pos/download-on-demand` // 암호화 파일 그대로
+      
+      const downloadUrl = `${endpoint}?materialCode=${encodeURIComponent(selectedMaterialCode)}&fileIndex=${fileIndex}`
+      
+      toast.success(`POS 파일 다운로드 시작: ${fileName}`, { id: `download-${fileIndex}` })
+      window.open(downloadUrl, '_blank', 'noopener,noreferrer')
+      
+      // 다운로드 시작 후 잠시 대기 후 상태 초기화
+      setTimeout(() => {
+        setDownloadingFileIndex(null)
+      }, 1000)
+    } catch (error) {
+      console.error("POS 파일 다운로드 오류:", error)
+      toast.error("POS 파일 다운로드에 실패했습니다", { id: `download-${fileIndex}` })
+      setDownloadingFileIndex(null)
+    }
+  }
+
+  // POS 다이얼로그 닫기
+  const handleClosePosDialog = () => {
+    setPosDialogOpen(false)
+    setSelectedMaterialCode("")
+    setPosFiles([])
+    setDownloadingFileIndex(null)
+  }
+
   // 상세품목 데이터 로드
   React.useEffect(() => {
     if (!open || !po) {
@@ -123,6 +223,7 @@ export function VendorPOItemsDialog({ open, onOpenChange, po }: VendorPOItemsDia
                     <TableHead className="min-w-[120px] text-right whitespace-nowrap">기본총액(BRTWR)</TableHead>
                     <TableHead className="min-w-[120px] text-right whitespace-nowrap">조정금액(ZPDT_EXDS_AMT)</TableHead>
                     <TableHead className="min-w-[120px] text-right whitespace-nowrap">최종정가(NETWR)</TableHead>
+                    <TableHead className="min-w-[100px] text-center whitespace-nowrap">POS</TableHead>
                     <TableHead className="min-w-[100px] whitespace-nowrap">예정시작일자</TableHead>
                     <TableHead className="min-w-[100px] whitespace-nowrap">납기일자</TableHead>
                     <TableHead className="min-w-[100px] whitespace-nowrap">예정종료일자</TableHead>
@@ -223,6 +324,24 @@ export function VendorPOItemsDialog({ open, onOpenChange, po }: VendorPOItemsDia
                         return item.NETWR ? formatNumber(item.NETWR, decimals) : '-'
                       })()}
                     </TableCell>
+                    <TableCell className="text-center">
+                      {item.materialNo ? (
+                        <Button
+                          variant="ghost"
+                          size="sm"
+                          className="h-8 px-2 text-xs text-green-600 hover:text-green-800"
+                          onClick={() => handleOpenPosDialog(item.materialNo!)}
+                          disabled={loadingPosFiles && selectedMaterialCode === item.materialNo}
+                          title={`POS 파일 다운로드 (자재코드: ${item.materialNo})`}
+                        >
+                          <FileText className="h-3 w-3 mr-1" />
+                          <Download className="h-3 w-3 mr-1" />
+                          {loadingPosFiles && selectedMaterialCode === item.materialNo ? '조회중...' : 'POS'}
+                        </Button>
+                      ) : (
+                        <span className="text-muted-foreground">-</span>
+                      )}
+                    </TableCell>
                     <TableCell>{item.ZPLN_ST_DT || '-'}</TableCell>
                     <TableCell>{item.ZPO_DLV_DT || item.deliveryDate || '-'}</TableCell>
                     <TableCell>{item.ZPLN_ED_DT || '-'}</TableCell>
@@ -277,6 +396,16 @@ export function VendorPOItemsDialog({ open, onOpenChange, po }: VendorPOItemsDia
             </div>
           </div>
         )}
+
+        {/* POS 파일 선택 다이얼로그 */}
+        <PosFileSelectionDialog
+          isOpen={posDialogOpen}
+          onClose={handleClosePosDialog}
+          materialCode={selectedMaterialCode}
+          files={posFiles}
+          onDownload={handleDownloadPosFile}
+          downloadingIndex={downloadingFileIndex}
+        />
       </DialogContent>
     </Dialog>
   )
-- 
cgit v1.2.3

