summaryrefslogtreecommitdiff
path: root/lib/sedp
diff options
context:
space:
mode:
authordujinkim <dujin.kim@dtsolution.co.kr>2025-06-01 13:52:21 +0000
committerdujinkim <dujin.kim@dtsolution.co.kr>2025-06-01 13:52:21 +0000
commitbac0228d21b7195065e9cddcc327ae33659c7bcc (patch)
tree8f3016ae4533c8706d0c00a605d9b1d41968c2bc /lib/sedp
parent2fdce8d7a57c792bba0ac36fa554dca9c9cc31e3 (diff)
(대표님) 20250601까지 작업사항
Diffstat (limited to 'lib/sedp')
-rw-r--r--lib/sedp/get-form-tags.ts726
-rw-r--r--lib/sedp/get-tags.ts116
-rw-r--r--lib/sedp/sync-package.ts282
3 files changed, 919 insertions, 205 deletions
diff --git a/lib/sedp/get-form-tags.ts b/lib/sedp/get-form-tags.ts
index 6911180b..4e819414 100644
--- a/lib/sedp/get-form-tags.ts
+++ b/lib/sedp/get-form-tags.ts
@@ -1,4 +1,3 @@
-// lib/sedp/get-tag.ts
import db from "@/db/db";
import {
contractItems,
@@ -14,6 +13,8 @@ import {
} from "@/db/schema";
import { eq, and, like, inArray } from "drizzle-orm";
import { getSEDPToken } from "./sedp-token";
+import { getFormMappingsByTagType } from "../tags/form-mapping-service";
+
interface Attribute {
ATT_ID: string;
@@ -42,12 +43,7 @@ interface Column {
/**
* 태그 가져오기 서비스 함수
* contractItemId(packageId)를 기반으로 외부 시스템에서 태그 데이터를 가져와 DB에 저장
- *
- * @param formCode 양식 코드
- * @param projectCode 프로젝트 코드
- * @param packageId 계약 아이템 ID (contractItemId)
- * @param progressCallback 진행 상황을 보고하기 위한 콜백 함수
- * @returns 처리 결과 정보 (처리된 태그 수, 오류 목록 등)
+ * formEntries와 tags 테이블 모두에 데이터를 저장
*/
export async function importTagsFromSEDP(
formCode: string,
@@ -58,6 +54,7 @@ export async function importTagsFromSEDP(
processedCount: number;
excludedCount: number;
totalEntries: number;
+ formCreated?: boolean; // 새로 추가: form이 생성되었는지 여부
errors?: string[];
}> {
try {
@@ -89,243 +86,568 @@ export async function importTagsFromSEDP(
// 진행 상황 보고
if (progressCallback) progressCallback(20);
- // 프로젝트 ID 가져오기
- const projectRecord = await db.select({ id: projects.id })
- .from(projects)
- .where(eq(projects.code, projectCode))
- .limit(1);
-
- if (!projectRecord || projectRecord.length === 0) {
- throw new Error(`Project not found for code: ${projectCode}`);
- }
-
- const projectId = projectRecord[0].id;
-
- // 양식 메타데이터 가져오기
- const formMetaRecord = await db.select({ columns: formMetas.columns })
- .from(formMetas)
- .where(and(
- eq(formMetas.projectId, projectId),
- eq(formMetas.formCode, formCode)
- ))
- .limit(1);
-
- if (!formMetaRecord || formMetaRecord.length === 0) {
- throw new Error(`Form metadata not found for formCode: ${formCode} and projectId: ${projectId}`);
- }
-
- // 진행 상황 보고
- if (progressCallback) progressCallback(30);
-
- // 컬럼 정보 파싱
- const columnsJSON: Column[] = (formMetaRecord[0].columns);
-
- // 현재 formEntries 데이터 가져오기
- const existingEntries = await db.select({ id: formEntries.id, data: formEntries.data })
- .from(formEntries)
- .where(and(
- eq(formEntries.formCode, formCode),
- eq(formEntries.contractItemId, packageId)
- ));
-
- // 진행 상황 보고
- if (progressCallback) progressCallback(50);
-
- // 기존 데이터를 맵으로 변환하여 태그 번호로 빠르게 조회할 수 있게 함
- const existingTagMap = new Map();
- existingEntries.forEach(entry => {
- const data = entry.data as any[];
- data.forEach(item => {
- if (item.TAG_NO) {
- existingTagMap.set(item.TAG_NO, {
- entryId: entry.id,
- data: item
- });
- }
- });
- });
-
- // 진행 상황 보고
- if (progressCallback) progressCallback(60);
-
- // 처리 결과 카운터
- let processedCount = 0;
- let excludedCount = 0;
-
- // 새로운 태그 데이터와 업데이트할 데이터 준비
- const newTagData: any[] = [];
- const updateData: {entryId: number, tagNo: string, updates: any}[] = [];
-
- // SEDP 태그 데이터 처리
- for (const tagEntry of tagEntries) {
- try {
- if (!tagEntry.TAG_NO) {
- excludedCount++;
- errors.push(`Missing TAG_NO in tag entry`);
- continue;
+ // 트랜잭션으로 모든 DB 작업 처리
+ return await db.transaction(async (tx) => {
+ // 프로젝트 ID 가져오기
+ const projectRecord = await tx.select({ id: projects.id })
+ .from(projects)
+ .where(eq(projects.code, projectCode))
+ .limit(1);
+
+ if (!projectRecord || projectRecord.length === 0) {
+ throw new Error(`Project not found for code: ${projectCode}`);
+ }
+
+ const projectId = projectRecord[0].id;
+
+ // form ID 가져오기 - 없으면 생성
+ let formRecord = await tx.select({ id: forms.id })
+ .from(forms)
+ .where(and(
+ eq(forms.formCode, formCode),
+ eq(forms.contractItemId, packageId)
+ ))
+ .limit(1);
+
+ let formCreated = false;
+
+ // form이 없으면 생성
+ if (!formRecord || formRecord.length === 0) {
+ console.log(`[IMPORT TAGS] Form ${formCode} not found, attempting to create...`);
+
+ // 첫 번째 태그의 정보를 사용해서 form mapping을 찾습니다
+ // 모든 태그가 같은 formCode를 사용한다고 가정
+ if (tagEntries.length > 0) {
+ const firstTag = tagEntries[0];
+
+ // tagType 조회 (TAG_TYPE_ID -> description)
+ let tagTypeDescription = firstTag.TAG_TYPE_ID; // 기본값
+ if (firstTag.TAG_TYPE_ID) {
+ const tagTypeRecord = await tx.select({ description: tagTypes.description })
+ .from(tagTypes)
+ .where(and(
+ eq(tagTypes.code, firstTag.TAG_TYPE_ID),
+ eq(tagTypes.projectId, projectId)
+ ))
+ .limit(1);
+
+ if (tagTypeRecord && tagTypeRecord.length > 0) {
+ tagTypeDescription = tagTypeRecord[0].description;
+ }
+ }
+
+ // tagClass 조회 (CLS_ID -> label)
+ let tagClassLabel = firstTag.CLS_ID; // 기본값
+ if (firstTag.CLS_ID) {
+ const tagClassRecord = await tx.select({ label: tagClasses.label })
+ .from(tagClasses)
+ .where(and(
+ eq(tagClasses.code, firstTag.CLS_ID),
+ eq(tagClasses.projectId, projectId)
+ ))
+ .limit(1);
+
+ if (tagClassRecord && tagClassRecord.length > 0) {
+ tagClassLabel = tagClassRecord[0].label;
+ }
+ }
+
+ // 태그 타입에 따른 폼 정보 가져오기
+ const allFormMappings = await getFormMappingsByTagType(
+ tagTypeDescription,
+ projectId,
+ tagClassLabel
+ );
+
+ // ep가 "IMEP"인 것만 필터링
+ const formMappings = allFormMappings?.filter(mapping => mapping.ep === "IMEP") || [];
+
+ // 현재 formCode와 일치하는 매핑 찾기
+ const targetFormMapping = formMappings.find(mapping => mapping.formCode === formCode);
+
+ if (targetFormMapping) {
+ console.log(`[IMPORT TAGS] Found IMEP form mapping for ${formCode}, creating form...`);
+
+ // form 생성
+ const insertResult = await tx
+ .insert(forms)
+ .values({
+ contractItemId: packageId,
+ formCode: targetFormMapping.formCode,
+ formName: targetFormMapping.formName,
+ eng: true, // ENG 모드에서 가져오는 것이므로 eng: true
+ im: targetFormMapping.ep === "IMEP" ? true:false
+ })
+ .returning({ id: forms.id });
+
+ formRecord = insertResult;
+ formCreated = true;
+
+ console.log(`[IMPORT TAGS] Successfully created form:`, insertResult[0]);
+ } else {
+ console.log(`[IMPORT TAGS] No IMEP form mapping found for formCode: ${formCode}`);
+ console.log(`[IMPORT TAGS] Available IMEP mappings:`, formMappings.map(m => m.formCode));
+ throw new Error(`Form ${formCode} not found and no IMEP mapping available for tag type ${tagTypeDescription}`);
+ }
+ } else {
+ throw new Error(`Form not found for formCode: ${formCode} and contractItemId: ${packageId}, and no tags to derive form mapping`);
}
+ } else {
+ console.log(`[IMPORT TAGS] Found existing form:`, formRecord[0].id);
- // 기본 태그 데이터 객체 생성
- const tagObject: any = {
- TAG_NO: tagEntry.TAG_NO,
- TAG_DESC: tagEntry.TAG_DESC || ""
- };
+ // 기존 form이 있는 경우 eng와 im 필드를 체크하고 업데이트
+ const existingForm = await tx.select({
+ eng: forms.eng,
+ im: forms.im
+ })
+ .from(forms)
+ .where(eq(forms.id, formRecord[0].id))
+ .limit(1);
- // ATTRIBUTES 필드에서 shi=true인 컬럼의 값 추출
- if (Array.isArray(tagEntry.ATTRIBUTES)) {
- for (const attr of tagEntry.ATTRIBUTES) {
- // 해당 어트리뷰트가 양식 메타에 있는지 확인
- const columnInfo = columnsJSON.find(col => col.key === attr.ATT_ID);
- if (columnInfo) {
- // shi가 true인 컬럼이거나 필수 컬럼만 처리
- if (columnInfo.shi === true) {
- // 값 타입에 따른 변환
+ if (existingForm.length > 0) {
+ // form mapping 정보 가져오기 (im 필드 업데이트를 위해)
+ let shouldUpdateIm = false;
+ let targetImValue = false;
+
+ // 첫 번째 태그의 정보를 사용해서 form mapping을 확인
+ if (tagEntries.length > 0) {
+ const firstTag = tagEntries[0];
+
+ // tagType 조회
+ let tagTypeDescription = firstTag.TAG_TYPE_ID;
+ if (firstTag.TAG_TYPE_ID) {
+ const tagTypeRecord = await tx.select({ description: tagTypes.description })
+ .from(tagTypes)
+ .where(and(
+ eq(tagTypes.code, firstTag.TAG_TYPE_ID),
+ eq(tagTypes.projectId, projectId)
+ ))
+ .limit(1);
+
+ if (tagTypeRecord && tagTypeRecord.length > 0) {
+ tagTypeDescription = tagTypeRecord[0].description;
+ }
+ }
+
+ // tagClass 조회
+ let tagClassLabel = firstTag.CLS_ID;
+ if (firstTag.CLS_ID) {
+ const tagClassRecord = await tx.select({ label: tagClasses.label })
+ .from(tagClasses)
+ .where(and(
+ eq(tagClasses.code, firstTag.CLS_ID),
+ eq(tagClasses.projectId, projectId)
+ ))
+ .limit(1);
+
+ if (tagClassRecord && tagClassRecord.length > 0) {
+ tagClassLabel = tagClassRecord[0].label;
+ }
+ }
+
+ // form mapping 정보 가져오기
+ const allFormMappings = await getFormMappingsByTagType(
+ tagTypeDescription,
+ projectId,
+ tagClassLabel
+ );
+
+ // ep가 "IMEP"인 것만 필터링
+ const formMappings = allFormMappings?.filter(mapping => mapping.ep === "IMEP") || [];
+
+ // 현재 formCode와 일치하는 매핑 찾기
+ const targetFormMapping = formMappings.find(mapping => mapping.formCode === formCode);
+
+ if (targetFormMapping) {
+ targetImValue = targetFormMapping.ep === "IMEP";
+ shouldUpdateIm = existingForm[0].im !== targetImValue;
+ }
+ }
+
+ // 업데이트할 필드들 준비
+ const updates: any = {};
+ let hasUpdates = false;
+
+ // eng 필드 체크
+ if (existingForm[0].eng !== true) {
+ updates.eng = true;
+ hasUpdates = true;
+ }
+
+ // im 필드 체크
+ if (shouldUpdateIm) {
+ updates.im = targetImValue;
+ hasUpdates = true;
+ }
+
+ // 업데이트 실행
+ if (hasUpdates) {
+ await tx
+ .update(forms)
+ .set(updates)
+ .where(eq(forms.id, formRecord[0].id));
+
+ console.log(`[IMPORT TAGS] Form ${formRecord[0].id} updated with:`, updates);
+ }
+ }
+ }
+
+ const formId = formRecord[0].id;
+
+ // 양식 메타데이터 가져오기
+ const formMetaRecord = await tx.select({ columns: formMetas.columns })
+ .from(formMetas)
+ .where(and(
+ eq(formMetas.projectId, projectId),
+ eq(formMetas.formCode, formCode)
+ ))
+ .limit(1);
+
+ if (!formMetaRecord || formMetaRecord.length === 0) {
+ throw new Error(`Form metadata not found for formCode: ${formCode} and projectId: ${projectId}`);
+ }
+
+ // 진행 상황 보고
+ if (progressCallback) progressCallback(30);
+
+ // 컬럼 정보 파싱
+ const columnsJSON: Column[] = (formMetaRecord[0].columns);
+
+ // 현재 formEntries 데이터 가져오기
+ const existingEntries = await tx.select({ id: formEntries.id, data: formEntries.data })
+ .from(formEntries)
+ .where(and(
+ eq(formEntries.formCode, formCode),
+ eq(formEntries.contractItemId, packageId)
+ ));
+
+ // 기존 tags 데이터 가져오기
+ const existingTags = await tx.select()
+ .from(tags)
+ .where(eq(tags.contractItemId, packageId));
+
+ // 진행 상황 보고
+ if (progressCallback) progressCallback(50);
+
+ // 기존 데이터를 맵으로 변환
+ const existingTagMap = new Map();
+ const existingTagsMap = new Map();
+
+ existingEntries.forEach(entry => {
+ const data = entry.data as any[];
+ data.forEach(item => {
+ if (item.TAG_NO) {
+ existingTagMap.set(item.TAG_NO, {
+ entryId: entry.id,
+ data: item
+ });
+ }
+ });
+ });
+
+ existingTags.forEach(tag => {
+ existingTagsMap.set(tag.tagNo, tag);
+ });
+
+ // 진행 상황 보고
+ if (progressCallback) progressCallback(60);
+
+ // 처리 결과 카운터
+ let processedCount = 0;
+ let excludedCount = 0;
+
+ // 새로운 태그 데이터와 업데이트할 데이터 준비
+ const newTagData: any[] = [];
+ const upsertTagRecords: any[] = []; // 새로 추가되거나 업데이트될 태그들
+ const updateData: {entryId: number, tagNo: string, updates: any}[] = [];
+
+ // SEDP 태그 데이터 처리
+ for (const tagEntry of tagEntries) {
+ try {
+ if (!tagEntry.TAG_NO) {
+ excludedCount++;
+ errors.push(`Missing TAG_NO in tag entry`);
+ continue;
+ }
+
+ // tagType 조회 (TAG_TYPE_ID -> description)
+ let tagTypeDescription = tagEntry.TAG_TYPE_ID; // 기본값
+ if (tagEntry.TAG_TYPE_ID) {
+ const tagTypeRecord = await tx.select({ description: tagTypes.description })
+ .from(tagTypes)
+ .where(and(
+ eq(tagTypes.code, tagEntry.TAG_TYPE_ID),
+ eq(tagTypes.projectId, projectId)
+ ))
+ .limit(1);
+
+ if (tagTypeRecord && tagTypeRecord.length > 0) {
+ tagTypeDescription = tagTypeRecord[0].description;
+ }
+ }
+
+ // tagClass 조회 (CLS_ID -> label)
+ let tagClassLabel = tagEntry.CLS_ID; // 기본값
+ if (tagEntry.CLS_ID) {
+ const tagClassRecord = await tx.select({ label: tagClasses.label })
+ .from(tagClasses)
+ .where(and(
+ eq(tagClasses.code, tagEntry.CLS_ID),
+ eq(tagClasses.projectId, projectId)
+ ))
+ .limit(1);
+
+ if (tagClassRecord && tagClassRecord.length > 0) {
+ tagClassLabel = tagClassRecord[0].label;
+ }
+ }
+
+ // 기본 태그 데이터 객체 생성 (formEntries용)
+ const tagObject: any = {
+ TAG_NO: tagEntry.TAG_NO,
+ TAG_DESC: tagEntry.TAG_DESC || "",
+ status: "From S-EDP" // SEDP에서 가져온 데이터임을 표시
+ };
+
+ // tags 테이블용 데이터 (UPSERT용)
+ const tagRecord = {
+ contractItemId: packageId,
+ formId: formId,
+ tagNo: tagEntry.TAG_NO,
+ tagType: tagTypeDescription,
+ class: tagClassLabel,
+ description: tagEntry.TAG_DESC || null,
+ createdAt: new Date(),
+ updatedAt: new Date()
+ };
+
+ // ATTRIBUTES 필드에서 shi=true인 컬럼의 값 추출
+ if (Array.isArray(tagEntry.ATTRIBUTES)) {
+ for (const attr of tagEntry.ATTRIBUTES) {
+ const columnInfo = columnsJSON.find(col => col.key === attr.ATT_ID);
+ if (columnInfo && columnInfo.shi === true) {
if (columnInfo.type === "NUMBER") {
- // // 먼저 VALUE_DBL이 있는지 확인
- // if (attr.VALUE_DBL !== undefined && attr.VALUE_DBL !== null) {
- // tagObject[attr.ATT_ID] = attr.VALUE_DBL;
- // }
- // VALUE_DBL이 없으면 VALUE 사용 시도
if (attr.VALUE !== undefined && attr.VALUE !== null) {
- // 문자열에서 숫자 추출
if (typeof attr.VALUE === 'string') {
- // 문자열에서 첫 번째 숫자 부분 추출
const numberMatch = attr.VALUE.match(/(-?\d+(\.\d+)?)/);
if (numberMatch) {
tagObject[attr.ATT_ID] = parseFloat(numberMatch[0]);
} else {
- // 숫자로 직접 변환 시도
const parsed = parseFloat(attr.VALUE);
if (!isNaN(parsed)) {
tagObject[attr.ATT_ID] = parsed;
}
}
} else if (typeof attr.VALUE === 'number') {
- // 이미 숫자인 경우
tagObject[attr.ATT_ID] = attr.VALUE;
}
}
} else if (attr.VALUE !== null && attr.VALUE !== undefined) {
- // 숫자 타입이 아닌 경우 VALUE 그대로 사용
tagObject[attr.ATT_ID] = attr.VALUE;
}
}
}
}
- }
- // 기존 태그가 있는지 확인하고 처리
- const existingTag = existingTagMap.get(tagEntry.TAG_NO);
- if (existingTag) {
- // 기존 태그가 있으면 업데이트할 필드 찾기
- const updates: any = {};
- let hasUpdates = false;
- // shi=true인 필드만 업데이트
- for (const key of Object.keys(tagObject)) {
- if (key === "TAG_NO") continue; // TAG_NO는 업데이트 안 함
-
- // TAG_DESC는 항상 업데이트
- if (key === "TAG_DESC" && tagObject[key] !== existingTag.data[key]) {
- updates[key] = tagObject[key];
- hasUpdates = true;
- continue;
- }
+ // 기존 태그가 있는지 확인하고 처리
+ const existingTag = existingTagMap.get(tagEntry.TAG_NO);
+
+ if (existingTag) {
+ // 기존 태그가 있으면 formEntries 업데이트 데이터 준비
+ const updates: any = {};
+ let hasUpdates = false;
- // 그 외 필드는 컬럼 정보에서 shi=true인 것만 업데이트
- const columnInfo = columnsJSON.find(col => col.key === key);
- if (columnInfo && columnInfo.shi === true) {
- if (existingTag.data[key] !== tagObject[key]) {
+ for (const key of Object.keys(tagObject)) {
+ if (key === "TAG_NO") continue;
+
+ if (key === "TAG_DESC" && tagObject[key] !== existingTag.data[key]) {
updates[key] = tagObject[key];
hasUpdates = true;
+ continue;
+ }
+
+ if (key === "status" && tagObject[key] !== existingTag.data[key]) {
+ updates[key] = tagObject[key];
+ hasUpdates = true;
+ continue;
+ }
+
+ const columnInfo = columnsJSON.find(col => col.key === key);
+ if (columnInfo && columnInfo.shi === true) {
+ if (existingTag.data[key] !== tagObject[key]) {
+ updates[key] = tagObject[key];
+ hasUpdates = true;
+ }
}
}
+
+ if (hasUpdates) {
+ updateData.push({
+ entryId: existingTag.entryId,
+ tagNo: tagEntry.TAG_NO,
+ updates
+ });
+ }
+ } else {
+ // 기존 태그가 없으면 새로 추가
+ newTagData.push(tagObject);
}
- // 업데이트할 내용이 있으면 추가
- if (hasUpdates) {
- updateData.push({
- entryId: existingTag.entryId,
- tagNo: tagEntry.TAG_NO,
- updates
- });
- }
+ // tags 테이블에는 항상 upsert (새로 추가되거나 업데이트)
+ upsertTagRecords.push(tagRecord);
+
+ processedCount++;
+ } catch (error) {
+ excludedCount++;
+ errors.push(`Error processing tag ${tagEntry.TAG_NO || 'unknown'}: ${error}`);
+ }
+ }
+
+ // 진행 상황 보고
+ if (progressCallback) progressCallback(80);
+
+ // formEntries 업데이트 실행
+ for (const update of updateData) {
+ try {
+ const entry = existingEntries.find(e => e.id === update.entryId);
+ if (!entry) continue;
+
+ const data = entry.data as any[];
+ const updatedData = data.map(item => {
+ if (item.TAG_NO === update.tagNo) {
+ return { ...item, ...update.updates };
+ }
+ return item;
+ });
+
+ await tx.update(formEntries)
+ .set({
+ data: updatedData,
+ updatedAt: new Date()
+ })
+ .where(eq(formEntries.id, update.entryId));
+ } catch (error) {
+ errors.push(`Error updating formEntry for tag ${update.tagNo}: ${error}`);
+ }
+ }
+
+ // 새 태그 추가 (formEntries)
+ if (newTagData.length > 0) {
+ if (existingEntries.length > 0) {
+ const firstEntry = existingEntries[0];
+ const existingData = firstEntry.data as any[];
+ const updatedData = [...existingData, ...newTagData];
+
+ await tx.update(formEntries)
+ .set({
+ data: updatedData,
+ updatedAt: new Date()
+ })
+ .where(eq(formEntries.id, firstEntry.id));
} else {
- // 기존 태그가 없으면 새로 추가
- newTagData.push(tagObject);
+ await tx.insert(formEntries)
+ .values({
+ formCode,
+ contractItemId: packageId,
+ data: newTagData,
+ createdAt: new Date(),
+ updatedAt: new Date()
+ });
}
-
- processedCount++;
- } catch (error) {
- excludedCount++;
- errors.push(`Error processing tag ${tagEntry.TAG_NO || 'unknown'}: ${error}`);
}
- }
-
- // 진행 상황 보고
- if (progressCallback) progressCallback(80);
-
- // 업데이트 실행
- for (const update of updateData) {
- try {
- const entry = existingEntries.find(e => e.id === update.entryId);
- if (!entry) continue;
+
+ // tags 테이블 처리 (INSERT + UPDATE 분리)
+ if (upsertTagRecords.length > 0) {
+ const newTagRecords: any[] = [];
+ const updateTagRecords: {tagId: number, updates: any}[] = [];
- const data = entry.data as any[];
- const updatedData = data.map(item => {
- if (item.TAG_NO === update.tagNo) {
- return { ...item, ...update.updates };
+ // 각 태그를 확인하여 신규/업데이트 분류
+ for (const tagRecord of upsertTagRecords) {
+ const existingTagRecord = existingTagsMap.get(tagRecord.tagNo);
+
+ if (existingTagRecord) {
+ // 기존 태그가 있으면 업데이트 준비
+ const tagUpdates: any = {};
+ let hasTagUpdates = false;
+
+ if (existingTagRecord.tagType !== tagRecord.tagType) {
+ tagUpdates.tagType = tagRecord.tagType;
+ hasTagUpdates = true;
+ }
+ if (existingTagRecord.class !== tagRecord.class) {
+ tagUpdates.class = tagRecord.class;
+ hasTagUpdates = true;
+ }
+ if (existingTagRecord.description !== tagRecord.description) {
+ tagUpdates.description = tagRecord.description;
+ hasTagUpdates = true;
+ }
+ if (existingTagRecord.formId !== tagRecord.formId) {
+ tagUpdates.formId = tagRecord.formId;
+ hasTagUpdates = true;
+ }
+
+ if (hasTagUpdates) {
+ updateTagRecords.push({
+ tagId: existingTagRecord.id,
+ updates: { ...tagUpdates, updatedAt: new Date() }
+ });
+ }
+ } else {
+ // 새로운 태그
+ newTagRecords.push(tagRecord);
}
- return item;
- });
+ }
- await db.update(formEntries)
- .set({
- data: updatedData,
- updatedAt: new Date()
- })
- .where(eq(formEntries.id, update.entryId));
- } catch (error) {
- errors.push(`Error updating tag ${update.tagNo}: ${error}`);
- }
- }
-
- // 새 태그 추가
- if (newTagData.length > 0) {
- // 기존 엔트리가 있으면 첫 번째 것에 추가
- if (existingEntries.length > 0) {
- const firstEntry = existingEntries[0];
- const existingData = firstEntry.data as any[];
- const updatedData = [...existingData, ...newTagData];
+ // 새 태그 삽입
+ if (newTagRecords.length > 0) {
+ try {
+ await tx.insert(tags)
+ .values(newTagRecords)
+ .onConflictDoNothing({
+ target: [tags.contractItemId, tags.tagNo]
+ });
+ } catch (error) {
+ // 개별 삽입으로 재시도
+ for (const tagRecord of newTagRecords) {
+ try {
+ await tx.insert(tags)
+ .values(tagRecord)
+ .onConflictDoNothing({
+ target: [tags.contractItemId, tags.tagNo]
+ });
+ } catch (individualError) {
+ errors.push(`Error inserting tag ${tagRecord.tagNo}: ${individualError}`);
+ }
+ }
+ }
+ }
- await db.update(formEntries)
- .set({
- data: updatedData,
- updatedAt: new Date()
- })
- .where(eq(formEntries.id, firstEntry.id));
- } else {
- // 기존 엔트리가 없으면 새로 생성
- await db.insert(formEntries)
- .values({
- formCode,
- contractItemId: packageId,
- data: newTagData,
- createdAt: new Date(),
- updatedAt: new Date()
- });
+ // 기존 태그 업데이트
+ for (const update of updateTagRecords) {
+ try {
+ await tx.update(tags)
+ .set(update.updates)
+ .where(eq(tags.id, update.tagId));
+ } catch (error) {
+ errors.push(`Error updating tag record ${update.tagId}: ${error}`);
+ }
+ }
}
- }
-
- // 진행 상황 보고
- if (progressCallback) progressCallback(100);
+
+ // 진행 상황 보고
+ if (progressCallback) progressCallback(100);
+
+ // 최종 결과 반환
+ return {
+ processedCount,
+ excludedCount,
+ totalEntries: tagEntries.length,
+ formCreated, // 새로 추가: form이 생성되었는지 여부
+ errors: errors.length > 0 ? errors : undefined
+ };
+ });
- // 최종 결과 반환
- return {
- processedCount,
- excludedCount,
- totalEntries: tagEntries.length,
- errors: errors.length > 0 ? errors : undefined
- };
} catch (error: any) {
console.error("Tag import error:", error);
throw error;
diff --git a/lib/sedp/get-tags.ts b/lib/sedp/get-tags.ts
index 7021d7d2..00916eb2 100644
--- a/lib/sedp/get-tags.ts
+++ b/lib/sedp/get-tags.ts
@@ -3,6 +3,7 @@ import {
contractItems,
tags,
forms,
+ formEntries, // 추가
items,
tagTypeClassFormMappings,
projects,
@@ -11,6 +12,7 @@ import {
contracts
} from "@/db/schema";
import { eq, and, like, inArray } from "drizzle-orm";
+import { revalidateTag } from "next/cache"; // 추가
import { getSEDPToken } from "./sedp-token";
/**
@@ -240,6 +242,9 @@ export async function importTagsFromSEDP(
insertValues.eng = true;
} else if (mode === "IM") {
insertValues.im = true;
+ if (mapping.remark && mapping.remark.includes("VD_")) {
+ insertValues.eng = true;
+ }
}
}
@@ -289,8 +294,6 @@ export async function importTagsFromSEDP(
try {
// Step 5: Call the external API to get tag data
-
-
const tagData = await fetchTagDataFromSEDP(projectCode, baseMappings[0].formCode);
// 진행 상황 보고
@@ -309,13 +312,21 @@ export async function importTagsFromSEDP(
const tagEntries = tagData[tableName];
if (!Array.isArray(tagEntries) || tagEntries.length === 0) {
- allErrors.push(`No tag data found in the API response for formCode ${baseFormCode}`);
+ allErrors.push(`No tag data found in the API response for formCode ${baseMappings[0].formCode}`);
continue; // 다음 매핑으로 진행
}
const entriesCount = tagEntries.length;
totalEntriesCount += entriesCount;
+ // formEntries를 위한 데이터 수집
+ const newTagsForFormEntry: Array<{
+ TAG_NO: string;
+ TAG_DESC: string | null;
+ status: string;
+ [key: string]: any;
+ }> = [];
+
// Process each tag entry
for (let i = 0; i < tagEntries.length; i++) {
try {
@@ -371,6 +382,24 @@ export async function importTagsFromSEDP(
}
});
+ // formEntries용 데이터 수집
+ const tagDataForFormEntry = {
+ TAG_NO: entry.TAG_NO,
+ TAG_DESC: entry.TAG_DESC || null,
+ status:"From S-EDP" // SEDP에서 가져온 데이터임을 표시
+ };
+
+ // ATTRIBUTES가 있으면 추가 (SHI 필드들)
+ if (Array.isArray(entry.ATTRIBUTES)) {
+ for (const attr of entry.ATTRIBUTES) {
+ if (attr.ATT_ID && attr.VALUE !== null && attr.VALUE !== undefined) {
+ tagDataForFormEntry[attr.ATT_ID] = attr.VALUE;
+ }
+ }
+ }
+
+ newTagsForFormEntry.push(tagDataForFormEntry);
+
processedCount++;
totalProcessedCount++;
@@ -386,6 +415,87 @@ export async function importTagsFromSEDP(
}
}
+ // Step 7: formEntries 업데이트
+ if (newTagsForFormEntry.length > 0) {
+ try {
+ // 기존 formEntry 가져오기
+ const existingEntry = await db.query.formEntries.findFirst({
+ where: and(
+ eq(formEntries.formCode, formCode),
+ eq(formEntries.contractItemId, packageId)
+ )
+ });
+
+ if (existingEntry && existingEntry.id) {
+ // 기존 formEntry가 있는 경우
+ let existingData: Array<{
+ TAG_NO: string;
+ TAG_DESC?: string;
+ status?: string;
+ [key: string]: any;
+ }> = [];
+
+ if (Array.isArray(existingEntry.data)) {
+ existingData = existingEntry.data;
+ }
+
+ // 기존 TAG_NO들 추출
+ const existingTagNos = new Set(existingData.map(item => item.TAG_NO));
+
+ // 중복되지 않은 새 태그들만 필터링
+ const newUniqueTagsData = newTagsForFormEntry.filter(
+ tagData => !existingTagNos.has(tagData.TAG_NO)
+ );
+
+ // 기존 태그들의 status와 ATTRIBUTES 업데이트
+ const updatedExistingData = existingData.map(existingItem => {
+ const newTagData = newTagsForFormEntry.find(
+ newItem => newItem.TAG_NO === existingItem.TAG_NO
+ );
+
+ if (newTagData) {
+ // 기존 태그가 있으면 SEDP 데이터로 업데이트
+ return {
+ ...existingItem,
+ ...newTagData,
+ TAG_NO: existingItem.TAG_NO // TAG_NO는 유지
+ };
+ }
+
+ return existingItem;
+ });
+
+ const finalData = [...updatedExistingData, ...newUniqueTagsData];
+
+ await db
+ .update(formEntries)
+ .set({
+ data: finalData,
+ updatedAt: new Date()
+ })
+ .where(eq(formEntries.id, existingEntry.id));
+
+ console.log(`[IMPORT SEDP] Updated formEntry with ${newUniqueTagsData.length} new tags, updated ${updatedExistingData.length - newUniqueTagsData.length} existing tags for form ${formCode}`);
+ } else {
+ // formEntry가 없는 경우 새로 생성
+ await db.insert(formEntries).values({
+ formCode: formCode,
+ contractItemId: packageId,
+ data: newTagsForFormEntry,
+ createdAt: new Date(),
+ updatedAt: new Date(),
+ });
+
+ console.log(`[IMPORT SEDP] Created new formEntry with ${newTagsForFormEntry.length} tags for form ${formCode}`);
+ }
+
+ // 캐시 무효화
+ revalidateTag(`form-data-${formCode}-${packageId}`);
+ } catch (formEntryError) {
+ console.error(`[IMPORT SEDP] Error updating formEntry for form ${formCode}:`, formEntryError);
+ allErrors.push(`Error updating formEntry for form ${formCode}: ${formEntryError}`);
+ }
+ }
} catch (error: any) {
console.error(`Error processing mapping for formCode ${formCode}:`, error);
diff --git a/lib/sedp/sync-package.ts b/lib/sedp/sync-package.ts
new file mode 100644
index 00000000..c8f39ad8
--- /dev/null
+++ b/lib/sedp/sync-package.ts
@@ -0,0 +1,282 @@
+"use server"
+// src/lib/cron/syncItemsFromCodeLists.ts
+import db from "@/db/db";
+import { projects, items } from '@/db/schema';
+import { eq } from 'drizzle-orm';
+import { getSEDPToken } from "./sedp-token";
+
+const SEDP_API_BASE_URL = process.env.SEDP_API_BASE_URL || 'http://sedpwebapi.ship.samsung.co.kr/api';
+
+async function getCodeLists(projectCode: string): Promise<Map<string, CodeList>> {
+ try {
+ // 토큰(API 키) 가져오기
+ const apiKey = await getSEDPToken();
+
+ const response = await fetch(
+ `${SEDP_API_BASE_URL}/CodeList/Get`,
+ {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ 'accept': '*/*',
+ 'ApiKey': apiKey,
+ 'ProjectNo': projectCode
+ },
+ body: JSON.stringify({
+ ProjectNo: projectCode,
+ ContainDeleted: false
+ })
+ }
+ );
+
+ if (!response.ok) {
+ throw new Error(`코드 리스트 요청 실패: ${response.status} ${response.statusText}`);
+ }
+
+ // 안전하게 JSON 파싱
+ try {
+ const data = await response.json();
+
+ // 데이터가 배열인지 확인
+ const codeLists: CodeList[] = Array.isArray(data) ? data : [data];
+
+ // CL_ID로 효율적인 조회를 위한 맵 생성
+ const codeListMap = new Map<string, CodeList>();
+ for (const codeList of codeLists) {
+ if (!codeList.DELETED) {
+ codeListMap.set(codeList.CL_ID, codeList);
+ }
+ }
+
+ console.log(`프로젝트 ${projectCode}에서 ${codeListMap.size}개의 코드 리스트를 가져왔습니다`);
+ return codeListMap;
+
+ } catch (parseError) {
+ console.error(`프로젝트 ${projectCode}의 코드 리스트 응답 파싱 실패:`, parseError);
+ // 응답 내용 로깅
+ try {
+ const text = await response.clone().text();
+ console.log(`응답 내용: ${text.substring(0, 200)}${text.length > 200 ? '...' : ''}`);
+ } catch (textError) {
+ console.error('응답 내용 로깅 실패:', textError);
+ }
+ return new Map();
+ }
+ } catch (error) {
+ console.error(`프로젝트 ${projectCode}의 코드 리스트 가져오기 실패:`, error);
+ return new Map();
+ }
+ }
+
+
+interface CodeValue {
+ VALUE: string;
+ DESCC: string;
+ ATTRIBUTES: Array<{
+ ATT_ID: string;
+ VALUE: string;
+ }>;
+}
+
+interface CodeList {
+ PROJ_NO: string;
+ CL_ID: string;
+ DESC: string;
+ REMARK: string | null;
+ PRNT_CD_ID: string | null;
+ REG_TYPE_ID: string | null;
+ VAL_ATT_ID: string | null;
+ VALUES: CodeValue[];
+ LNK_ATT: any[];
+ DELETED: boolean;
+ CRTER_NO: string;
+ CRTE_DTM: string;
+ CHGER_NO: string | null;
+ CHGE_DTM: string | null;
+ _id: string;
+}
+
+export async function syncItemsFromCodeLists(): Promise<void> {
+ try {
+ console.log('아이템 동기화 시작...');
+
+ // 모든 프로젝트 가져오기
+ const allProjects = await db.select().from(projects);
+ console.log(`총 ${allProjects.length}개의 프로젝트를 처리합니다.`);
+
+ let totalItemsProcessed = 0;
+ let totalItemsInserted = 0;
+ let totalItemsUpdated = 0;
+
+ for (const project of allProjects) {
+ try {
+ console.log(`프로젝트 ${project.code} (${project.name}) 처리 중...`);
+
+ // 프로젝트의 코드리스트 가져오기
+ const codeListMap = await getCodeLists(project.code);
+
+ // PKG_NO 코드리스트 찾기
+ const pkgNoCodeList = codeListMap.get('PKG_NO');
+
+ if (!pkgNoCodeList) {
+ console.log(`프로젝트 ${project.code}에서 PKG_NO 코드리스트를 찾을 수 없습니다.`);
+ continue;
+ }
+
+ console.log(`프로젝트 ${project.code}에서 ${pkgNoCodeList.VALUES.length}개의 아이템을 처리합니다.`);
+
+ // VALUES 배열 순회하며 items 테이블에 삽입/업데이트
+ for (const codeValue of pkgNoCodeList.VALUES) {
+ try {
+ // ATTRIBUTES에서 필요한 값들 추출
+ const packageCodeAttr = codeValue.ATTRIBUTES?.find(attr => attr.ATT_ID === 'SHI_PACK_NO');
+ const smCodeAttr = codeValue.ATTRIBUTES?.find(attr => attr.ATT_ID === 'SM_code');
+
+ const itemData = {
+ ProjectNo: project.code,
+ itemCode: codeValue.VALUE,
+ itemName: codeValue.DESCC || '',
+ packageCode: packageCodeAttr?.VALUE || '',
+ smCode: smCodeAttr?.VALUE || null,
+ description: null, // 필요시 추가 매핑
+ parentItemCode: null, // 필요시 추가 매핑
+ itemLevel: null, // 필요시 추가 매핑
+ deleteFlag: 'N', // 기본값
+ unitOfMeasure: null, // 필요시 추가 매핑
+ steelType: null, // 필요시 추가 매핑
+ gradeMaterial: null, // 필요시 추가 매핑
+ changeDate: null, // 필요시 추가 매핑
+ baseUnitOfMeasure: null, // 필요시 추가 매핑
+ updatedAt: new Date()
+ };
+
+ // 기존 아이템 확인 (itemCode로 조회)
+ const existingItem = await db.select()
+ .from(items)
+ .where(eq(items.itemCode, codeValue.VALUE))
+ .limit(1);
+
+ if (existingItem.length > 0) {
+ // 기존 아이템 업데이트
+ await db.update(items)
+ .set(itemData)
+ .where(eq(items.itemCode, codeValue.VALUE));
+ totalItemsUpdated++;
+ } else {
+ // 새 아이템 삽입
+ await db.insert(items).values(itemData);
+ totalItemsInserted++;
+ }
+
+ totalItemsProcessed++;
+ } catch (itemError) {
+ console.error(`아이템 ${codeValue.VALUE} 처리 중 오류:`, itemError);
+ }
+ }
+
+ console.log(`프로젝트 ${project.code} 완료`);
+ } catch (projectError) {
+ console.error(`프로젝트 ${project.code} 처리 중 오류:`, projectError);
+ }
+ }
+
+ console.log(`아이템 동기화 완료:`);
+ console.log(`- 총 처리된 아이템: ${totalItemsProcessed}개`);
+ console.log(`- 새로 삽입된 아이템: ${totalItemsInserted}개`);
+ console.log(`- 업데이트된 아이템: ${totalItemsUpdated}개`);
+
+ } catch (error) {
+ console.error('아이템 동기화 중 전체 오류:', error);
+ throw error;
+ }
+}
+
+// 특정 프로젝트만 동기화하는 함수
+export async function syncItemsForProject(projectCode: string): Promise<void> {
+ try {
+ console.log(`프로젝트 ${projectCode}의 아이템 동기화 시작...`);
+
+ // 프로젝트 존재 확인
+ const project = await db.select()
+ .from(projects)
+ .where(eq(projects.code, projectCode))
+ .limit(1);
+
+ if (project.length === 0) {
+ throw new Error(`프로젝트 ${projectCode}를 찾을 수 없습니다.`);
+ }
+
+ // 프로젝트의 코드리스트 가져오기
+ const codeListMap = await getCodeLists(projectCode);
+
+ // PKG_NO 코드리스트 찾기
+ const pkgNoCodeList = codeListMap.get('PKG_NO');
+
+ if (!pkgNoCodeList) {
+ console.log(`프로젝트 ${projectCode}에서 PKG_NO 코드리스트를 찾을 수 없습니다.`);
+ return;
+ }
+
+ console.log(`${pkgNoCodeList.VALUES.length}개의 아이템을 처리합니다.`);
+
+ let itemsProcessed = 0;
+ let itemsInserted = 0;
+ let itemsUpdated = 0;
+
+ // VALUES 배열 순회하며 items 테이블에 삽입/업데이트
+ for (const codeValue of pkgNoCodeList.VALUES) {
+ try {
+ // ATTRIBUTES에서 필요한 값들 추출
+ const packageCodeAttr = codeValue.ATTRIBUTES?.find(attr => attr.ATT_ID === 'SHI_PACK_NO');
+ const smCodeAttr = codeValue.ATTRIBUTES?.find(attr => attr.ATT_ID === 'SM_code');
+
+ const itemData = {
+ ProjectNo: projectCode,
+ itemCode: codeValue.VALUE,
+ itemName: codeValue.DESCC || '',
+ packageCode: packageCodeAttr?.VALUE || '',
+ smCode: smCodeAttr?.VALUE || null,
+ description: null,
+ parentItemCode: null,
+ itemLevel: null,
+ deleteFlag: 'N',
+ unitOfMeasure: null,
+ steelType: null,
+ gradeMaterial: null,
+ changeDate: null,
+ baseUnitOfMeasure: null,
+ updatedAt: new Date()
+ };
+
+ // 기존 아이템 확인
+ const existingItem = await db.select()
+ .from(items)
+ .where(eq(items.itemCode, codeValue.VALUE))
+ .limit(1);
+
+ if (existingItem.length > 0) {
+ await db.update(items)
+ .set(itemData)
+ .where(eq(items.itemCode, codeValue.VALUE));
+ itemsUpdated++;
+ } else {
+ await db.insert(items).values(itemData);
+ itemsInserted++;
+ }
+
+ itemsProcessed++;
+ } catch (itemError) {
+ console.error(`아이템 ${codeValue.VALUE} 처리 중 오류:`, itemError);
+ }
+ }
+
+ console.log(`프로젝트 ${projectCode} 아이템 동기화 완료:`);
+ console.log(`- 처리된 아이템: ${itemsProcessed}개`);
+ console.log(`- 새로 삽입된 아이템: ${itemsInserted}개`);
+ console.log(`- 업데이트된 아이템: ${itemsUpdated}개`);
+
+ } catch (error) {
+ console.error(`프로젝트 ${projectCode} 아이템 동기화 중 오류:`, error);
+ throw error;
+ }
+} \ No newline at end of file