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([]) const [loading, setLoading] = React.useState(false) const [error, setError] = React.useState(null) + // POS 파일 선택 다이얼로그 상태 + const [posDialogOpen, setPosDialogOpen] = React.useState(false) + const [selectedMaterialCode, setSelectedMaterialCode] = React.useState("") + const [posFiles, setPosFiles] = React.useState>([]) + const [loadingPosFiles, setLoadingPosFiles] = React.useState(false) + const [downloadingFileIndex, setDownloadingFileIndex] = React.useState(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 기본총액(BRTWR) 조정금액(ZPDT_EXDS_AMT) 최종정가(NETWR) + POS 예정시작일자 납기일자 예정종료일자 @@ -223,6 +324,24 @@ export function VendorPOItemsDialog({ open, onOpenChange, po }: VendorPOItemsDia return item.NETWR ? formatNumber(item.NETWR, decimals) : '-' })()} + + {item.materialNo ? ( + + ) : ( + - + )} + {item.ZPLN_ST_DT || '-'} {item.ZPO_DLV_DT || item.deliveryDate || '-'} {item.ZPLN_ED_DT || '-'} @@ -277,6 +396,16 @@ export function VendorPOItemsDialog({ open, onOpenChange, po }: VendorPOItemsDia )} + + {/* POS 파일 선택 다이얼로그 */} + ) -- cgit v1.2.3