diff options
| author | joonhoekim <26rote@gmail.com> | 2025-11-02 14:03:34 +0900 |
|---|---|---|
| committer | joonhoekim <26rote@gmail.com> | 2025-11-02 14:03:34 +0900 |
| commit | de4c6593f0cc91c6e0c1a4e2bf9581f11f4f5c98 (patch) | |
| tree | 8b3d88637309ac9fb67f79606d834364d784105b /lib/vendor-document-list/plant | |
| parent | fd5ff7a9eaea4baeacc3f4bec8254925d63bf255 (diff) | |
(김준회) SWP 리스트 관리 파트 오류 수정 및 요구사항 반영, 동적 상태 리스트 필터링 변경, null은 동기화 전(전송 전)으로 간주, 선택된 것만 보내도록 변경
Diffstat (limited to 'lib/vendor-document-list/plant')
7 files changed, 174 insertions, 190 deletions
diff --git a/lib/vendor-document-list/plant/document-stage-dialogs.tsx b/lib/vendor-document-list/plant/document-stage-dialogs.tsx index 8a7dcbc4..6c8fa797 100644 --- a/lib/vendor-document-list/plant/document-stage-dialogs.tsx +++ b/lib/vendor-document-list/plant/document-stage-dialogs.tsx @@ -92,7 +92,6 @@ const getStatusText = (status: string) => { // Form validation schema const documentFormSchema = z.object({ documentClassId: z.string().min(1, "Document class is required"), - docClass: z.string().min(1, "Document class is required"), title: z.string().min(1, "Document title is required"), shiFieldValues: z.record(z.string()).optional(), cpyFieldValues: z.record(z.string()).optional(), @@ -133,7 +132,6 @@ export function AddDocumentDialog({ resolver: zodResolver(documentFormSchema), defaultValues: { documentClassId: '', - docClass: '', title: '', shiFieldValues: {}, cpyFieldValues: {}, @@ -376,11 +374,15 @@ export function AddDocumentDialog({ const shiDocNumber = shiType ? generateShiPreview() : '' const cpyDocNumber = cpyType ? generateCpyPreview() : '' + // 선택된 Document Class의 code 값을 가져오기 (SWP API의 DOC_CLASS로 사용) + const selectedDocClass = documentClasses.find(cls => String(cls.id) === data.documentClassId) + const docClassCode = selectedDocClass?.code || '' + try { const submitData = { contractId, documentClassId: Number(data.documentClassId), - docClass: data.docClass, + docClass: docClassCode, // 첫 번째 선택기의 code 값 사용 (A, B, C 등) title: data.title, docNumber: shiDocNumber, vendorDocNumber: cpyDocNumber, @@ -618,34 +620,6 @@ export function AddDocumentDialog({ )} </div> - {/* Document Class Selection (B3, B4, B5) */} - <div className="space-y-2"> - <Label htmlFor="docClass"> - Document Class <span className="text-red-500">*</span> - </Label> - <Controller - name="docClass" - control={form.control} - render={({ field }) => ( - <Select value={field.value} onValueChange={field.onChange}> - <SelectTrigger> - <SelectValue placeholder="Select document class (B3, B4, B5)" /> - </SelectTrigger> - <SelectContent> - <SelectItem value="B3">B3 - Basic Engineering Document</SelectItem> - <SelectItem value="B4">B4 - Detail Engineering Document</SelectItem> - <SelectItem value="B5">B5 - Vendor Document</SelectItem> - </SelectContent> - </Select> - )} - /> - {form.formState.errors.docClass && ( - <p className="text-xs text-red-500"> - {form.formState.errors.docClass.message} - </p> - )} - </div> - {/* Document Class Options with Plan Dates */} {documentClassOptions.length > 0 && ( <Card> diff --git a/lib/vendor-document-list/plant/document-stage-toolbar.tsx b/lib/vendor-document-list/plant/document-stage-toolbar.tsx index 51767528..4a0b32c8 100644 --- a/lib/vendor-document-list/plant/document-stage-toolbar.tsx +++ b/lib/vendor-document-list/plant/document-stage-toolbar.tsx @@ -56,14 +56,24 @@ export function DocumentsTableToolbarActions({ }) async function handleSendToSHI() { + // 선택된 문서 가져오기 + const selectedRows = table.getFilteredSelectedRowModel().rows + const selectedDocumentIds = selectedRows.map(row => row.original.documentId) + + if (selectedDocumentIds.length === 0) { + toast.error("전송할 문서를 선택해주세요.") + return + } + setIsSending(true) try { - const result = await sendDocumentsToSHI(contractId) + const result = await sendDocumentsToSHI(contractId, selectedDocumentIds) if (result.success) { toast.success(result.message) + // 선택 해제 + table.toggleAllRowsSelected(false) router.refresh() - // 테이블 새로고침 } else { toast.error(result.message) } diff --git a/lib/vendor-document-list/plant/document-stages-columns.tsx b/lib/vendor-document-list/plant/document-stages-columns.tsx index f9cde264..c74f2d71 100644 --- a/lib/vendor-document-list/plant/document-stages-columns.tsx +++ b/lib/vendor-document-list/plant/document-stages-columns.tsx @@ -357,10 +357,11 @@ export function getDocumentStagesColumns({ ), cell: ({ row }) => { const doc = row.original + const displayStatus = doc.buyerSystemStatus || 'Before Sync' return ( <div className="flex items-center gap-2"> - {getStatusText(doc.buyerSystemStatus || '')} + {displayStatus} </div> ) }, diff --git a/lib/vendor-document-list/plant/document-stages-service.ts b/lib/vendor-document-list/plant/document-stages-service.ts index ae9ea314..47bc6ff8 100644 --- a/lib/vendor-document-list/plant/document-stages-service.ts +++ b/lib/vendor-document-list/plant/document-stages-service.ts @@ -1165,10 +1165,10 @@ export async function getDocumentsByStageStats(contractId: number) { } // Send to SHI 버튼 핸들러가 이 함수 호출 -export async function sendDocumentsToSHI(contractId: number) { +export async function sendDocumentsToSHI(contractId: number, selectedDocumentIds?: number[]) { try { const api = new ShiBuyerSystemAPI() - const result = await api.sendToSHI(contractId) + const result = await api.sendToSHI(contractId, selectedDocumentIds) // 캐시 무효화 revalidatePath(`/partners/document-list-only/${contractId}`) diff --git a/lib/vendor-document-list/plant/document-stages-table.tsx b/lib/vendor-document-list/plant/document-stages-table.tsx index 63f0eae6..cd23db2d 100644 --- a/lib/vendor-document-list/plant/document-stages-table.tsx +++ b/lib/vendor-document-list/plant/document-stages-table.tsx @@ -11,8 +11,8 @@ import { useDataTable } from "@/hooks/use-data-table" import { getDocumentStagesOnly } from "./document-stages-service" import type { StageDocumentsView } from "@/db/schema" import { DataTableAdvancedToolbar } from "@/components/data-table/data-table-advanced-toolbar" -import { Badge } from "@/components/ui/badge" import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card" +import { cn } from "@/lib/utils" import { FileText, Send, @@ -62,7 +62,7 @@ export function DocumentStagesTable({ // 상태 관리 const [rowAction, setRowAction] = React.useState<DataTableRowAction<StageDocumentsView> | null>(null) const [expandedRows, setExpandedRows] = React.useState<Set<string>>(new Set()) - const [quickFilter, setQuickFilter] = React.useState<'all' | 'submitted' | 'under_review' | 'approved' | 'rejected'>('all') + const [quickFilter, setQuickFilter] = React.useState<string>('all') // 다이얼로그 상태들 const [addDocumentOpen, setAddDocumentOpen] = React.useState(false) @@ -115,44 +115,88 @@ export function DocumentStagesTable({ [expandedRows, projectType, currentDomain] ) - // 문서 상태별 통계 계산 + // 문서 상태별 통계 계산 (동적) - buyerSystemStatus 기준 const stats = React.useMemo(() => { const totalDocs = data?.length || 0 - const submitted = data?.filter(doc => doc.status === 'SUBMITTED')?.length || 0 - const underReview = data?.filter(doc => doc.status === 'UNDER_REVIEW')?.length || 0 - const approved = data?.filter(doc => doc.status === 'APPROVED')?.length || 0 - const rejected = data?.filter(doc => doc.status === 'REJECTED')?.length || 0 - const notSubmitted = data?.filter(doc => - !doc.status || !['SUBMITTED', 'UNDER_REVIEW', 'APPROVED', 'REJECTED'].includes(doc.status) - )?.length || 0 + + // 모든 고유한 buyerSystemStatus 값 추출 + const statusCounts = new Map<string, number>() + data?.forEach(doc => { + // buyerSystemStatus가 null이면 'beforeSync'로 처리 + const status = doc.buyerSystemStatus || 'beforeSync' + statusCounts.set(status, (statusCounts.get(status) || 0) + 1) + }) return { total: totalDocs, - submitted, - underReview, - approved, - rejected, - notSubmitted, - approvalRate: totalDocs > 0 - ? Math.round((approved / totalDocs) * 100) - : 0 + statusCounts, } }, [data]) - // 빠른 필터링 + // 상태별 메타 정보 (아이콘, 색상, 레이블) - buyerSystemStatus 기준 + const getStatusMeta = (status: string) => { + const metaMap: Record<string, { + icon: React.ElementType + color: string + textColor: string + label: string + description: string + }> = { + 'beforeSync': { + icon: FileText, + color: 'border-gray-200 dark:border-gray-800', + textColor: 'text-gray-600 dark:text-gray-400', + label: 'Before Sync', + description: '동기화 전' + }, + '생성요청': { + icon: Send, + color: 'border-blue-200 dark:border-blue-800', + textColor: 'text-blue-600 dark:text-blue-400', + label: '생성요청', + description: 'SHI에 생성 요청됨' + }, + '검토중': { + icon: Search, + color: 'border-orange-200 dark:border-orange-800', + textColor: 'text-orange-600 dark:text-orange-400', + label: '검토중', + description: 'SHI 검토 진행중' + }, + '승인(DC)': { + icon: CheckCircle2, + color: 'border-green-200 dark:border-green-800', + textColor: 'text-green-600 dark:text-green-400', + label: '승인(DC)', + description: 'SHI 승인 완료' + }, + '반려': { + icon: XCircle, + color: 'border-red-200 dark:border-red-800', + textColor: 'text-red-600 dark:text-red-400', + label: '반려', + description: '재작업 필요' + } + } + + return metaMap[status] || { + icon: FileText, + color: 'border-gray-200 dark:border-gray-800', + textColor: 'text-gray-600 dark:text-gray-400', + label: status, + description: status + } + } + + // 빠른 필터링 - buyerSystemStatus 기준 const filteredData = React.useMemo(() => { - switch (quickFilter) { - case 'submitted': - return data.filter(doc => doc.status === 'SUBMITTED') - case 'under_review': - return data.filter(doc => doc.status === 'UNDER_REVIEW') - case 'approved': - return data.filter(doc => doc.status === 'APPROVED') - case 'rejected': - return data.filter(doc => doc.status === 'REJECTED') - default: - return data + if (quickFilter === 'all') { + return data } + return data.filter(doc => { + const status = doc.buyerSystemStatus || 'beforeSync' + return status === quickFilter + }) }, [data, quickFilter]) // 핸들러 함수들 @@ -231,112 +275,60 @@ export function DocumentStagesTable({ return ( <div className="space-y-6"> - {/* 문서 상태 대시보드 */} + {/* 문서 상태 대시보드 - 동적 생성 */} <div className="grid grid-cols-2 md:grid-cols-4 gap-4"> - {/* 전체 문서 */} - <Card className="cursor-pointer hover:shadow-md transition-shadow" onClick={() => setQuickFilter('all')}> + {/* 전체 문서 카드 (항상 첫 번째) */} + <Card + className={cn( + "cursor-pointer transition-all", + quickFilter === 'all' + ? "shadow-lg border-primary ring-2 ring-primary ring-offset-2" + : "hover:shadow-md" + )} + onClick={() => setQuickFilter('all')} + > <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2"> <CardTitle className="text-sm font-medium">Total Documents</CardTitle> <FileText className="h-4 w-4 text-muted-foreground" /> </CardHeader> <CardContent> <div className="text-2xl font-bold">{stats.total}</div> - <p className="text-xs text-muted-foreground"> - 전체 등록 문서 - </p> - </CardContent> - </Card> - - {/* 제출됨 */} - <Card className="cursor-pointer hover:shadow-md transition-shadow" onClick={() => setQuickFilter('submitted')}> - <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2"> - <CardTitle className="text-sm font-medium">Submitted</CardTitle> - <Send className="h-4 w-4 text-blue-500" /> - </CardHeader> - <CardContent> - <div className="text-2xl font-bold text-blue-600 dark:text-blue-400">{stats.submitted}</div> - <p className="text-xs text-muted-foreground">제출 대기중</p> - </CardContent> - </Card> - - {/* 검토중 */} - {/* <Card className="cursor-pointer hover:shadow-md transition-shadow" onClick={() => setQuickFilter('under_review')}> - <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2"> - <CardTitle className="text-sm font-medium">Under Review</CardTitle> - <Search className="h-4 w-4 text-orange-500" /> - </CardHeader> - <CardContent> - <div className="text-2xl font-bold text-orange-600 dark:text-orange-400">{stats.underReview}</div> - <p className="text-xs text-muted-foreground">검토 진행중</p> - </CardContent> - </Card> */} - - {/* 승인됨 */} - <Card className="cursor-pointer hover:shadow-md transition-shadow" onClick={() => setQuickFilter('approved')}> - <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2"> - <CardTitle className="text-sm font-medium">Approved</CardTitle> - <CheckCircle2 className="h-4 w-4 text-green-500" /> - </CardHeader> - <CardContent> - <div className="text-2xl font-bold text-green-600 dark:text-green-400">{stats.approved}</div> - <p className="text-xs text-muted-foreground">승인 완료 ({stats.approvalRate}%)</p> + <p className="text-xs text-muted-foreground">전체 등록 문서</p> </CardContent> </Card> - <Card className="cursor-pointer hover:shadow-md transition-shadow border-red-200 dark:border-red-800" - onClick={() => setQuickFilter('rejected')}> - <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2"> - <CardTitle className="text-sm font-medium">Rejected</CardTitle> - <XCircle className="h-4 w-4 text-red-500" /> - </CardHeader> - <CardContent> - <div className="text-2xl font-bold text-red-600 dark:text-red-400">{stats.rejected}</div> - <p className="text-xs text-muted-foreground">재작업 필요</p> - </CardContent> - </Card> - </div> - - {/* 빠른 필터 뱃지 */} - <div className="flex gap-2 overflow-x-auto pb-2"> - <Badge - variant={quickFilter === 'all' ? 'default' : 'outline'} - className="cursor-pointer hover:bg-primary hover:text-primary-foreground whitespace-nowrap" - onClick={() => setQuickFilter('all')} - > - 전체 ({stats.total}) - </Badge> - <Badge - variant={quickFilter === 'submitted' ? 'default' : 'outline'} - className="cursor-pointer hover:bg-blue-500 hover:text-white dark:hover:bg-blue-600 whitespace-nowrap" - onClick={() => setQuickFilter('submitted')} - > - <Send className="w-3 h-3 mr-1" /> - 제출됨 ({stats.submitted}) - </Badge> - <Badge - variant={quickFilter === 'under_review' ? 'default' : 'outline'} - className="cursor-pointer hover:bg-orange-500 hover:text-white dark:hover:bg-orange-600 whitespace-nowrap" - onClick={() => setQuickFilter('under_review')} - > - <Search className="w-3 h-3 mr-1" /> - 검토중 ({stats.underReview}) - </Badge> - <Badge - variant={quickFilter === 'approved' ? 'success' : 'outline'} - className="cursor-pointer hover:bg-green-500 hover:text-white dark:hover:bg-green-600 whitespace-nowrap" - onClick={() => setQuickFilter('approved')} - > - <CheckCircle2 className="w-3 h-3 mr-1" /> - 승인됨 ({stats.approved}) - </Badge> - <Badge - variant={quickFilter === 'rejected' ? 'destructive' : 'outline'} - className="cursor-pointer hover:bg-destructive hover:text-destructive-foreground whitespace-nowrap" - onClick={() => setQuickFilter('rejected')} - > - <XCircle className="w-3 h-3 mr-1" /> - 반려됨 ({stats.rejected}) - </Badge> + {/* 동적으로 생성된 상태별 카드 */} + {Array.from(stats.statusCounts.entries()) + .sort(([a], [b]) => a.localeCompare(b)) // 알파벳 순 정렬 + .map(([status, count]) => { + const meta = getStatusMeta(status) + const IconComponent = meta.icon + const isSelected = quickFilter === status + + return ( + <Card + key={status} + className={cn( + "cursor-pointer transition-all", + meta.color, + isSelected + ? "shadow-lg ring-2 ring-offset-2" + : "hover:shadow-md" + )} + onClick={() => setQuickFilter(status)} + > + <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2"> + <CardTitle className="text-sm font-medium">{meta.label}</CardTitle> + <IconComponent className={cn("h-4 w-4", meta.textColor)} /> + </CardHeader> + <CardContent> + <div className={cn("text-2xl font-bold", meta.textColor)}>{count}</div> + <p className="text-xs text-muted-foreground">{meta.description}</p> + </CardContent> + </Card> + ) + }) + } </div> {/* 메인 테이블 */} diff --git a/lib/vendor-document-list/plant/excel-import-stage.tsx b/lib/vendor-document-list/plant/excel-import-stage.tsx index 8dc85c51..53e12eeb 100644 --- a/lib/vendor-document-list/plant/excel-import-stage.tsx +++ b/lib/vendor-document-list/plant/excel-import-stage.tsx @@ -326,10 +326,10 @@ function FileFormatGuide({ projectType }: { projectType: "ship" | "plant" }) { <strong>통합 Documents 시트:</strong> </p> <ul className="ml-4 list-disc"> - <li>Document Number* (문서번호)</li> + <li>SHI Document Number* (SHI 문서번호)</li> <li>Document Name* (문서명)</li> <li>Document Class* (문서클래스 - 드롭다운 선택)</li> - <li>Project Doc No.* (프로젝트 문서번호)</li> + <li>Company / Owner Document Number (회사/소유주 문서번호)</li> <li>각 Stage Name 컬럼 (계획날짜 입력: YYYY-MM-DD)</li> </ul> <p className="mt-2 text-green-600 dark:text-green-400"> @@ -440,10 +440,10 @@ async function createImportTemplate(projectType: "ship" | "plant", contractId: n const worksheet = workbook.addWorksheet("Documents") const headers = [ - "Document Number*", + "SHI Document Number*", "Document Name*", "Document Class*", - ...(projectType === "plant" ? ["Project Doc No.*"] : []), + ...(projectType === "plant" ? ["Company / Owner Document Number"] : []), ...allStageNames, ] const headerRow = worksheet.addRow(headers) @@ -466,7 +466,7 @@ async function createImportTemplate(projectType: "ship" | "plant", contractId: n ] worksheet.addRow(sampleRow) - const docNumberColIndex = 1; // A: Document Number* + const docNumberColIndex = 1; // A: SHI Document Number* const docNameColIndex = 2; // B: Document Name* const docNumberColLetter = getExcelColumnName(docNumberColIndex); const docNameColLetter = getExcelColumnName(docNameColIndex); @@ -569,7 +569,7 @@ worksheet.addConditionalFormatting({ ], }); -// ===== Project Doc No.* (Plant 전용): (이미 작성하신 코드 유지) ===== +// ===== Company / Owner Document Number (Plant 전용): (이미 작성하신 코드 유지) ===== if (projectType === "plant") { const vendorDocColIndex = 4; // D const vendorDocColLetter = getExcelColumnName(vendorDocColIndex); @@ -581,7 +581,7 @@ if (projectType === "plant") { allowBlank: false, showErrorMessage: true, errorTitle: "필수 입력", - error: "Project Doc No.는 필수 항목입니다.", + error: "Company / Owner Document Number는 필수 항목입니다.", }); worksheet.addConditionalFormatting({ @@ -608,7 +608,7 @@ if (projectType === "plant") { } if (projectType === "plant") { - const vendorDocColIndex = 4; // Document Number, Name, Class 다음이 Project Doc No.* + const vendorDocColIndex = 4; // Document Number, Name, Class 다음이 Company / Owner Document Number const vendorDocColLetter = getExcelColumnName(vendorDocColIndex); // 공백 불가: 글자수 > 0 @@ -619,7 +619,7 @@ if (projectType === "plant") { allowBlank: false, showErrorMessage: true, errorTitle: "필수 입력", - error: "Project Doc No.는 필수 항목입니다.", + error: "Company / Owner Document Number는 필수 항목입니다.", }); // UX: 비어있으면 빨간 배경으로 표시 (조건부 서식) @@ -673,10 +673,10 @@ if (projectType === "plant") { ["📋 통합 문서 임포트 가이드"], [""], ["1. 하나의 시트에서 모든 정보 관리"], - [" • Document Number*: 고유한 문서 번호"], + [" • SHI Document Number*: 고유한 문서 번호"], [" • Document Name*: 문서명"], [" • Document Class*: 드롭다운에서 선택"], - ...(projectType === "plant" ? [[" • Project Doc No.: 벤더 문서 번호"]] : []), + ...(projectType === "plant" ? [[" • Company / Owner Document Number: 벤더 문서 번호"]] : []), [" • Stage 컬럼들: 각 스테이지의 계획 날짜 (YYYY-MM-DD)"], [""], ["2. 스마트 검증 기능"], @@ -820,7 +820,7 @@ async function parseDocumentsWithStages( const docNumberIdx = headers.findIndex((h) => h.includes("Document Number")) const docNameIdx = headers.findIndex((h) => h.includes("Document Name")) const docClassIdx = headers.findIndex((h) => h.includes("Document Class")) - const vendorDocNoIdx = projectType === "plant" ? headers.findIndex((h) => h.includes("Project Doc No")) : -1 + const vendorDocNoIdx = projectType === "plant" ? headers.findIndex((h) => h.includes("Company / Owner Document Number")) : -1 if (docNumberIdx === -1 || docNameIdx === -1 || docClassIdx === -1) { errors.push("필수 헤더가 누락되었습니다") @@ -852,7 +852,7 @@ async function parseDocumentsWithStages( return } if (projectType === "plant" && !vendorDocNo) { - errors.push(`행 ${rowNumber}: Project Doc No.가 없습니다`) + errors.push(`행 ${rowNumber}: Company / Owner Document Number가 없습니다`) return } if (seenDocNumbers.has(docNumber)) { diff --git a/lib/vendor-document-list/plant/shi-buyer-system-api.ts b/lib/vendor-document-list/plant/shi-buyer-system-api.ts index b23bd269..21f28fac 100644 --- a/lib/vendor-document-list/plant/shi-buyer-system-api.ts +++ b/lib/vendor-document-list/plant/shi-buyer-system-api.ts @@ -281,10 +281,10 @@ export class ShiBuyerSystemAPI { return `\\\\60.100.91.61\\SBox\\${projNo}\\${cpyCode}\\${timestamp}\\${fileName}`; } - async sendToSHI(contractId: number) { + async sendToSHI(contractId: number, selectedDocumentIds?: number[]) { try { // 1. 전송할 문서 조회 - const documents = await this.getDocumentsToSend(contractId) + const documents = await this.getDocumentsToSend(contractId, selectedDocumentIds) if (documents.length === 0) { return { success: false, message: "전송할 문서가 없습니다." } @@ -317,8 +317,24 @@ export class ShiBuyerSystemAPI { } } - private async getDocumentsToSend(contractId: number): Promise<DocumentWithStages[]> { - // 1. 먼저 문서 목록을 가져옴 + private async getDocumentsToSend(contractId: number, selectedDocumentIds?: number[]): Promise<DocumentWithStages[]> { + // 1. 기본 WHERE 조건 구성 + const whereConditions = [ + eq(stageDocuments.contractId, contractId), + eq(stageDocuments.status, 'ACTIVE'), + // 승인되지 않은 문서만 (null이거나 "승인(DC)"가 아닌 것) + or( + isNull(stageDocuments.buyerSystemStatus), + ne(stageDocuments.buyerSystemStatus, "승인(DC)") + ) + ] + + // 2. 선택된 문서 ID가 있으면 추가 필터링 + if (selectedDocumentIds && selectedDocumentIds.length > 0) { + whereConditions.push(inArray(stageDocuments.id, selectedDocumentIds)) + } + + // 3. 문서 목록을 가져옴 const documents = await db .select({ documentId: stageDocuments.id, @@ -331,19 +347,10 @@ export class ShiBuyerSystemAPI { projectCode: sql<string>`(SELECT code FROM projects WHERE id = ${stageDocuments.projectId})`, vendorCode: sql<string>`(SELECT vendor_code FROM vendors WHERE id = ${stageDocuments.vendorId})`, vendorName: sql<string>`(SELECT vendor_name FROM vendors WHERE id = ${stageDocuments.vendorId})`, + updatedAt: stageDocuments.updatedAt, }) .from(stageDocuments) - .where( - and( - eq(stageDocuments.contractId, contractId), - eq(stageDocuments.status, 'ACTIVE'), - // ne는 null을 포함하지 않음 - or( - isNull(stageDocuments.buyerSystemStatus), - ne(stageDocuments.buyerSystemStatus, "승인(DC)") - ) - ) - ) + .where(and(...whereConditions)) // 2. 각 문서에 대해 스테이지 정보를 별도로 조회 const documentsWithStages = await Promise.all( |
