summaryrefslogtreecommitdiff
path: root/lib/sedp
diff options
context:
space:
mode:
Diffstat (limited to 'lib/sedp')
-rw-r--r--lib/sedp/get-tags-plant.ts1013
1 files changed, 470 insertions, 543 deletions
diff --git a/lib/sedp/get-tags-plant.ts b/lib/sedp/get-tags-plant.ts
index be0e398b..f804ebe9 100644
--- a/lib/sedp/get-tags-plant.ts
+++ b/lib/sedp/get-tags-plant.ts
@@ -3,651 +3,578 @@ import {
tagsPlant,
formsPlant,
formEntriesPlant,
- items,
- tagTypeClassFormMappings,
projects,
tagTypes,
tagClasses,
} from "@/db/schema";
-import { eq, and, like, inArray } from "drizzle-orm";
-import { revalidateTag } from "next/cache"; // 추가
+import { eq, and } from "drizzle-orm";
+import { revalidatePath } from "next/cache";
import { getSEDPToken } from "./sedp-token";
-/**
- * 태그 가져오기 서비스 함수
- * contractItemId(packageId)를 기반으로 외부 시스템에서 태그 데이터를 가져와 DB에 저장
- * TAG_IDX를 기준으로 태그를 식별합니다.
- *
- * @param projectCode 계약 아이템 ID (contractItemId)
- * @param packageCode 계약 아이템 ID (contractItemId)
- * @param progressCallback 진행 상황을 보고하기 위한 콜백 함수
- * @returns 처리 결과 정보 (처리된 태그 수, 오류 목록 등)
- */
+const SEDP_API_BASE_URL = process.env.SEDP_API_BASE_URL || 'http://sedpwebapi.ship.samsung.co.kr/api';
+
+// ============ 타입 정의 ============
+interface newRegister {
+ PROJ_NO: string;
+ MAP_ID: string;
+ EP_ID: string;
+ DESC: string;
+ CATEGORY: string;
+ BYPASS: boolean;
+ REG_TYPE_ID: string;
+ TOOL_ID: string;
+ TOOL_TYPE: string;
+ SCOPES: string[];
+ MAP_CLS: {
+ TOOL_ATT_NAME: string;
+ ITEMS: any[];
+ };
+ MAP_ATT: any[];
+ MAP_TMPLS: string[];
+ CRTER_NO: string;
+ CRTE_DTM: string;
+ CHGER_NO: string;
+ _id: string;
+}
+
+interface Register {
+ PROJ_NO: string;
+ TYPE_ID: string;
+ EP_ID: string;
+ DESC: string;
+ REMARK: string | null;
+ NEW_TAG_YN: boolean;
+ ALL_TAG_YN: boolean;
+ VND_YN: boolean;
+ SEQ: number;
+ CMPLX_YN: boolean;
+ CMPL_SETT: any | null;
+ MAP_ATT: any[];
+ MAP_CLS_ID: string[];
+ MAP_OPER: any | null;
+ LNK_ATT: any[];
+ JOIN_TABLS: any[];
+ DELETED: boolean;
+ CRTER_NO: string;
+ CRTE_DTM: string;
+ CHGER_NO: string | null;
+ CHGE_DTM: string | null;
+ _id: string;
+}
+
+interface FormInfo {
+ formCode: string;
+ formName: string;
+ im: boolean;
+ eng: boolean;
+}
+
+// ============ API 호출 함수들 ============
+
+async function getNewRegisters(projectCode: string): Promise<newRegister[]> {
+ const apiKey = await getSEDPToken();
+
+ const response = await fetch(
+ `${SEDP_API_BASE_URL}/AdapterDataMapping/GetByToolID`,
+ {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ 'accept': '*/*',
+ 'ApiKey': apiKey,
+ 'ProjectNo': projectCode
+ },
+ body: JSON.stringify({
+ ProjectNo: projectCode,
+ TOOL_ID: "eVCP"
+ })
+ }
+ );
+
+ if (!response.ok) {
+ throw new Error(`새 레지스터 요청 실패: ${response.status} ${response.statusText}`);
+ }
+
+ const data = await response.json();
+ const registers: newRegister[] = Array.isArray(data) ? data : [data];
+
+ console.log(`[getNewRegisters] 프로젝트 ${projectCode}에서 ${registers.length}개의 레지스터를 가져왔습니다.`);
+ return registers;
+}
+
+async function getRegisters(projectCode: string): Promise<Register[]> {
+ const apiKey = await getSEDPToken();
+
+ const response = await fetch(
+ `${SEDP_API_BASE_URL}/Register/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}`);
+ }
+
+ const data = await response.json();
+ const registers: Register[] = Array.isArray(data) ? data : [data];
+
+ console.log(`[getRegisters] 프로젝트 ${projectCode}에서 ${registers.length}개의 레지스터를 가져왔습니다.`);
+ return registers;
+}
+
+async function fetchTagDataFromSEDP(projectCode: string, formCode: string): Promise<any> {
+ const apiKey = await getSEDPToken();
+
+ const response = await fetch(
+ `${SEDP_API_BASE_URL}/Data/GetPubData`,
+ {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ 'accept': '*/*',
+ 'ApiKey': apiKey,
+ 'ProjectNo': projectCode
+ },
+ body: JSON.stringify({
+ ProjectNo: projectCode,
+ REG_TYPE_ID: formCode,
+ ContainDeleted: false
+ })
+ }
+ );
+
+ if (!response.ok) {
+ const errorText = await response.text();
+ throw new Error(`SEDP API 요청 실패: ${response.status} ${response.statusText} - ${errorText}`);
+ }
+
+ return await response.json();
+}
+
+async function getRegisterDetail(projectCode: string, formCode: string): Promise<Register | null> {
+ const apiKey = await getSEDPToken();
+
+ const response = await fetch(
+ `${SEDP_API_BASE_URL}/Register/GetByID`,
+ {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ 'accept': '*/*',
+ 'ApiKey': apiKey,
+ 'ProjectNo': projectCode
+ },
+ body: JSON.stringify({
+ ProjectNo: projectCode,
+ TYPE_ID: formCode,
+ ContainDeleted: false
+ })
+ }
+ );
+
+ if (!response.ok) {
+ console.error(`Register detail 요청 실패: ${formCode}`);
+ return null;
+ }
+
+ return await response.json();
+}
+
+// ============ 메인 함수 ============
+
export async function importTagsFromSEDP(
projectCode: string,
packageCode: string,
- progressCallback?: (progress: number) => void,
- mode?: string
+ progressCallback?: (progress: number) => void
): Promise<{
processedCount: number;
excludedCount: number;
totalEntries: number;
errors?: string[];
}> {
+ const allErrors: string[] = [];
+ let totalProcessedCount = 0;
+ let totalExcludedCount = 0;
+ let totalEntriesCount = 0;
+
try {
- // 진행 상황 보고
if (progressCallback) progressCallback(5);
+ // Step 1: 프로젝트 ID 조회
const project = await db.query.projects.findFirst({
where: eq(projects.code, projectCode),
- columns: {
- id: true
- }
+ columns: { id: true }
});
+ if (!project) {
+ throw new Error(`Project not found: ${projectCode}`);
+ }
+ const projectId = project.id;
+
+ if (progressCallback) progressCallback(10);
- // 프로젝트 ID 획득
- const projectId = project?.id;
+ // Step 2: 두 API 동시 호출
+ const [newRegisters, registers] = await Promise.all([
+ getNewRegisters(projectCode),
+ getRegisters(projectCode)
+ ]);
- // Step 1-2: Get the item using itemId from contractItem
- const item = await db.query.items.findFirst({
- where: and(eq(items.ProjectNo, projectCode), eq(items.packageCode, packageCode))
+ if (progressCallback) progressCallback(20);
+
+ // ======== 서브클래스 매핑을 위한 태그 클래스 로드 ========
+ const allTagClasses = await db.query.tagClasses.findMany({
+ where: eq(tagClasses.projectId, projectId)
});
- if (!item) {
- throw new Error(`Item with ID ${item?.id} not found`);
+ // 클래스 코드로 빠른 조회를 위한 Map
+ const tagClassByCode = new Map(allTagClasses.map(tc => [tc.code, tc]));
+
+ // 서브클래스 코드로 부모 클래스 찾기 위한 Map
+ const parentBySubclassCode = new Map<string, typeof allTagClasses[0]>();
+ for (const tc of allTagClasses) {
+ if (tc.subclasses && Array.isArray(tc.subclasses)) {
+ for (const sub of tc.subclasses as { id: string; desc: string }[]) {
+ parentBySubclassCode.set(sub.id, tc);
+ }
+ }
}
- const itemCode = item.itemCode;
+ console.log(`[importTagsFromSEDP] 태그 클래스 ${allTagClasses.length}개 로드, 서브클래스 매핑 ${parentBySubclassCode.size}개 생성`);
+ // ======== 서브클래스 매핑 준비 완료 ========
- // 진행 상황 보고
- if (progressCallback) progressCallback(10);
+ // Step 3: packageCode에 해당하는 폼 정보 추출
+ const formsToProcess: FormInfo[] = [];
- // 기본 매핑 검색 - 모든 모드에서 사용
- const baseMappings = await db.query.tagTypeClassFormMappings.findMany({
- where: and(
- like(tagTypeClassFormMappings.remark, `%${itemCode}%`),
- eq(tagTypeClassFormMappings.projectId, projectId)
- )
- });
-
- if (baseMappings.length === 0) {
- throw new Error(`No mapping found for item code ${itemCode}`);
+ // Register 정보를 Map으로 변환 (TYPE_ID로 빠른 조회)
+ const registerMap = new Map<string, Register>();
+ for (const reg of registers) {
+ registerMap.set(reg.TYPE_ID, reg);
}
- // Step 2: Find the mapping entries - 모드에 따라 다른 조건 적용
- let mappings = [];
-
- if (mode === 'IM') {
- // IM 모드일 때는 먼저 SEDP에서 태그 데이터를 가져와 TAG_TYPE_ID 리스트 확보
-
- // 프로젝트 코드 가져오기
- const project = await db.query.projects.findFirst({
- where: eq(projects.id, projectId)
- });
-
- if (!project) {
- throw new Error(`Project with ID ${projectId} not found`);
- }
-
- // 각 매핑의 formCode에 대해 태그 데이터 조회
- const tagTypeIds = new Set<string>();
-
- for (const mapping of baseMappings) {
- try {
- // SEDP에서 태그 데이터 가져오기
- const tagData = await fetchTagDataFromSEDP(project.code, mapping.formCode);
-
- // 첫 번째 키를 테이블 이름으로 사용
- const tableName = Object.keys(tagData)[0];
- const tagEntries = tagData[tableName];
-
- if (Array.isArray(tagEntries)) {
- // 모든 태그에서 TAG_TYPE_ID 수집
- for (const entry of tagEntries) {
- if (entry.TAG_TYPE_ID && entry.TAG_TYPE_ID !== "") {
- tagTypeIds.add(entry.TAG_TYPE_ID);
- }
- }
- }
- } catch (error) {
- console.error(`Error fetching tag data for formCode ${mapping.formCode}:`, error);
- }
- }
-
- if (tagTypeIds.size === 0) {
- throw new Error('No valid TAG_TYPE_ID found in SEDP tag data');
- }
-
- // 수집된 TAG_TYPE_ID로 tagTypes에서 정보 조회
- const tagTypeInfo = await db.query.tagTypes.findMany({
- where: and(
- inArray(tagTypes.code, Array.from(tagTypeIds)),
- eq(tagTypes.projectId, projectId)
- )
- });
-
- if (tagTypeInfo.length === 0) {
- throw new Error('No matching tag types found for the collected TAG_TYPE_IDs');
- }
-
- // 태그 타입 설명 수집
- const tagLabels = tagTypeInfo.map(tt => tt.description);
-
- // IM 모드에 맞는 매핑 조회 - ep가 "IMEP"인 항목만
- mappings = await db.query.tagTypeClassFormMappings.findMany({
- where: and(
- inArray(tagTypeClassFormMappings.tagTypeLabel, tagLabels),
- eq(tagTypeClassFormMappings.projectId, projectId),
- eq(tagTypeClassFormMappings.ep, "IMEP")
- )
- });
-
- } else {
- // ENG 모드 또는 기본 모드일 때 - 기본 매핑 사용
- mappings = [...baseMappings];
-
- // ENG 모드에서는 ep 필드가 "IMEP"가 아닌 매핑만 필터링
- if (mode === 'ENG') {
- mappings = mappings.filter(mapping => mapping.ep !== "IMEP");
+ // newRegisters에서 packageCode가 SCOPES에 포함된 것 필터링
+ for (const newReg of newRegisters) {
+ if (newReg.SCOPES && newReg.SCOPES.includes(packageCode)) {
+ const formCode = newReg.REG_TYPE_ID;
+ const formName = newReg.DESC;
+
+ // Register에서 EP_ID 확인하여 im/eng 결정
+ const register = registerMap.get(formCode);
+ const isIM = register?.EP_ID === "IMEP";
+
+ formsToProcess.push({
+ formCode,
+ formName,
+ im: isIM,
+ eng: !isIM
+ });
}
}
- // 매핑이 없는 경우 모드에 따라 다른 오류 메시지 사용
- if (mappings.length === 0) {
- if (mode === 'IM') {
- throw new Error('No suitable mappings found for IM mode');
- } else {
- throw new Error(`No mapping found for item code ${itemCode}`);
- }
+ if (formsToProcess.length === 0) {
+ throw new Error(`No forms found for packageCode: ${packageCode}`);
}
-
- // 진행 상황 보고
- if (progressCallback) progressCallback(15);
-
- // 결과 누적을 위한 변수들 초기화
- let totalProcessedCount = 0;
- let totalExcludedCount = 0;
- let totalEntriesCount = 0;
- const allErrors: string[] = [];
-
- // 각 매핑에 대해 처리
- for (let mappingIndex = 0; mappingIndex < mappings.length; mappingIndex++) {
- const mapping = mappings[mappingIndex];
-
- // Step 3: Get the project code
- const project = await db.query.projects.findFirst({
- where: eq(projects.id, mapping.projectId)
- });
-
- if (!project) {
- allErrors.push(`Project with ID ${mapping.projectId} not found`);
- continue; // 다음 매핑으로 진행
- }
- // IM 모드에서는 baseMappings에서 같은 formCode를 가진 매핑을 찾음
- let formCode = mapping.formCode;
- if (mode === 'IM') {
- // baseMapping에서 동일한 formCode를 가진 매핑 찾기
- const originalMapping = baseMappings.find(
- baseMapping => baseMapping.formCode === mapping.formCode
- );
-
- // 찾았으면 해당 formCode 사용, 못 찾았으면 현재 매핑의 formCode 유지
- if (originalMapping) {
- formCode = originalMapping.formCode;
- }
- }
+ console.log(`[importTagsFromSEDP] ${formsToProcess.length}개의 폼을 처리합니다.`);
- // 진행 상황 보고 - 매핑별 진행률 조정
- if (progressCallback) {
- const baseProgress = 15;
- const mappingProgress = Math.floor(15 * (mappingIndex + 1) / mappings.length);
- progressCallback(baseProgress + mappingProgress);
- }
+ if (progressCallback) progressCallback(25);
- // Step 4: Find the form ID
- const form = await db.query.formsPlant.findFirst({
- where: and(
- eq(formsPlant.projectCode, projectCode),
- eq(formsPlant.formCode, formCode),
- eq(formsPlant.packageCode, packageCode)
- )
- });
-
- let formId;
-
- // If form doesn't exist, create it
- if (!form) {
- // 폼이 없는 경우 새로 생성 - 모드에 따른 필드 설정
- const insertValues: any = {
- projectCode,
- packageCode,
- formCode: formCode,
- formName: mapping.formName
- };
-
- // 모드 정보가 있으면 해당 필드 설정
- if (mode) {
- if (mode === "ENG") {
- insertValues.eng = true;
- } else if (mode === "IM") {
- insertValues.im = true;
- if (mapping.remark && mapping.remark.includes("VD_")) {
- insertValues.eng = true;
- }
- }
- }
+ // Step 4: 각 폼에 대해 처리
+ for (let i = 0; i < formsToProcess.length; i++) {
+ const formInfo = formsToProcess[i];
+ const { formCode, formName, im, eng } = formInfo;
- const insertResult = await db.insert(formsPlant)
- .values(insertValues)
- .onConflictDoUpdate({
- target: [formsPlant.projectCode, formsPlant.formCode],
- set: {
- packageCode: insertValues.packageCode,
- formName: insertValues.formName,
- eng: insertValues.eng ?? false,
- im: insertValues.im ?? false,
- updatedAt: new Date()
- }
- })
- .returning({ id: formsPlant.id });
+ try {
+ // 진행률 계산
+ const baseProgress = 25;
+ const progressPerForm = 70 / formsToProcess.length;
- if (insertResult.length === 0) {
- allErrors.push(`Failed to create form record for formCode ${formCode}`);
- continue; // 다음 매핑으로 진행
- }
-
- formId = insertResult[0].id;
- } else {
- // 폼이 이미 존재하는 경우 - 필요시 모드 필드 업데이트
- formId = form.id;
-
- if (mode) {
- let shouldUpdate = false;
- const updateValues: any = {};
-
- if (mode === "ENG" && form.eng !== true) {
- updateValues.eng = true;
- shouldUpdate = true;
- } else if (mode === "IM" && form.im !== true) {
- updateValues.im = true;
- shouldUpdate = true;
- }
-
- if (shouldUpdate) {
- await db.update(formsPlant)
- .set({
- ...updateValues,
- updatedAt: new Date()
- })
- .where(eq(formsPlant.id, formId));
+ // Step 4-1: formsPlant upsert
+ const existingForm = await db.query.formsPlant.findFirst({
+ where: and(
+ eq(formsPlant.projectCode, projectCode),
+ eq(formsPlant.packageCode, packageCode),
+ eq(formsPlant.formCode, formCode)
+ )
+ });
+
+ let formId: number;
+
+ if (existingForm) {
+ // 기존 폼 업데이트
+ await db.update(formsPlant)
+ .set({
+ formName,
+ im,
+ eng,
+ updatedAt: new Date()
+ })
+ .where(eq(formsPlant.id, existingForm.id));
+
+ formId = existingForm.id;
+ console.log(`[formsPlant] Updated form: ${formCode}`);
+ } else {
+ // 새 폼 생성
+ const insertResult = await db.insert(formsPlant)
+ .values({
+ projectCode,
+ packageCode,
+ formCode,
+ formName,
+ im,
+ eng
+ })
+ .returning({ id: formsPlant.id });
- console.log(`Updated form ${formId} with ${mode} mode enabled`);
- }
+ formId = insertResult[0].id;
+ console.log(`[formsPlant] Created form: ${formCode}`);
}
- }
-
- // 진행 상황 보고 - 매핑별 진행률 조정
- if (progressCallback) {
- const baseProgress = 30;
- const mappingProgress = Math.floor(20 * (mappingIndex + 1) / mappings.length);
- progressCallback(baseProgress + mappingProgress);
- }
- try {
- // Step 5: Call the external API to get tag data
- const tagData = await fetchTagDataFromSEDP(projectCode, baseMappings[0].formCode);
-
- // 진행 상황 보고
if (progressCallback) {
- const baseProgress = 50;
- const mappingProgress = Math.floor(10 * (mappingIndex + 1) / mappings.length);
- progressCallback(baseProgress + mappingProgress);
+ progressCallback(baseProgress + progressPerForm * (i + 0.2));
}
- // Step 6: Process the data and insert into the tags table
- let processedCount = 0;
- let excludedCount = 0;
-
- // Get the first key from the response as the table name
+ // Step 4-2: SEDP에서 태그 데이터 가져오기
+ const tagData = await fetchTagDataFromSEDP(projectCode, formCode);
const tableName = Object.keys(tagData)[0];
const tagEntries = tagData[tableName];
if (!Array.isArray(tagEntries) || tagEntries.length === 0) {
- allErrors.push(`No tag data found in the API response for formCode ${baseMappings[0].formCode}`);
- continue; // 다음 매핑으로 진행
+ console.log(`[importTagsFromSEDP] No tag data for formCode: ${formCode}`);
+ continue;
}
- const entriesCount = tagEntries.length;
- totalEntriesCount += entriesCount;
-
- // formEntries를 위한 데이터 수집
- const newTagsForFormEntry: Array<{
- TAG_IDX: string; // 변경: TAG_NO → TAG_IDX
- TAG_NO?: string; // TAG_NO도 함께 저장 (편집 가능한 필드)
- TAG_DESC: string | null;
- status: string;
- [key: string]: any;
- }> = [];
- const SEDP_API_BASE_URL = process.env.SEDP_API_BASE_URL || 'http://sedpwebapi.ship.samsung.co.kr/api';
- const apiKey = await getSEDPToken();
-
- const registerResponse = await fetch(
- `${SEDP_API_BASE_URL}/Register/GetByID`,
- {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- 'accept': '*/*',
- 'ApiKey': apiKey,
- 'ProjectNo': projectCode
- },
- body: JSON.stringify({
- ProjectNo: projectCode,
- TYPE_ID: baseMappings[0].formCode, // 또는 mapping.formCode
- ContainDeleted: false
- })
- }
- )
-
- if (!registerResponse.ok) {
- allErrors.push(`Failed to fetch register details for ${baseMappings[0].formCode}`)
- continue
+ totalEntriesCount += tagEntries.length;
+
+ if (progressCallback) {
+ progressCallback(baseProgress + progressPerForm * (i + 0.4));
}
-
- const registerDetail: Register = await registerResponse.json()
+
+ // Step 4-3: Register detail에서 허용된 ATT_ID 추출
+ const registerDetail = await getRegisterDetail(projectCode, formCode);
+ const allowedAttIds = new Set<string>();
- // ✅ MAP_ATT에서 허용된 ATT_ID 목록 추출
- const allowedAttIds = new Set<string>()
- if (Array.isArray(registerDetail.MAP_ATT)) {
+ if (registerDetail?.MAP_ATT && Array.isArray(registerDetail.MAP_ATT)) {
for (const mapAttr of registerDetail.MAP_ATT) {
if (mapAttr.ATT_ID) {
- allowedAttIds.add(mapAttr.ATT_ID)
+ allowedAttIds.add(mapAttr.ATT_ID);
}
}
}
-
- // Process each tag entry
- for (let i = 0; i < tagEntries.length; i++) {
- try {
- const entry = tagEntries[i];
-
- // TAG_IDX가 없는 경우 제외 (변경: TAG_NO → TAG_IDX 체크)
- if (!entry.TAG_IDX) {
- excludedCount++;
- totalExcludedCount++;
-
- // 주기적으로 진행 상황 보고 (건너뛰어도 진행률은 업데이트)
- if (progressCallback && (i % 10 === 0 || i === tagEntries.length - 1)) {
- const baseProgress = 60;
- const entryProgress = Math.floor(30 * ((mappingIndex * entriesCount + i) / (mappings.length * entriesCount)));
- progressCallback(baseProgress + entryProgress);
- }
+ // Step 4-4: 태그 처리
+ const newTagsForFormEntry: Array<Record<string, any>> = [];
+ let processedCount = 0;
+ let excludedCount = 0;
- continue; // 이 항목은 건너뜀
- }
+ for (const entry of tagEntries) {
+ // TAG_IDX 없으면 제외
+ if (!entry.TAG_IDX) {
+ excludedCount++;
+ continue;
+ }
+
+ // TAG_TYPE_ID 없으면 제외
+ if (!entry.TAG_TYPE_ID || entry.TAG_TYPE_ID === "") {
+ excludedCount++;
+ continue;
+ }
- const attributes: Record<string, string> = {}
- if (Array.isArray(entry.ATTRIBUTES)) {
- for (const attr of entry.ATTRIBUTES) {
- // MAP_ATT에 정의된 ATT_ID만 포함
- if (attr.ATT_ID && allowedAttIds.has(attr.ATT_ID)) {
- if (attr.VALUE !== null && attr.VALUE !== undefined) {
- attributes[attr.ATT_ID] = String(attr.VALUE)
- }
+ // attributes 추출 (허용된 ATT_ID만)
+ const attributes: Record<string, string> = {};
+ if (Array.isArray(entry.ATTRIBUTES)) {
+ for (const attr of entry.ATTRIBUTES) {
+ if (attr.ATT_ID && allowedAttIds.has(attr.ATT_ID)) {
+ if (attr.VALUE !== null && attr.VALUE !== undefined) {
+ attributes[attr.ATT_ID] = String(attr.VALUE);
}
}
}
-
-
- // TAG_TYPE_ID가 null이거나 빈 문자열인 경우 제외
- if (entry.TAG_TYPE_ID === null || entry.TAG_TYPE_ID === "") {
- excludedCount++;
- totalExcludedCount++;
-
- // 주기적으로 진행 상황 보고 (건너뛰어도 진행률은 업데이트)
- if (progressCallback && (i % 10 === 0 || i === tagEntries.length - 1)) {
- const baseProgress = 60;
- const entryProgress = Math.floor(30 * ((mappingIndex * entriesCount + i) / (mappings.length * entriesCount)));
- progressCallback(baseProgress + entryProgress);
- }
+ }
- continue; // 이 항목은 건너뜀
+ // tagType 조회
+ const tagType = await db.query.tagTypes.findFirst({
+ where: and(
+ eq(tagTypes.code, entry.TAG_TYPE_ID),
+ eq(tagTypes.projectId, projectId)
+ )
+ });
+
+ // ======== 클래스 및 서브클래스 결정 로직 ========
+ let classLabel: string;
+ let subclassValue: string | null = null;
+ let tagClassId: number | null = null;
+
+ // 1. 먼저 CLS_ID로 직접 tagClass 찾기
+ const tagClass = tagClassByCode.get(entry.CLS_ID);
+
+ if (tagClass) {
+ // 직접 찾은 경우 - 이게 메인 클래스
+ classLabel = tagClass.label || entry.CLS_ID;
+ tagClassId = tagClass.id;
+ } else {
+ // 2. 서브클래스인지 확인 (부모 클래스의 subclasses 배열에 있는지)
+ const parentClass = parentBySubclassCode.get(entry.CLS_ID);
+
+ if (parentClass) {
+ // 서브클래스인 경우
+ classLabel = parentClass.label || parentClass.code;
+ subclassValue = entry.CLS_ID;
+ tagClassId = parentClass.id;
+
+ console.log(`[importTagsFromSEDP] 서브클래스 발견: ${entry.CLS_ID} -> 부모: ${parentClass.code}`);
+ } else {
+ // 어디에도 없는 경우 - 원본 값 사용
+ classLabel = entry.CLS_ID;
+ console.log(`[importTagsFromSEDP] 클래스를 찾을 수 없음: ${entry.CLS_ID}`);
}
-
- // Get tag type description
- const tagType = await db.query.tagTypes.findFirst({
- where: and(
- eq(tagTypes.code, entry.TAG_TYPE_ID),
- eq(tagTypes.projectId, mapping.projectId)
- )
- });
-
- // Get tag class label
- const tagClass = await db.query.tagClasses.findFirst({
- where: and(
- eq(tagClasses.code, entry.CLS_ID),
- eq(tagClasses.projectId, mapping.projectId)
- )
- });
-
- // Insert or update the tag - tagIdx 필드 추가
- await db.insert(tagsPlant).values({
- projectCode,
- packageCode,
- formId: formId,
- tagIdx: entry.TAG_IDX,
+ }
+ // ======== 클래스/서브클래스 결정 완료 ========
+
+ // tagsPlant upsert (subclass 필드 추가)
+ await db.insert(tagsPlant).values({
+ projectCode,
+ packageCode,
+ formId,
+ tagIdx: entry.TAG_IDX,
+ tagNo: entry.TAG_NO || entry.TAG_IDX,
+ tagType: tagType?.description || entry.TAG_TYPE_ID,
+ tagClassId: tagClassId,
+ class: classLabel,
+ subclass: subclassValue,
+ description: entry.TAG_DESC,
+ attributes,
+ }).onConflictDoUpdate({
+ target: [tagsPlant.projectCode, tagsPlant.packageCode, tagsPlant.tagIdx],
+ set: {
+ formId,
tagNo: entry.TAG_NO || entry.TAG_IDX,
tagType: tagType?.description || entry.TAG_TYPE_ID,
- tagClassId: tagClass?.id,
- class: tagClass?.label || entry.CLS_ID,
+ tagClassId: tagClassId,
+ class: classLabel,
+ subclass: subclassValue,
description: entry.TAG_DESC,
- attributes: attributes, // JSONB로 저장
- }).onConflictDoUpdate({
- target: [tagsPlant.projectCode, tagsPlant.packageCode, tagsPlant.tagIdx],
- set: {
- formId: formId,
- tagNo: entry.TAG_NO || entry.TAG_IDX,
- tagType: tagType?.description || entry.TAG_TYPE_ID,
- class: tagClass?.label || entry.CLS_ID,
- description: entry.TAG_DESC,
- attributes: attributes, // JSONB 업데이트
- updatedAt: new Date()
- }
- })
- // formEntries용 데이터 수집
- const tagDataForFormEntry = {
- TAG_IDX: entry.TAG_IDX, // 변경: TAG_NO → TAG_IDX
- TAG_NO: entry.TAG_NO || entry.TAG_IDX, // TAG_NO도 함께 저장
- TAG_DESC: entry.TAG_DESC || null,
- status: "From S-EDP", // SEDP에서 가져온 데이터임을 표시
- source: "S-EDP" // 태그 출처 (불변) - S-EDP에서 가져옴
- };
-
- // 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;
- }
+ attributes,
+ updatedAt: new Date()
+ }
+ });
+
+ // formEntriesPlant용 데이터 준비
+ const tagDataForFormEntry: Record<string, any> = {
+ TAG_IDX: entry.TAG_IDX,
+ TAG_NO: entry.TAG_NO || entry.TAG_IDX,
+ TAG_DESC: entry.TAG_DESC || null,
+ status: "From S-EDP",
+ source: "S-EDP"
+ };
+
+ // ATTRIBUTES 추가
+ 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);
+ newTagsForFormEntry.push(tagDataForFormEntry);
+ processedCount++;
+ }
- processedCount++;
- totalProcessedCount++;
+ totalProcessedCount += processedCount;
+ totalExcludedCount += excludedCount;
- // 주기적으로 진행 상황 보고
- if (progressCallback && (i % 10 === 0 || i === tagEntries.length - 1)) {
- const baseProgress = 60;
- const entryProgress = Math.floor(30 * ((mappingIndex * entriesCount + i) / (mappings.length * entriesCount)));
- progressCallback(baseProgress + entryProgress);
- }
- } catch (error: any) {
- console.error(`Error processing tag entry:`, error);
- allErrors.push(error.message || 'Unknown error');
- }
+ if (progressCallback) {
+ progressCallback(baseProgress + progressPerForm * (i + 0.8));
}
- // Step 7: formEntries 업데이트 - TAG_IDX 기준으로 변경
+ // Step 4-5: formEntriesPlant upsert
if (newTagsForFormEntry.length > 0) {
- try {
- // 기존 formEntry 가져오기
- const existingEntry = await db.query.formEntriesPlant.findFirst({
- where: and(
- eq(formEntriesPlant.formCode, formCode),
- eq(formEntriesPlant.projectCode, projectCode),
- eq(formEntriesPlant.packageCode, packageCode)
- )
- });
-
- if (existingEntry && existingEntry.id) {
- // 기존 formEntry가 있는 경우
- let existingData: Array<{
- TAG_IDX?: string; // 추가: TAG_IDX 필드
- TAG_NO?: string;
- TAG_DESC?: string;
- status?: string;
- [key: string]: any;
- }> = [];
-
- if (Array.isArray(existingEntry.data)) {
- existingData = existingEntry.data;
- }
+ const existingEntry = await db.query.formEntriesPlant.findFirst({
+ where: and(
+ eq(formEntriesPlant.formCode, formCode),
+ eq(formEntriesPlant.projectCode, projectCode),
+ eq(formEntriesPlant.packageCode, packageCode)
+ )
+ });
+
+ if (existingEntry) {
+ // 기존 데이터 병합
+ let existingData: Array<Record<string, any>> = [];
+ if (Array.isArray(existingEntry.data)) {
+ existingData = existingEntry.data;
+ }
- // 기존 TAG_IDX들 추출 (변경: TAG_NO → TAG_IDX)
- const existingTagIdxs = new Set(
- existingData
- .map(item => item.TAG_IDX)
- .filter(tagIdx => tagIdx !== undefined && tagIdx !== null)
- );
+ const existingTagIdxs = new Set(
+ existingData.map(item => item.TAG_IDX).filter(Boolean)
+ );
- // 중복되지 않은 새 태그들만 필터링 (변경: TAG_NO → TAG_IDX)
- const newUniqueTagsData = newTagsForFormEntry.filter(
- tagData => !existingTagIdxs.has(tagData.TAG_IDX)
+ // 기존 데이터 업데이트 + 새 데이터 추가
+ const updatedData = existingData.map(existingItem => {
+ const newData = newTagsForFormEntry.find(
+ n => n.TAG_IDX === existingItem.TAG_IDX
);
+ return newData ? { ...existingItem, ...newData } : existingItem;
+ });
- // 기존 태그들의 status와 ATTRIBUTES 업데이트 (변경: TAG_NO → TAG_IDX)
- const updatedExistingData = existingData.map(existingItem => {
- const newTagData = newTagsForFormEntry.find(
- newItem => newItem.TAG_IDX === existingItem.TAG_IDX
- );
-
- if (newTagData) {
- // 기존 태그가 있으면 SEDP 데이터로 업데이트
- return {
- ...existingItem,
- ...newTagData,
- TAG_IDX: existingItem.TAG_IDX // TAG_IDX는 유지
- };
- }
-
- return existingItem;
- });
-
- const finalData = [...updatedExistingData, ...newUniqueTagsData];
+ const newUniqueData = newTagsForFormEntry.filter(
+ n => !existingTagIdxs.has(n.TAG_IDX)
+ );
- await db
- .update(formEntriesPlant)
- .set({
- data: finalData,
- updatedAt: new Date()
- })
- .where(eq(formEntriesPlant.id, existingEntry.id));
+ await db.update(formEntriesPlant)
+ .set({
+ data: [...updatedData, ...newUniqueData],
+ updatedAt: new Date()
+ })
+ .where(eq(formEntriesPlant.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(formEntriesPlant).values({
- formCode: formCode,
- projectCode,
- packageCode,
- data: newTagsForFormEntry,
- createdAt: new Date(),
- updatedAt: new Date(),
- });
-
- console.log(`[IMPORT SEDP] Created new formEntry with ${newTagsForFormEntry.length} tags for form ${formCode}`);
- }
+ console.log(`[formEntriesPlant] Updated: ${formCode} (${newUniqueData.length} new, ${updatedData.length} updated)`);
+ } else {
+ // 새로 생성
+ await db.insert(formEntriesPlant).values({
+ formCode,
+ projectCode,
+ packageCode,
+ data: newTagsForFormEntry,
+ createdAt: new Date(),
+ updatedAt: new Date()
+ });
- // 캐시 무효화
- // 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}`);
+ console.log(`[formEntriesPlant] Created: ${formCode} (${newTagsForFormEntry.length} tags)`);
}
}
+ if (progressCallback) {
+ progressCallback(baseProgress + progressPerForm * (i + 1));
+ }
+
} catch (error: any) {
- console.error(`Error processing mapping for formCode ${formCode}:`, error);
- allErrors.push(`Error with formCode ${formCode}: ${error.message || 'Unknown error'}`);
+ console.error(`Error processing form ${formCode}:`, error);
+ allErrors.push(`Form ${formCode}: ${error.message}`);
}
}
- // 모든 매핑 처리 완료 - 진행률 100%
- if (progressCallback) {
- progressCallback(100);
- }
+ if (progressCallback) progressCallback(100);
- // 최종 결과 반환
return {
processedCount: totalProcessedCount,
excludedCount: totalExcludedCount,
totalEntries: totalEntriesCount,
errors: allErrors.length > 0 ? allErrors : undefined
};
+
} catch (error: any) {
console.error("Tag import error:", error);
throw error;
}
-}
-
-/**
- * SEDP API에서 태그 데이터 가져오기
- *
- * @param projectCode 프로젝트 코드
- * @param formCode 양식 코드
- * @returns API 응답 데이터
- */
-async function fetchTagDataFromSEDP(projectCode: string, formCode: string): Promise<any> {
- try {
- // Get the token
- const apiKey = await getSEDPToken();
-
- // Define the API base URL
- const SEDP_API_BASE_URL = process.env.SEDP_API_BASE_URL || 'http://sedpwebapi.ship.samsung.co.kr/api';
-
- // Make the API call
- const response = await fetch(
- `${SEDP_API_BASE_URL}/Data/GetPubData`,
- {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- 'accept': '*/*',
- 'ApiKey': apiKey,
- 'ProjectNo': projectCode
- },
- body: JSON.stringify({
- ProjectNo: projectCode,
- REG_TYPE_ID: formCode,
- ContainDeleted: false
- })
- }
- );
-
- if (!response.ok) {
- const errorText = await response.text();
- throw new Error(`SEDP API request failed: ${response.status} ${response.statusText} - ${errorText}`);
- }
-
- const data = await response.json();
- return data;
- } catch (error: any) {
- console.error('Error calling SEDP API:', error);
- throw new Error(`Failed to fetch data from SEDP API: ${error.message || 'Unknown error'}`);
- }
} \ No newline at end of file