summaryrefslogtreecommitdiff
path: root/lib/swp
diff options
context:
space:
mode:
Diffstat (limited to 'lib/swp')
-rw-r--r--lib/swp/api-client.ts1
-rw-r--r--lib/swp/swp-upload-server-actions.ts63
-rw-r--r--lib/swp/table/swp-table-toolbar.tsx90
-rw-r--r--lib/swp/table/swp-upload-validation-dialog.tsx69
4 files changed, 165 insertions, 58 deletions
diff --git a/lib/swp/api-client.ts b/lib/swp/api-client.ts
index 1befa217..39ce02b0 100644
--- a/lib/swp/api-client.ts
+++ b/lib/swp/api-client.ts
@@ -100,6 +100,7 @@ export interface SwpDocumentApiResponse {
// 선택적 필드 (null 가능)
DOC_GB: string | null;
DOC_TYPE: string | null;
+ DOC_CLS: string | null; // Document Class (A, B, C 등)
OWN_DOC_NO: string | null;
SHI_DOC_NO: string | null;
PROJ_NM: string | null;
diff --git a/lib/swp/swp-upload-server-actions.ts b/lib/swp/swp-upload-server-actions.ts
new file mode 100644
index 00000000..2c07cf77
--- /dev/null
+++ b/lib/swp/swp-upload-server-actions.ts
@@ -0,0 +1,63 @@
+"use server";
+
+/**
+ * SWP 파일 업로드 관련 서버 액션
+ * 클라이언트 컴포넌트에서 호출할 수 있도록 래핑
+ */
+
+import { getProjectIdByCode } from "./project-utils";
+import { getDocumentClassInfoForSwpUpload } from "@/lib/vendor-document-list/plant/document-stages-service";
+
+/**
+ * 프로젝트 코드로 Document Class 정보 조회
+ * @param projectCode 프로젝트 코드 (PROJ_NO)
+ * @returns vendorDocNumber → Document Class 매핑 + Document Class → 허용 Stage 목록
+ */
+export async function getDocumentClassInfoByProjectCode(projectCode: string): Promise<{
+ success: boolean;
+ vendorDocNumberToDocClassMap: Record<string, string>;
+ documentClassStages: Record<string, string[]>;
+ error?: string;
+}> {
+ try {
+ console.log(`[getDocumentClassInfoByProjectCode] 프로젝트 ${projectCode} 정보 조회 시작`);
+
+ // 1. 프로젝트 코드 → 프로젝트 ID 변환
+ const projectId = await getProjectIdByCode(projectCode);
+
+ if (!projectId) {
+ console.warn(`[getDocumentClassInfoByProjectCode] 프로젝트 ID를 찾을 수 없음: ${projectCode}`);
+ return {
+ success: false,
+ vendorDocNumberToDocClassMap: {},
+ documentClassStages: {},
+ error: `프로젝트를 찾을 수 없습니다: ${projectCode}`,
+ };
+ }
+
+ console.log(`[getDocumentClassInfoByProjectCode] 프로젝트 ID: ${projectId}`);
+
+ // 2. EVCP DB에서 문서 정보 조회
+ const classInfo = await getDocumentClassInfoForSwpUpload(projectId);
+
+ console.log(`[getDocumentClassInfoByProjectCode] 조회 완료:`, {
+ vendorDocNumbers: Object.keys(classInfo.vendorDocNumberToDocClassMap).length,
+ documentClasses: Object.keys(classInfo.documentClassStages).length,
+ });
+
+ return {
+ success: true,
+ vendorDocNumberToDocClassMap: classInfo.vendorDocNumberToDocClassMap,
+ documentClassStages: classInfo.documentClassStages,
+ };
+ } catch (error) {
+ console.error("[getDocumentClassInfoByProjectCode] 오류:", error);
+ return {
+ success: false,
+ vendorDocNumberToDocClassMap: {},
+ documentClassStages: {},
+ error: error instanceof Error ? error.message : "문서 정보 조회 실패",
+ };
+ }
+}
+
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 {