summaryrefslogtreecommitdiff
path: root/lib/vendor-document-list/plant
diff options
context:
space:
mode:
authorjoonhoekim <26rote@gmail.com>2025-11-02 14:03:34 +0900
committerjoonhoekim <26rote@gmail.com>2025-11-02 14:03:34 +0900
commitde4c6593f0cc91c6e0c1a4e2bf9581f11f4f5c98 (patch)
tree8b3d88637309ac9fb67f79606d834364d784105b /lib/vendor-document-list/plant
parentfd5ff7a9eaea4baeacc3f4bec8254925d63bf255 (diff)
(김준회) SWP 리스트 관리 파트 오류 수정 및 요구사항 반영, 동적 상태 리스트 필터링 변경, null은 동기화 전(전송 전)으로 간주, 선택된 것만 보내도록 변경
Diffstat (limited to 'lib/vendor-document-list/plant')
-rw-r--r--lib/vendor-document-list/plant/document-stage-dialogs.tsx36
-rw-r--r--lib/vendor-document-list/plant/document-stage-toolbar.tsx14
-rw-r--r--lib/vendor-document-list/plant/document-stages-columns.tsx3
-rw-r--r--lib/vendor-document-list/plant/document-stages-service.ts4
-rw-r--r--lib/vendor-document-list/plant/document-stages-table.tsx244
-rw-r--r--lib/vendor-document-list/plant/excel-import-stage.tsx26
-rw-r--r--lib/vendor-document-list/plant/shi-buyer-system-api.ts37
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(