diff options
Diffstat (limited to 'lib/bidding-projects/actions.ts')
| -rw-r--r-- | lib/bidding-projects/actions.ts | 237 |
1 files changed, 237 insertions, 0 deletions
diff --git a/lib/bidding-projects/actions.ts b/lib/bidding-projects/actions.ts new file mode 100644 index 00000000..bbb755aa --- /dev/null +++ b/lib/bidding-projects/actions.ts @@ -0,0 +1,237 @@ +"use server"; + +import { revalidatePath, revalidateTag } from "next/cache"; +import db from "@/db/db"; +import { biddingProjects, NewBiddingProject } from "@/db/schema/projects"; +import { plftbEstmProjMast } from "@/db/schema/NONSAP/nonsap"; +import { eq, desc } from "drizzle-orm"; +import { updateBiddingProjectSchema, type UpdateBiddingProjectSchema } from "./validation"; + +/** + * NONSAP에서 견적프로젝트 데이터를 가져와서 biddingProjects 테이블에 upsert하는 서버 액션 + */ +export async function syncProjectsFromNonSap() { + try { + // 1. NONSAP에서 IF_TRGT_YN = 'Y'인 데이터를 가져오되, + // 같은 ESTM_PROJ_NO에 대해 REV_NO가 가장 높은 것만 가져오기 + const nonsapProjects = await db + .select() + .from(plftbEstmProjMast) + .where(eq(plftbEstmProjMast.IF_TRGT_YN, "Y")) + .orderBy(desc(plftbEstmProjMast.REV_NO)); + + if (nonsapProjects.length === 0) { + return { + success: true, + message: "동기화할 프로젝트가 없습니다.", + syncedCount: 0, + }; + } + + // 2. 프로젝트 번호별로 가장 높은 REV_NO만 유지 + const latestProjects = new Map(); + for (const project of nonsapProjects) { + const existingProject = latestProjects.get(project.ESTM_PROJ_NO); + if ( + !existingProject || + (project.REV_NO && project.REV_NO > existingProject.REV_NO) + ) { + latestProjects.set(project.ESTM_PROJ_NO, project); + } + } + + let syncedCount = 0; + const errors: string[] = []; + + // 3. 각 프로젝트를 biddingProjects 형식으로 변환하고 upsert + for (const nonsapProject of latestProjects.values()) { + try { + // pspid가 없으면 해당 프로젝트는 건너뛰기 + if (!nonsapProject.ESTM_PROJ_NO?.trim()) { + console.warn(`프로젝트 번호가 없는 데이터 건너뛰기:`, nonsapProject); + continue; + } + + // 컬럼 매핑 및 타입 변환 + const biddingProject: NewBiddingProject = { + pspid: nonsapProject.ESTM_PROJ_NO.substring(0, 8), // 해양 TOP은 8자리만 사용 + projNm: nonsapProject.ESTM_PROJ_NM?.substring(0, 90) || null, // 90자 제한 + sector: nonsapProject.BIZ_CLS?.substring(0, 1) || null, // 1자 제한 + projMsrm: nonsapProject.SERS_CNT + ? (() => { + const numValue = Number(nonsapProject.SERS_CNT); + return isNaN(numValue) + ? null + : String(Math.min(Math.max(numValue, 0), 999)); + })() + : null, // precision 3,0 제한 (0~999, 숫자가 아니면 null) + kunnr: nonsapProject.OWNER_CD?.substring(0, 10) || null, // 10자 제한 + kunnrNm: null, // NONSAP에 없음 + cls1: nonsapProject.CLS_1?.substring(0, 10) || null, // 10자 제한 + cls1Nm: null, // NONSAP에 없음 + ptype: nonsapProject.SKND_CD?.substring(0, 3) || null, // 10 -> 3자 제한 + ptypeNm: null, // NONSAP에 없음 + pmodelCd: nonsapProject.SHTYPE_CD?.substring(0, 10) || null, // 10자 제한 + pmodelNm: null, // NONSAP에 없음 + pmodelSz: nonsapProject.SHTYPE_SIZE + ? String(nonsapProject.SHTYPE_SIZE).substring(0, 20) + : null, // numeric to varchar(20) + pmodelUom: nonsapProject.SHTYPE_UOM?.substring(0, 5) || null, // 10 -> 5자 제한 + txt04: nonsapProject.ESTM_TYPE?.substring(0, 4) || null, // 1 -> 4자 제한 + txt30: null, // NONSAP에 없음 + estmPm: null, // NONSAP에 없음 + pjtType: "TOP", // NONSAP에서 오는 것은 해양 탑으로 고정 + }; + + // upsert 처리 (pspid를 기준으로) + const existingProject = await db.query.biddingProjects.findFirst({ + where: eq(biddingProjects.pspid, biddingProject.pspid), + }); + + if (existingProject) { + // 업데이트 - 시트에서 수정 가능한 필드들은 제외하고 NONSAP 원본 데이터만 업데이트 + await db + .update(biddingProjects) + .set({ + // NONSAP 원본 데이터만 업데이트 (시트 수정 필드 제외) + pspid: biddingProject.pspid, + projNm: biddingProject.projNm, + sector: biddingProject.sector, + projMsrm: biddingProject.projMsrm, + kunnr: biddingProject.kunnr, + // kunnrNm: 시트에서 수정 가능하므로 제외 + cls1: biddingProject.cls1, + // cls1Nm: 시트에서 수정 가능하므로 제외 + ptype: biddingProject.ptype, + // ptypeNm: 시트에서 수정 가능하므로 제외 + pmodelCd: biddingProject.pmodelCd, + // pmodelNm: 시트에서 수정 가능하므로 제외 + pmodelSz: biddingProject.pmodelSz, + pmodelUom: biddingProject.pmodelUom, + txt04: biddingProject.txt04, + // txt30: 시트에서 수정 가능하므로 제외 + // estmPm: 시트에서 수정 가능하므로 제외 + pjtType: biddingProject.pjtType, + updatedAt: new Date(), + }) + .where(eq(biddingProjects.pspid, biddingProject.pspid)); + } else { + // 신규 등록 + await db.insert(biddingProjects).values(biddingProject); + } + + syncedCount++; + } catch (error) { + const errorMsg = `프로젝트 ${nonsapProject.ESTM_PROJ_NO} 동기화 실패: ${ + error instanceof Error ? error.message : "알 수 없는 오류" + }`; + errors.push(errorMsg); + console.error(errorMsg, error); + } + } + + // 4. 관련 페이지 재검증 (공격적으로) + revalidatePath("/ko/evcp/bid-projects"); // 국제화 경로 + revalidatePath("/en/evcp/bid-projects"); // 영어 경로 + revalidatePath("/ko/evcp/vendor-info/projects"); // 국제화 기존 경로 + revalidatePath("/en/evcp/vendor-info/projects"); // 영어 기존 경로 + + // 관련 태그들 재검증 + revalidateTag("bidding-projects"); + revalidateTag("projects"); + revalidateTag("bid-projects-list"); + revalidateTag("nonsap-projects"); + revalidateTag("vendor-info"); + + return { + success: true, + message: `${syncedCount}개 프로젝트가 성공적으로 동기화되었습니다.${ + errors.length > 0 ? ` (${errors.length}개 오류 발생)` : "" + }`, + syncedCount, + errors: errors.length > 0 ? errors : undefined, + }; + } catch (error) { + console.error("NONSAP 프로젝트 동기화 오류:", error); + return { + success: false, + error: + error instanceof Error + ? error.message + : "프로젝트 동기화에 실패했습니다.", + }; + } +} + +/** + * 견적 프로젝트 정보를 업데이트하는 서버 액션 + * @param input 업데이트할 프로젝트 데이터 + */ +export async function updateBiddingProject(input: UpdateBiddingProjectSchema) { + try { + // 입력 데이터 검증 + const validatedInput = updateBiddingProjectSchema.parse(input); + + // 프로젝트 존재 여부 확인 + const existingProject = await db.query.biddingProjects.findFirst({ + where: eq(biddingProjects.id, validatedInput.id), + }); + + if (!existingProject) { + return { + success: false, + error: "프로젝트를 찾을 수 없습니다.", + }; + } + + // TOP 타입만 수정 가능하도록 제한 + if (existingProject.pjtType !== "TOP") { + return { + success: false, + error: "해양 TOP 프로젝트만 수정할 수 있습니다.", + }; + } + + // 업데이트 실행 + await db + .update(biddingProjects) + .set({ + projNm: validatedInput.projNm, + kunnrNm: validatedInput.kunnrNm, + cls1Nm: validatedInput.cls1Nm, + ptypeNm: validatedInput.ptypeNm, + pmodelNm: validatedInput.pmodelNm, + pmodelSz: validatedInput.pmodelSz, + txt30: validatedInput.txt30, + estmPm: validatedInput.estmPm, + updatedAt: new Date(), + }) + .where(eq(biddingProjects.id, validatedInput.id)); + + // 관련 페이지 재검증 (공격적으로) + revalidatePath("/evcp/bid-projects"); // 해당 페이지 + revalidatePath("/evcp"); // 상위 경로 + revalidatePath("/"); // 루트 경로 + revalidatePath("/ko/evcp/bid-projects"); // 국제화 경로 + revalidatePath("/en/evcp/bid-projects"); // 영어 경로 + + // 관련 태그들 재검증 + revalidateTag("bidding-projects"); + revalidateTag("projects"); + revalidateTag("bid-projects-list"); + + return { + success: true, + message: "프로젝트가 성공적으로 업데이트되었습니다.", + }; + } catch (error) { + console.error("프로젝트 업데이트 오류:", error); + return { + success: false, + error: + error instanceof Error + ? error.message + : "프로젝트 업데이트에 실패했습니다.", + }; + } +} |
