summaryrefslogtreecommitdiff
path: root/lib/swp/table
diff options
context:
space:
mode:
authorjoonhoekim <26rote@gmail.com>2025-11-13 12:21:18 +0900
committerjoonhoekim <26rote@gmail.com>2025-11-13 12:21:18 +0900
commitd113d1732f7c6356af6619dfaff98604fb68e5ad (patch)
treeea77e9c4a5da0123e9aa5e1482d9f1c93afe76ba /lib/swp/table
parenta4ceade24d28af0bde985bf750017efc02f053ff (diff)
(김준회) SWP 문서리스트: OWN_DOC_NO로 스테이지 검증 추가
Diffstat (limited to 'lib/swp/table')
-rw-r--r--lib/swp/table/swp-table-toolbar.tsx90
-rw-r--r--lib/swp/table/swp-upload-validation-dialog.tsx69
2 files changed, 101 insertions, 58 deletions
diff --git a/lib/swp/table/swp-table-toolbar.tsx b/lib/swp/table/swp-table-toolbar.tsx
index 013b4a13..ea5ee729 100644
--- a/lib/swp/table/swp-table-toolbar.tsx
+++ b/lib/swp/table/swp-table-toolbar.tsx
@@ -19,7 +19,7 @@ import {
validateFileName
} from "./swp-upload-validation-dialog";
import { SwpUploadedFilesDialog } from "./swp-uploaded-files-dialog";
-import { getProjectDocumentClassStages } from "@/lib/docu-list-rule/document-class/service";
+import { getDocumentClassInfoByProjectCode } from "@/lib/swp/swp-upload-server-actions";
import type { DocumentListItem } from "@/lib/swp/document-service";
interface SwpTableFilters {
@@ -40,7 +40,7 @@ interface SwpTableToolbarProps {
vendorCode?: string;
droppedFiles?: File[];
onFilesProcessed?: () => void;
- documents?: DocumentListItem[]; // 업로드 권한 검증 + DOC_TYPE 확인용 문서 목록
+ documents?: DocumentListItem[]; // 업로드 권한 검증 + DOC_CLS (Document Class) 확인용 문서 목록
userId?: string; // 파일 취소 시 필요
}
@@ -82,31 +82,25 @@ export function SwpTableToolbar({
}>>([]);
const [showValidationDialog, setShowValidationDialog] = useState(false);
- // Document Class-Stage 매핑 (프로젝트별)
+ // EVCP DB에서 조회한 문서 정보 (vendorDocNumber → Document Class 매핑)
+ const [vendorDocNumberToDocClassMap, setVendorDocNumberToDocClassMap] = useState<Record<string, string>>({});
+ // Document Class별 허용 Stage 목록
const [documentClassStages, setDocumentClassStages] = useState<Record<string, string[]>>({});
- const [isLoadingDocClassStages, setIsLoadingDocClassStages] = useState(false);
/**
* 업로드 가능한 문서번호 목록 추출 (OWN_DOC_NO 기준)
+ * SWP API의 OWN_DOC_NO가 EVCP DB의 vendorDocNumber와 매핑되는지 확인
*/
const availableDocNos = useMemo(() => {
return documents
.map(doc => doc.OWN_DOC_NO)
- .filter((ownDocNo): ownDocNo is string => ownDocNo !== null && ownDocNo !== undefined);
- }, [documents]);
-
- /**
- * 문서번호 → DOC_TYPE 매핑 (Stage 검증용)
- */
- const docNoToDocTypeMap = useMemo(() => {
- const map: Record<string, string> = {};
- for (const doc of documents) {
- if (doc.OWN_DOC_NO && doc.DOC_TYPE) {
- map[doc.OWN_DOC_NO] = doc.DOC_TYPE;
- }
- }
- return map;
- }, [documents]);
+ .filter((ownDocNo): ownDocNo is string => {
+ // OWN_DOC_NO가 있고, EVCP DB에 등록된 문서인지 확인
+ return ownDocNo !== null &&
+ ownDocNo !== undefined &&
+ vendorDocNumberToDocClassMap[ownDocNo] !== undefined;
+ });
+ }, [documents, vendorDocNumberToDocClassMap]);
/**
* 벤더 모드 여부 (벤더 코드가 있으면 벤더 모드)
@@ -114,42 +108,68 @@ export function SwpTableToolbar({
const isVendorMode = !!vendorCode;
/**
- * 프로젝트 변경 시 Document Class-Stage 매핑 로드
+ * 프로젝트 변경 시 EVCP DB에서 문서 정보 로드
+ * - vendorDocNumber → docClass 매핑
+ * - Document Class별 허용 Stage 목록
*/
useEffect(() => {
if (!projNo) {
+ setVendorDocNumberToDocClassMap({});
setDocumentClassStages({});
return;
}
let isCancelled = false;
- const loadDocumentClassStages = async () => {
+ const loadDocumentClassInfo = async () => {
try {
- setIsLoadingDocClassStages(true);
- const stages = await getProjectDocumentClassStages(projNo);
+ console.log(`[SwpTableToolbar] 프로젝트 ${projNo} 문서 정보 로드 시작`);
+
+ // 서버 액션 호출
+ const result = await getDocumentClassInfoByProjectCode(projNo);
+
if (!isCancelled) {
- setDocumentClassStages(stages);
- console.log(`[SwpTableToolbar] Document Class-Stage 매핑 로드 완료:`, stages);
+ if (result.success) {
+ setVendorDocNumberToDocClassMap(result.vendorDocNumberToDocClassMap);
+ setDocumentClassStages(result.documentClassStages);
+
+ console.log(`[SwpTableToolbar] 문서 정보 로드 완료:`, {
+ vendorDocNumbers: Object.keys(result.vendorDocNumberToDocClassMap).length,
+ documentClassStages: result.documentClassStages,
+ });
+ } else {
+ console.warn(`[SwpTableToolbar] 문서 정보 로드 실패:`, result.error);
+ setVendorDocNumberToDocClassMap({});
+ setDocumentClassStages({});
+
+ toast({
+ variant: "destructive",
+ title: "문서 정보 로드 실패",
+ description: result.error || "문서 정보를 가져올 수 없습니다.",
+ });
+ }
}
} catch (error) {
if (!isCancelled) {
- console.error('[SwpTableToolbar] Document Class-Stage 매핑 로드 실패:', error);
+ console.error('[SwpTableToolbar] 문서 정보 로드 실패:', error);
+ setVendorDocNumberToDocClassMap({});
setDocumentClassStages({});
- }
- } finally {
- if (!isCancelled) {
- setIsLoadingDocClassStages(false);
+
+ toast({
+ variant: "destructive",
+ title: "문서 정보 로드 실패",
+ description: "문서 정보를 가져올 수 없습니다. 페이지를 새로고침해주세요.",
+ });
}
}
};
- loadDocumentClassStages();
+ loadDocumentClassInfo();
return () => {
isCancelled = true;
};
- }, [projNo]);
+ }, [projNo, toast]);
/**
* 드롭된 파일 처리 - useEffect로 감지하여 자동 검증
@@ -183,7 +203,7 @@ export function SwpTableToolbar({
file.name,
availableDocNos,
isVendorMode,
- docNoToDocTypeMap,
+ vendorDocNumberToDocClassMap,
documentClassStages
);
return {
@@ -198,7 +218,7 @@ export function SwpTableToolbar({
setShowValidationDialog(true);
onFilesProcessed?.();
}
- }, [droppedFiles, projNo, vendorCode, toast, onFilesProcessed, availableDocNos, isVendorMode, docNoToDocTypeMap, documentClassStages]);
+ }, [droppedFiles, projNo, vendorCode, toast, onFilesProcessed, availableDocNos, isVendorMode, vendorDocNumberToDocClassMap, documentClassStages]);
/**
* 파일 업로드 핸들러
@@ -240,7 +260,7 @@ export function SwpTableToolbar({
file.name,
availableDocNos,
isVendorMode,
- docNoToDocTypeMap,
+ vendorDocNumberToDocClassMap,
documentClassStages
);
return {
diff --git a/lib/swp/table/swp-upload-validation-dialog.tsx b/lib/swp/table/swp-upload-validation-dialog.tsx
index ef48f0c6..a7cdb7c5 100644
--- a/lib/swp/table/swp-upload-validation-dialog.tsx
+++ b/lib/swp/table/swp-upload-validation-dialog.tsx
@@ -43,14 +43,14 @@ interface SwpUploadValidationDialogProps {
* @param fileName 검증할 파일명
* @param availableDocNos 업로드 가능한 문서번호 목록 (선택)
* @param isVendorMode 벤더 모드인지 여부 (true인 경우 문서번호 검증 필수)
- * @param docNoToDocTypeMap 문서번호 → DOC_TYPE 매핑 (Stage 검증용)
+ * @param docNoToDocClsMap 문서번호 → DOC_CLS (Document Class) 매핑 (Stage 검증용)
* @param documentClassStages Document Class → 허용 Stage 목록 매핑
*/
export function validateFileName(
fileName: string,
availableDocNos?: string[],
isVendorMode?: boolean,
- docNoToDocTypeMap?: Record<string, string>,
+ docNoToDocClsMap?: Record<string, string>,
documentClassStages?: Record<string, string[]>
): {
valid: boolean;
@@ -117,10 +117,12 @@ export function validateFileName(
};
}
+ // trim된 값 미리 준비 (중복 제거)
+ const trimmedDocNo = ownDocNo.trim();
+ const trimmedStage = stage.trim();
+
// 문서번호 검증 (벤더 모드에서는 필수)
if (isVendorMode) {
- const trimmedDocNo = ownDocNo.trim();
-
// 벤더 모드에서 문서 목록이 비어있으면 에러
if (!availableDocNos || availableDocNos.length === 0) {
return {
@@ -138,28 +140,49 @@ export function validateFileName(
}
}
- // Stage 검증 (DOC_TYPE별 허용 Stage 확인)
- const trimmedDocNo = ownDocNo.trim();
- const trimmedStage = stage.trim();
+ // Stage 검증 (Document Class별 허용 Stage 확인)
+ // EVCP DB에서 vendorDocNumber로 Document Class를 조회하고,
+ // 해당 Document Class의 허용 Stage 목록과 비교
+
+ if (docNoToDocClsMap && documentClassStages) {
+ const docCls = docNoToDocClsMap[trimmedDocNo];
+ console.log(`[validateFileName] 문서 '${trimmedDocNo}' → Document Class: '${docCls || "null"}'`);
+
+ if (!docCls) {
+ // 문서가 EVCP DB에 등록되지 않음
+ return {
+ valid: false,
+ error: `문서번호 '${trimmedDocNo}'는 문서 리스트에 등록되지 않았습니다. 먼저 문서 리스트를 제출해주세요.`,
+ };
+ }
- if (docNoToDocTypeMap && documentClassStages) {
- const docType = docNoToDocTypeMap[trimmedDocNo];
+ const allowedStages = documentClassStages[docCls];
+ console.log(`[validateFileName] Document Class '${docCls}' → 허용 Stage:`, allowedStages);
- if (docType) {
- const allowedStages = documentClassStages[docType];
-
- if (allowedStages && allowedStages.length > 0) {
- // 허용된 Stage 목록이 있는 경우에만 검증
- if (!allowedStages.includes(trimmedStage)) {
- return {
- valid: false,
- error: `문서 '${trimmedDocNo}'의 Document Class '${docType}'에서 Stage '${trimmedStage}'는 허용되지 않습니다. 허용된 Stage: ${allowedStages.join(", ")}`,
- };
- }
- }
- // allowedStages가 비어있으면 Stage 검증을 스킵 (설정되지 않은 경우)
+ if (!allowedStages || allowedStages.length === 0) {
+ // Document Class에 Stage가 설정되지 않음
+ return {
+ valid: false,
+ error: `문서 '${trimmedDocNo}'의 Document Class '${docCls}'에 Stage가 설정되지 않았습니다. 관리자에게 문의하세요.`,
+ };
+ }
+
+ console.log(`[validateFileName] Stage 검증: '${trimmedStage}' in [${allowedStages.join(", ")}]`);
+ if (!allowedStages.includes(trimmedStage)) {
+ return {
+ valid: false,
+ error: `문서 '${trimmedDocNo}'의 Document Class '${docCls}'에서 Stage '${trimmedStage}'는 허용되지 않습니다. 허용된 Stage: ${allowedStages.join(", ")}`,
+ };
}
- // docType이 없으면 Stage 검증을 스킵 (문서 정보가 없는 경우)
+
+ console.log(`[validateFileName] Stage 검증 통과: '${trimmedStage}'`);
+ } else {
+ // 검증 정보가 로드되지 않음
+ console.log(`[validateFileName] 검증 정보가 없음 → 업로드 차단`);
+ return {
+ valid: false,
+ error: "문서 정보를 가져올 수 없습니다. 페이지를 새로고침하거나 프로젝트를 다시 선택해주세요.",
+ };
}
return {