summaryrefslogtreecommitdiff
path: root/lib/bidding-projects/actions.ts
diff options
context:
space:
mode:
Diffstat (limited to 'lib/bidding-projects/actions.ts')
-rw-r--r--lib/bidding-projects/actions.ts237
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
+ : "프로젝트 업데이트에 실패했습니다.",
+ };
+ }
+}