diff options
| author | joonhoekim <26rote@gmail.com> | 2025-11-26 18:09:18 +0900 |
|---|---|---|
| committer | joonhoekim <26rote@gmail.com> | 2025-11-26 18:09:18 +0900 |
| commit | 8547034e6d82e4d1184f35af2dbff67180d89dc8 (patch) | |
| tree | 2e1835040f39adc7d0c410a108ebb558f9971a8b /lib/dolce-v2/actions.ts | |
| parent | 3131dce1f0c90d960f53bd384045b41023064bc4 (diff) | |
(김준회) dolce: 동기화 기능 추가, 로컬 다운로드, 삭제 추가, 동기화 dialog 개선 등
Diffstat (limited to 'lib/dolce-v2/actions.ts')
| -rw-r--r-- | lib/dolce-v2/actions.ts | 605 |
1 files changed, 605 insertions, 0 deletions
diff --git a/lib/dolce-v2/actions.ts b/lib/dolce-v2/actions.ts new file mode 100644 index 00000000..020da94e --- /dev/null +++ b/lib/dolce-v2/actions.ts @@ -0,0 +1,605 @@ +"use server"; + +import { + fetchDwgReceiptList as fetchDwgOriginal, + fetchDetailDwgReceiptList as fetchDetailOriginal, + fetchFileInfoList as fetchFileOriginal, + getVendorSessionInfo as getVendorSessionInfoOriginal, + fetchVendorProjects as fetchVendorProjectsOriginal, + downloadDolceFile as downloadDolceFileOriginal, + // 타입들 재사용 + DwgReceiptItem, GttDwgReceiptItem, UnifiedDwgReceiptItem, DetailDwgReceiptItem, FileInfoItem, + DetailDwgEditRequest, B4MappingSaveItem +} from "@/lib/dolce/actions"; +import db from "@/db/db"; +import { dolceSyncList } from "@/db/schema/dolce/dolce"; +import { eq, and, desc } from "drizzle-orm"; +import { saveToLocalBuffer, syncItem, getLocalFile, deleteLocalItem, deleteLocalFileFromItem } from "./sync-service"; + +// 타입 재-export +export type { + DwgReceiptItem, GttDwgReceiptItem, UnifiedDwgReceiptItem, + DetailDwgReceiptItem, FileInfoItem, DetailDwgEditRequest +}; + +// Re-export 함수들을 명시적인 async 함수로 래핑 +export async function getVendorSessionInfo() { + return getVendorSessionInfoOriginal(); +} + +export async function fetchVendorProjects() { + return fetchVendorProjectsOriginal(); +} + +export async function downloadDolceFile(params: { + fileId: string; + userId: string; + fileName: string; +}) { + return downloadDolceFileOriginal(params); +} + +// ============================================================================ +// 조회 액션 (로컬 데이터 병합) +// ============================================================================ + +/** + * 1. 도면 리스트 조회 (변경 없음 - 도면 리스트 자체는 외부 시스템 기준) + */ +export async function fetchDwgReceiptList(params: { + project: string; + drawingKind: string; + drawingMoveGbn?: string; + drawingNo?: string; + drawingName?: string; + drawingVendor?: string; + discipline?: string; +}) { + return fetchDwgOriginal(params); +} + +/** + * 2. 상세도면 리스트 조회 (로컬 임시 저장 건 병합) + */ +export async function fetchDetailDwgReceiptListV2(params: { + project: string; + drawingNo: string; + discipline: string; + drawingKind: string; + userId?: string; +}): Promise<DetailDwgReceiptItem[]> { + // 1. 외부 API 조회 + const originalList = await fetchDetailOriginal(params); + + // 2. 로컬 DB 조회 (미동기화된 ADD_DETAIL 건) + // projectNo와 drawingNo로 필터링 + const localItems = await db.query.dolceSyncList.findMany({ + where: and( + eq(dolceSyncList.projectNo, params.project), + eq(dolceSyncList.drawingNo, params.drawingNo), + eq(dolceSyncList.type, "ADD_DETAIL"), + eq(dolceSyncList.isSynced, false) + ), + orderBy: [desc(dolceSyncList.createdAt)], + }); + + // 3. 로컬 데이터를 DetailDwgReceiptItem 형식으로 변환하여 추가 + const mergedList = [...originalList]; + + for (const item of localItems) { + const payload = item.payload as { meta: { dwgList: DetailDwgEditRequest[] } }; + const dwgRequest = payload.meta.dwgList[0]; // 보통 1개씩 요청함 + + if (dwgRequest) { + // 임시 객체 생성 + const tempItem: DetailDwgReceiptItem = { + Category: dwgRequest.Category, + CategoryENM: dwgRequest.Category, // 임시: 코드값 사용 + CategoryNM: dwgRequest.Category, // 임시 + CreateDt: item.createdAt.toISOString(), + CreateUserENM: "", + CreateUserId: item.userId, + CreateUserNM: "", // 이름은 별도 조회 필요하나 생략 + Discipline: dwgRequest.Discipline, + DrawingKind: dwgRequest.DrawingKind, + DrawingName: dwgRequest.DrawingName, + DrawingNo: dwgRequest.DrawingNo, + DrawingRevNo: dwgRequest.DrawingRevNo || "", + DrawingUsage: "TEMP", // 임시 표시 + DrawingUsageENM: "Temporary Saved", + DrawingUsageNM: "임시저장", + Manager: dwgRequest.Manager, + Mode: "ADD", + OFDC_NO: "", + ProjectNo: dwgRequest.ProjectNo, + Receiver: dwgRequest.Receiver, + RegCompanyCode: dwgRequest.RegCompanyCode, + RegCompanyENM: "", + RegCompanyNM: "", + RegisterDesc: dwgRequest.RegisterDesc, + RegisterGroup: dwgRequest.RegisterGroupId, + RegisterGroupId: dwgRequest.RegisterGroupId, + RegisterId: 0, // 임시 ID + RegisterKind: dwgRequest.RegisterKind, + RegisterKindENM: dwgRequest.RegisterKind, // 임시: 코드값 사용 + RegisterKindNM: dwgRequest.RegisterKind, + RegisterSerialNo: dwgRequest.RegisterSerialNo, + SHINote: null, + Status: "EVCP Saved", // 작성중 + UploadId: dwgRequest.UploadId, + }; + + // 리스트 상단에 추가 (혹은 날짜순 정렬) + mergedList.unshift(tempItem); + } + } + + return mergedList; +} + +/** + * 3. 파일 리스트 조회 (로컬 임시 저장 건 병합) + */ +export async function fetchFileInfoListV2(uploadId: string): Promise<FileInfoItem[]> { + // 1. 외부 API 조회 + const originalList = await fetchFileOriginal(uploadId); + + // 2. 로컬 DB 조회 (이 uploadId에 대해 추가된 파일들) + // ADD_DETAIL(새 도면 생성 시 파일) 또는 ADD_FILE(기존 도면에 파일 추가) 모두 해당될 수 있음. + // upload_id 컬럼으로 조회 + const localItems = await db.query.dolceSyncList.findMany({ + where: and( + eq(dolceSyncList.uploadId, uploadId), + eq(dolceSyncList.isSynced, false) + ), + }); + + const mergedList = [...originalList]; + + for (const item of localItems) { + const payload = item.payload as { files: Array<{ originalName: string, size: number, localPath: string }> }; + + if (payload.files && payload.files.length > 0) { + payload.files.forEach((file, index) => { + const tempFile: FileInfoItem = { + CreateDt: item.createdAt.toISOString(), + CreateUserId: item.userId, + Deleted: "False", + FileDescription: "Local Temp File", + FileId: `LOCAL_${item.id}_${index}`, // 로컬 파일 식별자 + FileName: file.originalName, + FileRelativePath: file.localPath, // 로컬 경로 (다운로드 시 구분 필요) + FileSeq: String(9999 + index), // 임시 시퀀스 + FileServerId: "LOCAL", + FileSize: String(file.size), + FileTitle: null, + FileWriteDT: item.createdAt.toISOString(), + OwnerUserId: item.userId, + SourceDrmYn: "N", + SystemId: "EVCP", + TableName: "Temp", + UploadId: uploadId, + UseYn: "True" + }; + mergedList.push(tempFile); + }); + } + } + + return mergedList; +} + +// ============================================================================ +// 저장 액션 (로컬 버퍼링) +// ============================================================================ + +/** + * 4. 상세도면 추가/수정 (로컬 저장) + */ +export async function editDetailDwgReceiptV2( + formData: FormData +): Promise<{ success: boolean, syncId: string }> { + try { + const dwgListJson = formData.get("dwgList") as string; + const userId = formData.get("userId") as string; + const userNm = formData.get("userNm") as string; + const vendorCode = formData.get("vendorCode") as string; + const email = formData.get("email") as string; + + if (!dwgListJson || !userId) { + throw new Error("Required parameters are missing"); + } + + const dwgList = JSON.parse(dwgListJson) as DetailDwgEditRequest[]; + const request = dwgList[0]; // 보통 1건 처리 + const type = request.Mode === "ADD" ? "ADD_DETAIL" : "MOD_DETAIL"; + + // FormData에서 파일 추출 + const files: File[] = []; + // file_0, file_1 ... 형식으로 전송된다고 가정 + const fileCount = parseInt((formData.get("fileCount") as string) || "0"); + + for (let i = 0; i < fileCount; i++) { + const file = formData.get(`file_${i}`); + if (file instanceof File) { + files.push(file); + } + } + + const savedItem = await saveToLocalBuffer({ + type, + projectNo: request.ProjectNo, + userId, + userName: userNm, // [추가] + vendorCode: vendorCode, // [추가] + drawingNo: request.DrawingNo, + uploadId: request.UploadId, + metaData: { + dwgList, + userId, + userNm, + vendorCode, + email + }, + files + }); + + return { success: true, syncId: savedItem.id }; + + } catch (error) { + console.error("상세도면 로컬 저장 실패:", error); + throw error; + } +} + +/** + * 5. 파일 추가 (로컬 저장) + */ +export async function uploadFilesToDetailDrawingV2( + formData: FormData +): Promise<{ success: boolean, syncId?: string, error?: string }> { + try { + const uploadId = formData.get("uploadId") as string; + const userId = formData.get("userId") as string; + const fileCount = parseInt(formData.get("fileCount") as string); + const projectNo = formData.get("projectNo") as string; + const vendorCode = formData.get("vendorCode") as string; + + // [추가] 메타데이터 추출 + const drawingNo = formData.get("drawingNo") as string; + const revNo = formData.get("revNo") as string; + const drawingName = formData.get("drawingName") as string; + const discipline = formData.get("discipline") as string; + const registerKind = formData.get("registerKind") as string; + + if (!uploadId || !userId) { + throw new Error("Required parameters are missing"); + } + + const files: File[] = []; + for (let i = 0; i < fileCount; i++) { + const file = formData.get(`file_${i}`) as File; + if (file) files.push(file); + } + + const savedItem = await saveToLocalBuffer({ + type: "ADD_FILE", + projectNo: projectNo || "UNKNOWN", + userId, + vendorCode, + drawingNo: drawingNo || undefined, // [추가] DB drawingNo 컬럼 저장 + uploadId, + metaData: { + uploadId, + userId, + vendorCode, + // [추가] + drawingNo, + revNo, + drawingName, + discipline, + registerKind + }, + files + }); + + return { success: true, syncId: savedItem.id }; + } catch (error) { + console.error("파일 로컬 저장 실패:", error); + return { success: false, error: error instanceof Error ? error.message : "Unknown error" }; + } +} + +/** + * 6. 동기화 실행 액션 + */ +export async function syncDolceItem(id: string) { + return syncItem(id); +} + +/** + * 7. 미동기화 아이템 목록 조회 (내것만 - Sync 버튼 카운트용) + */ +export async function fetchPendingSyncItems(params: { + projectNo: string; + userId: string; +}): Promise<Array<{ id: string; type: string; desc: string }>> { + try { + const items = await db.query.dolceSyncList.findMany({ + where: and( + eq(dolceSyncList.projectNo, params.projectNo), + eq(dolceSyncList.userId, params.userId), + eq(dolceSyncList.isSynced, false) + ), + orderBy: [desc(dolceSyncList.createdAt)], + }); + + return items.map((item) => { + let desc = ""; + + if (item.type === "ADD_DETAIL" || item.type === "MOD_DETAIL") { + desc = `${item.type === "ADD_DETAIL" ? "Add" : "Mod"} Drawing ${item.drawingNo}`; + } else if (item.type === "ADD_FILE") { + desc = `Add Files to ${item.uploadId}`; + } else if (item.type === "B4_BULK") { + desc = `Bulk Upload ${item.drawingNo}`; + } + + return { + id: item.id, + type: item.type, + desc, + }; + }); + } catch (error) { + console.error("미동기화 목록 조회 실패:", error); + return []; + } +} + +// 상세 동기화 정보 인터페이스 +export interface PendingSyncItemDetail { + id: string; // syncId + type: string; + createdAt: Date; + userId: string; + userName: string; + + // 도면 정보 + drawingNo: string; + drawingName: string; + discipline: string; + + // 상세도면 정보 + revision: string; + registerKind: string; // 접수종류 + + // 파일 정보 + files: Array<{ + name: string; + size: number; + }>; +} + +/** + * 8. 프로젝트 전체 미동기화 아이템 목록 조회 (동기화 다이얼로그용 - 상세 정보 포함) + */ +export async function fetchProjectPendingSyncItems(params: { + projectNo: string; + currentUserId: string; + currentVendorCode: string; +}): Promise<{ + myItems: PendingSyncItemDetail[]; + otherItems: PendingSyncItemDetail[]; +}> { + try { + // 1. 내 아이템 조회 (userId 이용) + const myItemsPromise = db.query.dolceSyncList.findMany({ + where: and( + eq(dolceSyncList.projectNo, params.projectNo), + eq(dolceSyncList.userId, params.currentUserId), + eq(dolceSyncList.isSynced, false) + ), + orderBy: [desc(dolceSyncList.createdAt)], + }); + + // 2. 같은 벤더의 다른 사용자 아이템 조회 (vendorCode 이용, userId 제외) + const otherItemsPromise = db.query.dolceSyncList.findMany({ + where: and( + eq(dolceSyncList.projectNo, params.projectNo), + eq(dolceSyncList.vendorCode, params.currentVendorCode), + eq(dolceSyncList.isSynced, false) + ), + orderBy: [desc(dolceSyncList.createdAt)], + }); + + const [myDbItems, otherDbItems] = await Promise.all([myItemsPromise, otherItemsPromise]); + + // 아이템 상세 정보 파싱 헬퍼 + const parseItem = (item: typeof dolceSyncList.$inferSelect): PendingSyncItemDetail => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const payload = item.payload as any; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const files = payload.files?.map((f: any) => ({ name: f.originalName, size: f.size })) || []; + const meta = payload.meta || {}; + + let drawingNo = item.drawingNo || ""; + let drawingName = ""; + let discipline = ""; + let revision = ""; + let registerKind = ""; + + if (item.type === "ADD_DETAIL" || item.type === "MOD_DETAIL") { + const dwgRequest = meta.dwgList?.[0]; + if (dwgRequest) { + drawingNo = dwgRequest.DrawingNo; + drawingName = dwgRequest.DrawingName; + discipline = dwgRequest.Discipline; + revision = dwgRequest.DrawingRevNo || ""; + registerKind = dwgRequest.RegisterKind || ""; + } + } else if (item.type === "ADD_FILE") { + // ADD_FILE의 경우 meta에 저장된 정보 사용 + drawingNo = meta.drawingNo || item.drawingNo || ""; + drawingName = meta.drawingName || ""; + discipline = meta.discipline || ""; + revision = meta.revNo || ""; + registerKind = meta.registerKind || ""; + } else if (item.type === "B4_BULK") { + // B4_BULK의 경우 첫 번째 매핑 정보 사용 (보통 같은 도면) + const firstMapping = meta.mappingSaveLists?.[0]; + if (firstMapping) { + drawingNo = firstMapping.DrawingNo; + drawingName = firstMapping.DrawingName; + discipline = firstMapping.Discipline; + revision = firstMapping.RevNo; + registerKind = firstMapping.RegisterKindCode || ""; + } + } + + return { + id: item.id, + type: item.type, + createdAt: item.createdAt, + userId: item.userId, + userName: item.userName || item.userId, + drawingNo, + drawingName, + discipline, + revision, + registerKind, + files, + }; + }; + + // 내 아이템 매핑 + const myItems = myDbItems.map(parseItem); + + // 다른 사용자 아이템 매핑 + const otherItems = otherDbItems + .filter(item => item.userId !== params.currentUserId) + .map(parseItem); + + return { myItems, otherItems }; + } catch (error) { + console.error("프로젝트 미동기화 목록 조회 실패:", error); + return { myItems: [], otherItems: [] }; + } +} + +// B4 일괄 업로드 (로컬 저장 버전) +// ============================================================================ + +/** + * B4 일괄 업로드 V3 (로컬 저장) + */ +export async function bulkUploadB4FilesV3( + formData: FormData +): Promise<{ success: boolean, syncIds: string[], error?: string }> { + try { + // FormData에서 메타데이터 추출 + const projectNo = formData.get("projectNo") as string; + const userId = formData.get("userId") as string; + const userNm = formData.get("userNm") as string; + const email = formData.get("email") as string; + const vendorCode = formData.get("vendorCode") as string; + const fileCount = parseInt(formData.get("fileCount") as string); + + const syncIds: string[] = []; + + // 그룹핑: UploadId 기준 (하나의 상세도면에 여러 파일이 들어갈 수 있음) + const groups = new Map<string, { + files: File[], + mappings: B4MappingSaveItem[], // 타입을 명시적으로 지정 + drawingNo: string + }>(); + + for (let i = 0; i < fileCount; i++) { + const file = formData.get(`file_${i}`) as File; + const mappingJson = formData.get(`mappingData_${i}`) as string; + + if (!file || !mappingJson) continue; + + const mapping = JSON.parse(mappingJson); + const uploadId = mapping.UploadId; + + if (!groups.has(uploadId)) { + groups.set(uploadId, { files: [], mappings: [], drawingNo: mapping.DrawingNo }); + } + + groups.get(uploadId)!.files.push(file); + groups.get(uploadId)!.mappings.push(mapping); + } + + // 각 그룹(상세도면 단위)별로 로컬 저장 + for (const [uploadId, group] of groups.entries()) { + const savedItem = await saveToLocalBuffer({ + type: "B4_BULK", + projectNo, + userId, + userName: userNm, // [추가] + vendorCode: vendorCode, // [추가] + drawingNo: group.drawingNo, + uploadId, + metaData: { + mappingSaveLists: group.mappings, + userInfo: { userId, userName: userNm, vendorCode, email } + }, + files: group.files + }); + syncIds.push(savedItem.id); + } + + return { success: true, syncIds }; + + } catch (error) { + console.error("B4 일괄 업로드 로컬 저장 실패:", error); + return { success: false, syncIds: [], error: error instanceof Error ? error.message : "Unknown error" }; + } +} + +/** + * 9. 로컬 파일 다운로드 (Buffer 반환) + */ +export async function downloadLocalFile(fileId: string) { + return getLocalFile(fileId); +} + +/** + * 10. 로컬 상세도면 삭제 + */ +export async function deleteLocalDetailDrawing(uploadId: string) { + try { + // Find the item by uploadId and type + // We assume one ADD_DETAIL per uploadId for now (as per prepareB4DetailDrawingsV2 logic) + const item = await db.query.dolceSyncList.findFirst({ + where: and( + eq(dolceSyncList.uploadId, uploadId), + eq(dolceSyncList.type, "ADD_DETAIL"), + eq(dolceSyncList.isSynced, false) + ) + }); + + if (item) { + await deleteLocalItem(item.id); + return { success: true }; + } + return { success: false, error: "Item not found" }; + } catch (e) { + console.error("Failed to delete local drawing", e); + return { success: false, error: e instanceof Error ? e.message : "Unknown error" }; + } +} + +/** + * 11. 로컬 파일 삭제 + */ +export async function deleteLocalFile(fileId: string) { + try { + await deleteLocalFileFromItem(fileId); + return { success: true }; + } catch (e) { + console.error("Failed to delete local file", e); + return { success: false, error: e instanceof Error ? e.message : "Unknown error" }; + } +} |
