summaryrefslogtreecommitdiff
path: root/lib/dolce/actions.ts
diff options
context:
space:
mode:
Diffstat (limited to 'lib/dolce/actions.ts')
-rw-r--r--lib/dolce/actions.ts914
1 files changed, 914 insertions, 0 deletions
diff --git a/lib/dolce/actions.ts b/lib/dolce/actions.ts
new file mode 100644
index 00000000..a9cda76a
--- /dev/null
+++ b/lib/dolce/actions.ts
@@ -0,0 +1,914 @@
+"use server";
+
+import { getServerSession } from "next-auth/next";
+import { authOptions } from "@/app/api/auth/[...nextauth]/route";
+import db from "@/db/db";
+import { vendors, users, contracts, projects } from "@/db/schema";
+import { eq } from "drizzle-orm";
+
+const DOLCE_API_URL = process.env.DOLCE_API_URL || "http://60.100.99.217:1111";
+
+// ============================================================================
+// 타입 정의
+// ============================================================================
+
+// B3 벤더 도면 아이템
+export interface DwgReceiptItem {
+ AppDwg_PlanDate: string;
+ AppDwg_ResultDate: string;
+ CreateDt: string;
+ CreateUserENM: string | null;
+ CreateUserId: string | null;
+ CreateUserNo: string;
+ Discipline: string;
+ DrawingKind: string;
+ DrawingMoveGbn: string;
+ DrawingName: string;
+ DrawingNo: string;
+ Manager: string;
+ ManagerENM: string;
+ ManagerNo: string;
+ ProjectNo: string;
+ RegisterGroup: number;
+ RegisterGroupId: number;
+ VendorCode: string;
+ VendorName: string;
+ WorDwg_PlanDate: string;
+ WorDwg_ResultDate: string;
+}
+
+// B4 (GTT) 벤더 도면 아이템
+export interface GttDwgReceiptItem {
+ CGbn: string | null;
+ CreateDt: string;
+ CreateUserENM: string;
+ CreateUserId: string;
+ CreateUserNo: string;
+ DGbn: string | null;
+ DegreeGbn: string | null;
+ DeptGbn: string | null;
+ Discipline: string;
+ DrawingKind: string;
+ DrawingMoveGbn: string; // "도면제출" 또는 "도면입수"
+ DrawingName: string;
+ DrawingNo: string;
+ GTTInput_PlanDate: string | null;
+ GTTInput_ResultDate: string | null;
+ GTTPreDwg_PlanDate: string | null;
+ GTTPreDwg_ResultDate: string | null;
+ GTTWorkingDwg_PlanDate: string | null;
+ GTTWorkingDwg_ResultDate: string | null;
+ JGbn: string | null;
+ Manager: string;
+ ManagerENM: string;
+ ManagerNo: string;
+ ProjectNo: string;
+ RegisterGroup: number;
+ RegisterGroupId: number;
+ SGbn: string | null;
+ SHIDrawingNo: string | null;
+}
+
+// 통합 도면 아이템 타입
+export type UnifiedDwgReceiptItem = DwgReceiptItem | GttDwgReceiptItem;
+
+// 타입 가드는 클라이언트에서 직접 DrawingKind === "B4" 체크로 대체
+// (서버 액션 export 함수는 반드시 async여야 하므로 타입 가드는 export 불가)
+
+export interface DetailDwgReceiptItem {
+ Category: string;
+ CategoryENM: string;
+ CategoryNM: string;
+ CreateDt: string;
+ CreateUserENM: string;
+ CreateUserId: string;
+ CreateUserNM: string;
+ Discipline: string;
+ DrawingKind: string;
+ DrawingName: string;
+ DrawingNo: string;
+ DrawingRevNo: string;
+ DrawingUsage: string;
+ DrawingUsageENM: string | null;
+ DrawingUsageNM: string;
+ Manager: string;
+ Mode: string | null;
+ OFDC_NO: string;
+ ProjectNo: string;
+ Receiver: string | null;
+ RegCompanyCode: string | null;
+ RegCompanyENM: string | null;
+ RegCompanyNM: string | null;
+ RegisterDesc: string;
+ RegisterGroup: number;
+ RegisterGroupId: number;
+ RegisterId: number;
+ RegisterKind: string;
+ RegisterKindENM: string | null;
+ RegisterKindNM: string;
+ RegisterSerialNo: number;
+ SHINote: string | null;
+ Status: string;
+ UploadId: string;
+}
+
+export interface FileInfoItem {
+ CreateDt: string;
+ CreateUserId: string;
+ Deleted: string;
+ FileDescription: string;
+ FileId: string;
+ FileName: string;
+ FileRelativePath: string;
+ FileSeq: string;
+ FileServerId: string;
+ FileSize: string;
+ FileTitle: string | null;
+ FileWriteDT: string;
+ OwnerUserId: string;
+ SourceDrmYn: string | null;
+ SystemId: string;
+ TableName: string;
+ UploadId: string;
+ UseYn: string;
+}
+
+export interface DetailDwgEditRequest {
+ Mode: "ADD" | "MOD";
+ Status: string;
+ RegisterId: number;
+ ProjectNo: string;
+ Discipline: string;
+ DrawingKind: string;
+ DrawingNo: string;
+ DrawingName: string;
+ RegisterGroupId: number;
+ RegisterSerialNo: number;
+ RegisterKind: string;
+ DrawingRevNo: string;
+ Category: string;
+ Receiver: string | null;
+ Manager: string;
+ RegisterDesc: string;
+ UploadId: string;
+ RegCompanyCode: string;
+}
+
+// ============================================================================
+// 유틸리티 함수
+// ============================================================================
+
+async function dolceApiCall<T>(endpoint: string, body: Record<string, unknown>): Promise<T> {
+ const url = `${DOLCE_API_URL}/Services/VDCSWebService.svc/${endpoint}`;
+
+ console.log(`[DOLCE API] Calling ${endpoint}:`, JSON.stringify(body, null, 2));
+
+ const response = await fetch(url, {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ },
+ body: JSON.stringify(body),
+ });
+
+ if (!response.ok) {
+ throw new Error(`DOLCE API 오류 (${endpoint}): ${response.status} ${response.statusText}`);
+ }
+
+ const data = await response.json();
+ console.log(`[DOLCE API] Response from ${endpoint}:`, JSON.stringify(data, null, 2));
+
+ return data;
+}
+
+// ============================================================================
+// 서버 액션
+// ============================================================================
+
+/**
+ * 1. 도면 리스트 조회 (B3 및 B4 통합)
+ */
+export async function fetchDwgReceiptList(params: {
+ project: string;
+ drawingKind: string;
+ drawingMoveGbn?: string;
+ drawingNo?: string;
+ drawingName?: string;
+ drawingVendor?: string;
+ discipline?: string;
+}): Promise<UnifiedDwgReceiptItem[]> {
+ try {
+ const response = await dolceApiCall<{
+ DwgReceiptMgmtResult: {
+ FMEADwgList: unknown[];
+ GTTDwgList: GttDwgReceiptItem[];
+ VendorDwgList: DwgReceiptItem[];
+ };
+ }>("DwgReceiptMgmt", {
+ project: params.project,
+ drawingKind: params.drawingKind,
+ drawingMoveGbn: params.drawingMoveGbn || "",
+ drawingNo: params.drawingNo || "",
+ drawingName: params.drawingName || "",
+ drawingVendor: params.drawingVendor || "",
+ discipline: params.discipline || "",
+ });
+
+ // B4(GTT)인 경우 GTTDwgList 반환, B3인 경우 VendorDwgList 반환
+ if (params.drawingKind === "B4") {
+ return response.DwgReceiptMgmtResult.GTTDwgList;
+ } else {
+ return response.DwgReceiptMgmtResult.VendorDwgList;
+ }
+ } catch (error) {
+ console.error("도면 리스트 조회 실패:", error);
+ throw error;
+ }
+}
+
+/**
+ * 2. 상세도면 리스트 조회
+ */
+export async function fetchDetailDwgReceiptList(params: {
+ project: string;
+ drawingNo: string;
+ discipline: string;
+ drawingKind: string;
+ userId?: string;
+}): Promise<DetailDwgReceiptItem[]> {
+ try {
+ const response = await dolceApiCall<{
+ DetailDwgReceiptMgmtResult: DetailDwgReceiptItem[];
+ }>("DetailDwgReceiptMgmt", {
+ project: params.project,
+ drawingNo: params.drawingNo,
+ discipline: params.discipline,
+ drawingKind: params.drawingKind,
+ userId: params.userId || "",
+ });
+
+ return response.DetailDwgReceiptMgmtResult;
+ } catch (error) {
+ console.error("상세도면 리스트 조회 실패:", error);
+ throw error;
+ }
+}
+
+/**
+ * 3. 개별 상세도면 파일 리스트 조회
+ */
+export async function fetchFileInfoList(uploadId: string): Promise<FileInfoItem[]> {
+ try {
+ const response = await dolceApiCall<{
+ FileInfoListResult: FileInfoItem[];
+ }>("FileInfoList", {
+ uploadId,
+ });
+
+ // UseYn이 "True"인 파일만 반환
+ return response.FileInfoListResult.filter((file) => file.UseYn === "True");
+ } catch (error) {
+ console.error("파일 리스트 조회 실패:", error);
+ throw error;
+ }
+}
+
+/**
+ * 4. 상세도면 추가/수정
+ */
+export async function editDetailDwgReceipt(params: {
+ dwgList: DetailDwgEditRequest[];
+ userId: string;
+ userNm: string;
+ vendorCode: string;
+ email: string;
+}): Promise<number> {
+ try {
+ const response = await dolceApiCall<{
+ DetailDwgReceiptMmgtEditResult: number;
+ }>("DetailDwgReceiptMgmtEdit", {
+ DwgList: params.dwgList,
+ UserID: params.userId,
+ UserNM: params.userNm,
+ VENDORCODE: params.vendorCode,
+ EMAIL: params.email,
+ });
+
+ return response.DetailDwgReceiptMmgtEditResult;
+ } catch (error) {
+ console.error("상세도면 수정 실패:", error);
+ throw error;
+ }
+}
+
+/**
+ * 5. 파일 다운로드 (프록시)
+ */
+export async function downloadDolceFile(params: {
+ fileId: string;
+ userId: string;
+ fileName: string;
+}): Promise<{ blob: Blob; fileName: string }> {
+ try {
+ const { createDolceDownloadKey } = await import("./crypto-utils");
+
+ // DES 암호화된 키 생성
+ const encryptedKey = createDolceDownloadKey(
+ params.fileId,
+ params.userId,
+ params.fileName
+ );
+
+ const url = `${DOLCE_API_URL}/Download.aspx`;
+
+ // 디버깅용 상세 로그
+ const plainText = `${params.fileId}↔${params.userId}↔${params.fileName}`;
+ console.log("[DOLCE] 파일 다운로드 상세:", {
+ fileId: params.fileId,
+ userId: params.userId,
+ fileName: params.fileName,
+ plainText,
+ encryptedKey,
+ fullUrl: `${url}?key=${encryptedKey}`,
+ });
+
+ // 주의: encodeURIComponent 사용하지 않음!
+ // C#에서는 +를 |||로 변환 후 그대로 URL에 사용
+ const response = await fetch(`${url}?key=${encryptedKey}`, {
+ method: "GET",
+ });
+
+ console.log("[DOLCE] 응답 상태:", response.status, response.statusText);
+ console.log("[DOLCE] Content-Type:", response.headers.get("content-type"));
+
+ if (!response.ok) {
+ const errorText = await response.text();
+ console.error("[DOLCE] 에러 응답:", errorText);
+ throw new Error(`파일 다운로드 실패: ${response.status} - ${errorText}`);
+ }
+
+ // HTML/텍스트 응답인 경우 에러일 수 있음
+ const contentType = response.headers.get("content-type") || "";
+ if (contentType.includes("text") || contentType.includes("html")) {
+ const errorText = await response.text();
+ console.error("[DOLCE] 텍스트 응답 (에러):", errorText);
+ throw new Error(`예상치 못한 응답: ${errorText.substring(0, 200)}`);
+ }
+
+ const blob = await response.blob();
+ console.log("[DOLCE] 다운로드 성공:", blob.size, "bytes");
+
+ return {
+ blob,
+ fileName: params.fileName,
+ };
+ } catch (error) {
+ console.error("[DOLCE] 파일 다운로드 실패:", error);
+ throw error;
+ }
+}
+
+/**
+ * 벤더 세션 정보 조회
+ */
+export async function getVendorSessionInfo() {
+ try {
+ const session = await getServerSession(authOptions);
+ if (!session?.user) {
+ throw new Error("로그인이 필요합니다");
+ }
+
+ // DB에서 사용자 정보 조회
+ const userId = parseInt(session.user.id);
+ const userInfo = await db.query.users.findFirst({
+ where: eq(users.id, userId),
+ columns: {
+ id: true,
+ name: true,
+ email: true,
+ companyId: true,
+ },
+ });
+
+ if (!userInfo || !userInfo.companyId) {
+ throw new Error("벤더 정보를 찾을 수 없습니다");
+ }
+
+ // 벤더 정보 조회
+ const vendorInfo = await db.query.vendors.findFirst({
+ where: eq(vendors.id, userInfo.companyId),
+ columns: {
+ id: true,
+ vendorCode: true,
+ vendorName: true,
+ },
+ });
+
+ if (!vendorInfo) {
+ throw new Error("벤더 정보를 찾을 수 없습니다");
+ }
+
+ // GTT 벤더 확인 (A0016193)
+ const drawingKind = vendorInfo.vendorCode === "A0016193" ? "B4" : "B3";
+
+ return {
+ userId: String(userInfo.id),
+ userName: userInfo.name || "",
+ email: userInfo.email || "",
+ vendorCode: vendorInfo.vendorCode || "",
+ vendorName: vendorInfo.vendorName || "",
+ drawingKind, // B3 또는 B4
+ };
+ } catch (error) {
+ console.error("세션 정보 조회 실패:", error);
+ throw error;
+ }
+}
+
+/**
+ * 벤더의 프로젝트 목록 조회 (계약이 있는 프로젝트)
+ */
+export async function fetchVendorProjects(): Promise<
+ Array<{ code: string; name: string }>
+> {
+ try {
+ const vendorInfo = await getVendorSessionInfo();
+
+ // 벤더와 계약이 있는 프로젝트 목록 조회
+ const projectList = await db
+ .selectDistinct({
+ code: projects.code,
+ name: projects.name,
+ })
+ .from(contracts)
+ .innerJoin(projects, eq(contracts.projectId, projects.id))
+ .innerJoin(vendors, eq(contracts.vendorId, vendors.id))
+ .where(eq(vendors.vendorCode, vendorInfo.vendorCode));
+
+ return projectList.map((p) => ({
+ code: p.code || "",
+ name: p.name || "",
+ }));
+ } catch (error) {
+ console.error("프로젝트 목록 조회 실패:", error);
+ throw error;
+ }
+}
+
+// ============================================================================
+// B4 일괄 업로드 관련
+// ============================================================================
+
+/**
+ * MatchBatchFileDwg 매핑 체크 아이템
+ */
+export interface MappingCheckItem {
+ DrawingNo: string;
+ RevNo: string;
+ FileNm: string;
+}
+
+/**
+ * MatchBatchFileDwg 응답 아이템
+ */
+export interface MappingCheckResult {
+ CGbn: string | null;
+ Category: string | null;
+ CheckBox: string;
+ CreateDt: string | null;
+ CreateUserId: string | null;
+ DGbn: string | null;
+ DegreeGbn: string | null;
+ DeptGbn: string | null;
+ Discipline: string | null;
+ DrawingKind: string;
+ DrawingMoveGbn: string | null;
+ DrawingName: string | null;
+ DrawingNo: string;
+ DrawingUsage: string | null;
+ FileNm: string;
+ JGbn: string | null;
+ Manager: string | null;
+ MappingYN: "Y" | "N";
+ NewOrNot: string | null;
+ ProjectNo: string;
+ RegisterGroup: number;
+ RegisterGroupId: number;
+ RegisterKindCode: string | null;
+ RegisterSerialNo: number;
+ RevNo: string | null;
+ SGbn: string | null;
+ Status: string | null;
+ UploadId: string | null;
+}
+
+/**
+ * B4 일괄 업로드 결과
+ */
+export interface B4BulkUploadResult {
+ success: boolean;
+ successCount?: number;
+ failCount?: number;
+ error?: string;
+ results?: Array<{
+ drawingNo: string;
+ revNo: string;
+ fileName: string;
+ success: boolean;
+ error?: string;
+ }>;
+}
+
+/**
+ * B4 파일 매핑 상태 확인 (MatchBatchFileDwg)
+ */
+export async function checkB4MappingStatus(
+ projectNo: string,
+ fileList: MappingCheckItem[]
+): Promise<MappingCheckResult[]> {
+ try {
+ const response = await dolceApiCall<{
+ MatchBatchFileDwgResult: MappingCheckResult[];
+ }>("MatchBatchFileDwg", {
+ ProjectNo: projectNo,
+ DrawingKind: "B4",
+ drawingMoveGbn: "도면입수",
+ FileList: fileList.map((item) => ({
+ ProjectNo: projectNo,
+ DrawingKind: "B4",
+ DrawingNo: item.DrawingNo,
+ RevNo: item.RevNo,
+ FileNm: item.FileNm,
+ })),
+ });
+
+ return response.MatchBatchFileDwgResult;
+ } catch (error) {
+ console.error("매핑 상태 확인 실패:", error);
+ throw error;
+ }
+}
+
+/**
+ * 상세도면에 파일 업로드 결과
+ */
+export interface UploadFilesResult {
+ success: boolean;
+ uploadedCount?: number;
+ error?: string;
+}
+
+/**
+ * 상세도면에 파일 업로드 (기존 UploadId에 파일 추가)
+ */
+export async function uploadFilesToDetailDrawing(
+ formData: FormData
+): Promise<UploadFilesResult> {
+ try {
+ const uploadId = formData.get("uploadId") as string;
+ const userId = formData.get("userId") as string;
+ const fileCount = parseInt(formData.get("fileCount") as string);
+
+ if (!uploadId || !userId || !fileCount) {
+ throw new Error("필수 파라미터가 누락되었습니다");
+ }
+
+ const uploadResults: Array<{
+ FileId: string;
+ UploadId: string;
+ FileSeq: number;
+ FileName: string;
+ FileRelativePath: string;
+ FileSize: number;
+ FileCreateDT: string;
+ FileWriteDT: string;
+ OwnerUserId: string;
+ }> = [];
+
+ // 기존 파일 개수 조회 (FileSeq를 이어서 사용하기 위함)
+ const existingFiles = await dolceApiCall<{
+ FileInfoListResult: Array<{ FileSeq: string }>;
+ }>("FileInfoList", {
+ uploadId: uploadId,
+ });
+
+ const startSeq = existingFiles.FileInfoListResult.length + 1;
+
+ // 각 파일 업로드
+ for (let i = 0; i < fileCount; i++) {
+ const file = formData.get(`file_${i}`) as File;
+ if (!file) continue;
+
+ const fileId = crypto.randomUUID();
+
+ // 파일을 ArrayBuffer로 변환
+ const arrayBuffer = await file.arrayBuffer();
+
+ // PWPUploadService.ashx 호출
+ const uploadUrl = `${DOLCE_API_URL}/PWPUploadService.ashx?UploadId=${uploadId}&FileId=${fileId}`;
+
+ const uploadResponse = await fetch(uploadUrl, {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/octet-stream",
+ },
+ body: arrayBuffer,
+ });
+
+ if (!uploadResponse.ok) {
+ throw new Error(
+ `파일 업로드 실패: ${uploadResponse.status} ${uploadResponse.statusText}`
+ );
+ }
+
+ const fileRelativePath = await uploadResponse.text();
+
+ uploadResults.push({
+ FileId: fileId,
+ UploadId: uploadId,
+ FileSeq: startSeq + i,
+ FileName: file.name,
+ FileRelativePath: fileRelativePath,
+ FileSize: file.size,
+ FileCreateDT: new Date().toISOString(),
+ FileWriteDT: new Date().toISOString(),
+ OwnerUserId: userId,
+ });
+ }
+
+ // 업로드 완료 통지 (PWPUploadResultService.ashx)
+ const resultServiceUrl = `${DOLCE_API_URL}/PWPUploadResultService.ashx?`;
+
+ const resultResponse = await fetch(resultServiceUrl, {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ },
+ body: JSON.stringify(uploadResults),
+ });
+
+ if (!resultResponse.ok) {
+ throw new Error(
+ `업로드 완료 통지 실패: ${resultResponse.status} ${resultResponse.statusText}`
+ );
+ }
+
+ const resultText = await resultResponse.text();
+ if (resultText !== "Success") {
+ throw new Error(`업로드 완료 통지 실패: ${resultText}`);
+ }
+
+ return {
+ success: true,
+ uploadedCount: fileCount,
+ };
+ } catch (error) {
+ console.error("파일 업로드 실패:", error);
+ return {
+ success: false,
+ error: error instanceof Error ? error.message : "알 수 없는 오류",
+ };
+ }
+}
+
+/**
+ * B4 파일 일괄 업로드
+ * 주의: formData를 사용하여 대용량 파일 처리
+ */
+export async function bulkUploadB4Files(
+ formData: FormData
+): Promise<B4BulkUploadResult> {
+ try {
+ // FormData에서 메타데이터 추출
+ const projectNo = formData.get("projectNo") as string;
+ const userId = formData.get("userId") as string;
+ const fileCount = parseInt(formData.get("fileCount") as string);
+ const registerKind = formData.get("registerKind") as string;
+
+ if (!projectNo || !userId || !fileCount || !registerKind) {
+ throw new Error("필수 파라미터가 누락되었습니다");
+ }
+
+ const results: Array<{
+ drawingNo: string;
+ revNo: string;
+ fileName: string;
+ success: boolean;
+ error?: string;
+ }> = [];
+
+ let successCount = 0;
+ let failCount = 0;
+
+ // 파일별로 그룹화 (DrawingNo + RevNo 기준)
+ const uploadGroups = new Map<
+ string,
+ Array<{
+ file: File;
+ drawingNo: string;
+ revNo: string;
+ fileName: string;
+ registerGroupId: number;
+ }>
+ >();
+
+ for (let i = 0; i < fileCount; i++) {
+ const file = formData.get(`file_${i}`) as File;
+ const drawingNo = formData.get(`drawingNo_${i}`) as string;
+ const revNo = formData.get(`revNo_${i}`) as string;
+ const fileName = formData.get(`fileName_${i}`) as string;
+ const registerGroupId = parseInt(
+ formData.get(`registerGroupId_${i}`) as string
+ );
+
+ if (!file || !drawingNo || !revNo) {
+ continue;
+ }
+
+ const groupKey = `${drawingNo}_${revNo}`;
+ if (!uploadGroups.has(groupKey)) {
+ uploadGroups.set(groupKey, []);
+ }
+
+ uploadGroups.get(groupKey)!.push({
+ file,
+ drawingNo,
+ revNo,
+ fileName,
+ registerGroupId,
+ });
+ }
+
+ // 각 그룹별로 업로드 처리
+ for (const [, files] of uploadGroups.entries()) {
+ const { drawingNo, revNo, registerGroupId } = files[0];
+
+ try {
+ // 1. UploadId 생성 (그룹당 하나)
+ const uploadId = crypto.randomUUID();
+
+ // 2. 파일 업로드 (PWPUploadService.ashx)
+ const uploadResults: Array<{
+ FileId: string;
+ UploadId: string;
+ FileSeq: number;
+ FileName: string;
+ FileRelativePath: string;
+ FileSize: number;
+ FileCreateDT: string;
+ FileWriteDT: string;
+ OwnerUserId: string;
+ }> = [];
+
+ for (let i = 0; i < files.length; i++) {
+ const fileInfo = files[i];
+ const fileId = crypto.randomUUID();
+
+ // 파일을 ArrayBuffer로 변환
+ const arrayBuffer = await fileInfo.file.arrayBuffer();
+
+ // PWPUploadService.ashx 호출
+ const uploadUrl = `${DOLCE_API_URL}/PWPUploadService.ashx?UploadId=${uploadId}&FileId=${fileId}`;
+
+ const uploadResponse = await fetch(uploadUrl, {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/octet-stream",
+ },
+ body: arrayBuffer,
+ });
+
+ if (!uploadResponse.ok) {
+ throw new Error(
+ `파일 업로드 실패: ${uploadResponse.status} ${uploadResponse.statusText}`
+ );
+ }
+
+ const fileRelativePath = await uploadResponse.text();
+
+ uploadResults.push({
+ FileId: fileId,
+ UploadId: uploadId,
+ FileSeq: i + 1,
+ FileName: fileInfo.file.name,
+ FileRelativePath: fileRelativePath,
+ FileSize: fileInfo.file.size,
+ FileCreateDT: new Date().toISOString(),
+ FileWriteDT: new Date().toISOString(),
+ OwnerUserId: userId,
+ });
+ }
+
+ // 3. 업로드 완료 통지 (PWPUploadResultService.ashx)
+ const resultServiceUrl = `${DOLCE_API_URL}/PWPUploadResultService.ashx?`;
+
+ const resultResponse = await fetch(resultServiceUrl, {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ },
+ body: JSON.stringify(uploadResults),
+ });
+
+ if (!resultResponse.ok) {
+ throw new Error(
+ `업로드 완료 통지 실패: ${resultResponse.status} ${resultResponse.statusText}`
+ );
+ }
+
+ const resultText = await resultResponse.text();
+ if (resultText !== "Success") {
+ throw new Error(`업로드 완료 통지 실패: ${resultText}`);
+ }
+
+ // 4. 매핑 현황 재조회 (MatchBatchFileDwg)
+ const mappingCheckResults = await checkB4MappingStatus(projectNo, [
+ {
+ DrawingNo: drawingNo,
+ RevNo: revNo,
+ FileNm: files[0].fileName,
+ },
+ ]);
+
+ const mappingData = mappingCheckResults[0];
+ if (!mappingData || mappingData.RegisterGroupId === 0) {
+ throw new Error("매핑 정보를 찾을 수 없습니다");
+ }
+
+ // 5. 매핑 정보 저장 (MatchBatchFileDwgEdit)
+ await dolceApiCall("MatchBatchFileDwgEdit", {
+ mappingSaveLists: [
+ {
+ CGbn: mappingData.CGbn,
+ Category: mappingData.Category,
+ CheckBox: "0",
+ DGbn: mappingData.DGbn,
+ DegreeGbn: mappingData.DegreeGbn,
+ DeptGbn: mappingData.DeptGbn,
+ Discipline: mappingData.Discipline,
+ DrawingKind: "B4",
+ DrawingMoveGbn: "도면입수",
+ DrawingName: mappingData.DrawingName,
+ DrawingNo: drawingNo,
+ DrawingUsage: "입수용",
+ FileNm: files[0].fileName,
+ JGbn: mappingData.JGbn,
+ Manager: mappingData.Manager || "970043",
+ MappingYN: "Y",
+ NewOrNot: "N",
+ ProjectNo: projectNo,
+ RegisterGroup: 0,
+ RegisterGroupId: registerGroupId,
+ RegisterKindCode: registerKind, // 사용자가 선택한 RegisterKind 사용
+ RegisterSerialNo: mappingData.RegisterSerialNo,
+ RevNo: revNo,
+ SGbn: mappingData.SGbn,
+ UploadId: uploadId,
+ },
+ ],
+ UserID: parseInt(userId),
+ });
+
+ // 성공 처리
+ for (const fileInfo of files) {
+ results.push({
+ drawingNo,
+ revNo,
+ fileName: fileInfo.fileName,
+ success: true,
+ });
+ successCount++;
+ }
+ } catch (error) {
+ // 실패 처리
+ const errorMessage =
+ error instanceof Error ? error.message : "알 수 없는 오류";
+
+ for (const fileInfo of files) {
+ results.push({
+ drawingNo,
+ revNo,
+ fileName: fileInfo.fileName,
+ success: false,
+ error: errorMessage,
+ });
+ failCount++;
+ }
+ }
+ }
+
+ return {
+ success: successCount > 0,
+ successCount,
+ failCount,
+ results,
+ };
+ } catch (error) {
+ console.error("일괄 업로드 실패:", error);
+ return {
+ success: false,
+ error: error instanceof Error ? error.message : "알 수 없는 오류",
+ };
+ }
+}
+