summaryrefslogtreecommitdiff
path: root/lib/dolce-v2/actions.ts
diff options
context:
space:
mode:
Diffstat (limited to 'lib/dolce-v2/actions.ts')
-rw-r--r--lib/dolce-v2/actions.ts605
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" };
+ }
+}