summaryrefslogtreecommitdiff
path: root/lib/sedp
diff options
context:
space:
mode:
Diffstat (limited to 'lib/sedp')
-rw-r--r--lib/sedp/get-form-tags.ts380
-rw-r--r--lib/sedp/get-tags.ts263
-rw-r--r--lib/sedp/sync-form.ts756
-rw-r--r--lib/sedp/sync-object-class.ts257
-rw-r--r--lib/sedp/sync-projects.ts7
-rw-r--r--lib/sedp/sync-tag-types.ts88
6 files changed, 1586 insertions, 165 deletions
diff --git a/lib/sedp/get-form-tags.ts b/lib/sedp/get-form-tags.ts
new file mode 100644
index 00000000..b488bfad
--- /dev/null
+++ b/lib/sedp/get-form-tags.ts
@@ -0,0 +1,380 @@
+// lib/sedp/get-tag.ts
+import db from "@/db/db";
+import {
+ contractItems,
+ tags,
+ forms,
+ items,
+ tagTypeClassFormMappings,
+ projects,
+ tagTypes,
+ tagClasses,
+ formMetas,
+ formEntries
+} from "@/db/schema";
+import { eq, and, like, inArray } from "drizzle-orm";
+import { getSEDPToken } from "./sedp-token";
+
+interface Attribute {
+ ATT_ID: string;
+ VALUE: any;
+ VALUE_DBL: number;
+ UOM_ID: string | null;
+}
+
+interface TagEntry {
+ TAG_NO: string;
+ TAG_DESC: string;
+ EP_ID: string;
+ TAG_TYPE_ID: string;
+ CLS_ID: string;
+ ATTRIBUTES: Attribute[];
+ [key: string]: any;
+}
+
+interface Column {
+ key: string;
+ label: string;
+ type: string;
+ shi?: boolean;
+}
+
+/**
+ * 태그 가져오기 서비스 함수
+ * contractItemId(packageId)를 기반으로 외부 시스템에서 태그 데이터를 가져와 DB에 저장
+ *
+ * @param formCode 양식 코드
+ * @param projectCode 프로젝트 코드
+ * @param packageId 계약 아이템 ID (contractItemId)
+ * @param progressCallback 진행 상황을 보고하기 위한 콜백 함수
+ * @returns 처리 결과 정보 (처리된 태그 수, 오류 목록 등)
+ */
+export async function importTagsFromSEDP(
+ formCode: string,
+ projectCode: string,
+ packageId: number,
+ progressCallback?: (progress: number) => void
+): Promise<{
+ processedCount: number;
+ excludedCount: number;
+ totalEntries: number;
+ errors?: string[];
+}> {
+ try {
+ // 진행 상황 보고
+ if (progressCallback) progressCallback(5);
+
+ // 에러 수집 배열
+ const errors: string[] = [];
+
+ // SEDP API에서 태그 데이터 가져오기
+ const tagData = await fetchTagDataFromSEDP(projectCode, formCode);
+
+ // 데이터 형식 처리
+ const tableName = Object.keys(tagData)[0];
+ if (!tableName || !tagData[tableName]) {
+ throw new Error("Invalid tag data format from SEDP API");
+ }
+
+ const tagEntries: TagEntry[] = tagData[tableName];
+ if (!Array.isArray(tagEntries) || tagEntries.length === 0) {
+ return {
+ processedCount: 0,
+ excludedCount: 0,
+ totalEntries: 0,
+ errors: ["No tag entries found in API response"]
+ };
+ }
+
+ // 진행 상황 보고
+ 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[] = JSON.parse(formMetaRecord[0].columns as string);
+
+ // 현재 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;
+ }
+
+ // 기본 태그 데이터 객체 생성
+ const tagObject: any = {
+ TAG_NO: tagEntry.TAG_NO,
+ TAG_DESC: tagEntry.TAG_DESC || ""
+ };
+
+ // 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 (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;
+ }
+
+ // 그 외 필드는 컬럼 정보에서 shi=true인 것만 업데이트
+ 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);
+ }
+
+ 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;
+
+ const data = entry.data as any[];
+ const updatedData = data.map(item => {
+ if (item.TAG_NO === update.tagNo) {
+ return { ...item, ...update.updates };
+ }
+ 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];
+
+ 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()
+ });
+ }
+ }
+
+ // 진행 상황 보고
+ if (progressCallback) progressCallback(100);
+
+ // 최종 결과 반환
+ return {
+ processedCount,
+ excludedCount,
+ totalEntries: tagEntries.length,
+ errors: errors.length > 0 ? errors : 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
diff --git a/lib/sedp/get-tags.ts b/lib/sedp/get-tags.ts
new file mode 100644
index 00000000..7c5661c3
--- /dev/null
+++ b/lib/sedp/get-tags.ts
@@ -0,0 +1,263 @@
+// lib/sedp/get-tag.ts
+import db from "@/db/db";
+import {
+ contractItems,
+ tags,
+ forms,
+ items,
+ tagTypeClassFormMappings,
+ projects,
+ tagTypes,
+ tagClasses
+} from "@/db/schema";
+import { eq, and, like } from "drizzle-orm";
+import { getSEDPToken } from "./sedp-token";
+
+/**
+ * 태그 가져오기 서비스 함수
+ * contractItemId(packageId)를 기반으로 외부 시스템에서 태그 데이터를 가져와 DB에 저장
+ *
+ * @param packageId 계약 아이템 ID (contractItemId)
+ * @param progressCallback 진행 상황을 보고하기 위한 콜백 함수
+ * @returns 처리 결과 정보 (처리된 태그 수, 오류 목록 등)
+ */
+// 함수 반환 타입 업데이트
+export async function importTagsFromSEDP(
+ packageId: number,
+ progressCallback?: (progress: number) => void
+): Promise<{
+ processedCount: number;
+ excludedCount: number;
+ totalEntries: number;
+ errors?: string[];
+}> {
+ try {
+ // 진행 상황 보고
+ if (progressCallback) progressCallback(5);
+
+ // Step 1: Get the contract item to find relevant data
+ const contractItem = await db.query.contractItems.findFirst({
+ where: eq(contractItems.id, packageId)
+ });
+
+ if (!contractItem) {
+ throw new Error(`Contract item with ID ${packageId} not found`);
+ }
+
+ // 진행 상황 보고
+ if (progressCallback) progressCallback(5);
+
+ // Step 1-2: Get the item using itemId from contractItem
+ const item = await db.query.items.findFirst({
+ where: eq(items.id, contractItem.itemId)
+ });
+
+ if (!item) {
+ throw new Error(`Item with ID ${contractItem.itemId} not found`);
+ }
+
+ const itemCode = item.itemCode;
+
+ // 진행 상황 보고
+ if (progressCallback) progressCallback(10);
+
+ // Step 2: Find the mapping entry with the item code in remark field
+ // 더 유연한 검색 패턴 사용 (%itemCode%)
+ const mapping = await db.query.tagTypeClassFormMappings.findFirst({
+ where: like(tagTypeClassFormMappings.remark, `%${itemCode}%`)
+ });
+
+ if (!mapping) {
+ throw new Error(`No mapping found for item code ${itemCode}`);
+ }
+
+ // 진행 상황 보고
+ if (progressCallback) progressCallback(15);
+
+ // Step 3: Get the project code
+ const project = await db.query.projects.findFirst({
+ where: eq(projects.id, mapping.projectId)
+ });
+
+ if (!project) {
+ throw new Error(`Project with ID ${mapping.projectId} not found`);
+ }
+
+ const projectCode = project.code;
+ const formCode = mapping.formCode;
+
+ // 진행 상황 보고
+ if (progressCallback) progressCallback(20);
+
+ // Step 4: Find the form ID
+ const form = await db.query.forms.findFirst({
+ where: and(
+ eq(forms.contractItemId, packageId),
+ eq(forms.formCode, formCode)
+ )
+ });
+
+ let formId = form?.id;
+
+ // If form doesn't exist, create it
+ if (!form) {
+ const insertResult = await db.insert(forms).values({
+ contractItemId: packageId,
+ formCode: formCode,
+ formName: mapping.formName
+ }).returning({ id: forms.id });
+
+ if (insertResult.length === 0) {
+ throw new Error('Failed to create form record');
+ }
+
+ formId = insertResult[0].id;
+ }
+
+ // 진행 상황 보고
+ if (progressCallback) progressCallback(30);
+
+ // Step 5: Call the external API to get tag data
+ const tagData = await fetchTagDataFromSEDP(projectCode, formCode);
+
+ // 진행 상황 보고
+ if (progressCallback) progressCallback(50);
+
+ // Step 6: Process the data and insert into the tags table
+ let processedCount = 0;
+ let excludedCount = 0;
+ const errors: string[] = [];
+
+ // Get the first key from the response as the table name
+ const tableName = Object.keys(tagData)[0];
+ const tagEntries = tagData[tableName];
+
+ if (!Array.isArray(tagEntries) || tagEntries.length === 0) {
+ throw new Error('No tag data found in the API response');
+ }
+
+ const totalEntries = tagEntries.length;
+
+ // Process each tag entry
+ for (let i = 0; i < tagEntries.length; i++) {
+ try {
+ const entry = tagEntries[i];
+
+ // TAG_TYPE_ID가 null이거나 빈 문자열인 경우 제외
+ if (entry.TAG_TYPE_ID === null || entry.TAG_TYPE_ID === "") {
+ excludedCount++;
+
+ // 주기적으로 진행 상황 보고 (건너뛰어도 진행률은 업데이트)
+ if (progressCallback && (i % 10 === 0 || i === tagEntries.length - 1)) {
+ progressCallback(Math.floor(50 + (i / tagEntries.length) * 50));
+ }
+
+ continue; // 이 항목은 건너뜀
+ }
+
+ // 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
+ await db.insert(tags).values({
+ contractItemId: packageId,
+ formId: formId,
+ tagNo: entry.TAG_NO,
+ tagType: tagType?.description || entry.TAG_TYPE_ID,
+ class: tagClass?.label || entry.CLS_ID,
+ description: entry.TAG_DESC
+ }).onConflictDoUpdate({
+ target: [tags.contractItemId, tags.tagNo],
+ set: {
+ formId: formId,
+ tagType: tagType?.description || entry.TAG_TYPE_ID,
+ class: tagClass?.label || entry.CLS_ID,
+ description: entry.TAG_DESC,
+ updatedAt: new Date()
+ }
+ });
+
+ processedCount++;
+
+ // 주기적으로 진행 상황 보고
+ if (progressCallback && (i % 10 === 0 || i === tagEntries.length - 1)) {
+ progressCallback(Math.floor(50 + (i / tagEntries.length) * 50));
+ }
+ } catch (error: any) {
+ console.error(`Error processing tag entry:`, error);
+ errors.push(error.message || 'Unknown error');
+ }
+ }
+
+ // 최종 결과 반환
+ return {
+ processedCount,
+ excludedCount,
+ totalEntries,
+ errors: errors.length > 0 ? errors : 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
diff --git a/lib/sedp/sync-form.ts b/lib/sedp/sync-form.ts
index b9e6fa90..a3caa809 100644
--- a/lib/sedp/sync-form.ts
+++ b/lib/sedp/sync-form.ts
@@ -1,13 +1,42 @@
// src/lib/cron/syncTagFormMappings.ts
import db from "@/db/db";
-import { projects, tagTypes, tagClasses, tagTypeClassFormMappings, formMetas } from '@/db/schema';
-import { eq, and, inArray } from 'drizzle-orm';
+import { projects, tagTypes, tagClasses, tagTypeClassFormMappings, formMetas, forms, contractItems, items } from '@/db/schema';
+import { eq, and, inArray, ilike } 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/dev/api';
// 인터페이스 정의
+interface TagTypeClassFormMapping {
+ projectId: number;
+ tagTypeLabel: string;
+ classLabel: string;
+ formCode: string;
+ formName: string;
+ remark: string | null;
+ ep: string;
+ createdAt: Date;
+ updatedAt: Date;
+}
+
+interface FormMeta {
+ projectId: number;
+ formCode: string;
+ formName: string;
+ columns: string; // JSON 문자열
+ createdAt: Date;
+ updatedAt: Date;
+}
+
+interface FormRecord {
+ contractItemId: number;
+ formCode: string;
+ formName: string;
+ eng: boolean;
+ createdAt: Date;
+ updatedAt: Date;
+}
interface Register {
PROJ_NO: string;
TYPE_ID: string;
@@ -137,6 +166,87 @@ interface FormColumn {
options?: string[];
uom?: string;
uomId?: string;
+ shi?: Boolean;
+}
+
+// 아이템 코드 추출 함수
+function extractItemCodes(remark: string | null): string[] {
+ if (!remark) return [];
+
+ // 검색용으로만 소문자로 변환
+ const remarkLower = remark.toLowerCase();
+
+ // 'vd_' 접두사 확인
+ const hasVD_ = remarkLower.includes("vd_");
+
+ if (!hasVD_) return [];
+
+ let vdPart = "";
+
+ // 'vd_'가 있으면 원본 문자열에서 추출 (소문자 버전이 아님)
+ if (hasVD_) {
+ const vdIndex = remarkLower.indexOf("vd_");
+ vdPart = remark.substring(vdIndex + 3); // 원본 문자열에서 추출
+ }
+
+ if (!vdPart) return [];
+
+ // 쉼표로 구분된 여러 itemCode 처리
+ return vdPart.split(",").map(code => code.trim());
+}
+
+async function getDefaulTAttributes(): Promise<string[]> {
+ try {
+ const apiKey = await getSEDPToken();
+
+ const response = await fetch(
+ `${SEDP_API_BASE_URL}/Dictionary/GetByKey`,
+ {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ 'accept': '*/*',
+ 'ApiKey': apiKey,
+ },
+ body: JSON.stringify({
+ Key: "DefaultAttributesToCompare",
+ })
+ }
+ );
+
+ if (!response.ok) {
+ if (response.status === 404) {
+ console.warn(`디폴트 속성 찾을 수 없음`);
+ return [];
+ }
+ throw new Error(`코드 리스트 요청 실패: ${response.status} ${response.statusText}`);
+ }
+
+ // 안전하게 JSON 파싱
+ try {
+ const data = await response.json();
+ // 데이터가 배열인지 확인하고 문자열 배열로 변환
+ if (Array.isArray(data)) {
+ return data as string[];
+ } else {
+ console.warn('응답이 배열 형식이 아닙니다');
+ return [];
+ }
+ } catch (parseError) {
+ console.error(`디폴트 속성 응답 파싱 실패:`, parseError);
+ // 응답 내용 로깅
+ try {
+ const text = await response.clone().text();
+ console.log(`응답 내용: ${text.substring(0, 200)}${text.length > 200 ? '...' : ''}`);
+ } catch (textError) {
+ console.error('응답 내용 로깅 실패:', textError);
+ }
+ return [];
+ }
+ } catch (error) {
+ console.error(`디폴트 어트리뷰트 가져오기 실패:`, error);
+ throw error;
+ }
}
// 레지스터 데이터 가져오기
@@ -144,7 +254,7 @@ async function getRegisters(projectCode: string): Promise<Register[]> {
try {
// 토큰(API 키) 가져오기
const apiKey = await getSEDPToken();
-
+
const response = await fetch(
`${SEDP_API_BASE_URL}/Register/Get`,
{
@@ -156,36 +266,123 @@ async function getRegisters(projectCode: string): Promise<Register[]> {
'ProjectNo': projectCode
},
body: JSON.stringify({
- ContainDeleted: true
+ ProjectNo: projectCode,
+ ContainDeleted: false
})
}
);
-
+
if (!response.ok) {
throw new Error(`레지스터 요청 실패: ${response.status} ${response.statusText}`);
}
-
- const data = await response.json();
-
- // 결과가 배열인지 확인
- if (Array.isArray(data)) {
- return data;
- } else {
- // 단일 객체인 경우 배열로 변환
- return [data];
+
+ // 안전하게 JSON 파싱
+ let data;
+ try {
+ data = await response.json();
+ } catch (parseError) {
+ console.error(`프로젝트 ${projectCode}의 레지스터 응답 파싱 실패:`, parseError);
+ // 응답 내용 로깅
+ const text = await response.clone().text();
+ console.log(`응답 내용: ${text.substring(0, 200)}${text.length > 200 ? '...' : ''}`);
+ throw new Error(`레지스터 응답 파싱 실패: ${parseError instanceof Error ? parseError.message : String(parseError)}`);
}
+
+ // 결과를 배열로 변환 (단일 객체인 경우 배열로 래핑)
+ let registers: Register[] = Array.isArray(data) ? data : [data];
+
+ // MAP_CLS_ID가 비어있지 않고 REMARK가 vd, VD, vD, Vd 중 하나인 레지스터만 필터링
+ registers = registers.filter(register => {
+ // 삭제된 레지스터 제외
+ if (register.DELETED) return false;
+
+ // MAP_CLS_ID 배열이 존재하고 요소가 하나 이상 있는지 확인
+ const hasValidMapClsId = Array.isArray(register.MAP_CLS_ID) && register.MAP_CLS_ID.length > 0;
+
+ // REMARK가 'vd_' 또는 'vd' 포함 확인 (대소문자 구분 없이)
+ const remarkLower = register.REMARK && register.REMARK.toLowerCase();
+ const hasValidRemark = remarkLower && (remarkLower.includes('vd'));
+
+ // 두 조건 모두 충족해야 함
+ return hasValidMapClsId && hasValidRemark;
+ });
+
+ console.log(`프로젝트 ${projectCode}에서 ${registers.length}개의 유효한 레지스터를 가져왔습니다.`);
+ return registers;
} catch (error) {
console.error(`프로젝트 ${projectCode}의 레지스터 가져오기 실패:`, error);
throw error;
}
}
-// 특정 속성 가져오기
-async function getAttributeById(projectCode: string, attributeId: string): Promise<Attribute | null> {
+// 프로젝트의 모든 속성을 가져와 맵으로 반환
+async function getAttributes(projectCode: string): Promise<Map<string, Attribute>> {
try {
// 토큰(API 키) 가져오기
const apiKey = await getSEDPToken();
-
+
+ const response = await fetch(
+ `${SEDP_API_BASE_URL}/Attributes/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 attributes: Attribute[] = Array.isArray(data) ? data : [data];
+
+ // ATT_ID로 효율적인 조회를 위한 맵 생성
+ const attributeMap = new Map<string, Attribute>();
+ for (const attribute of attributes) {
+ if (!attribute.DELETED) {
+ attributeMap.set(attribute.ATT_ID, attribute);
+ }
+ }
+
+ console.log(`프로젝트 ${projectCode}에서 ${attributeMap.size}개의 속성을 가져왔습니다`);
+ return attributeMap;
+
+ } 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();
+ }
+}
+
+// 특정 속성 가져오기 (하위 호환성을 위해 유지)
+async function getAttributeById(projectCode: string, attributeId: string, register: string): Promise<Attribute | null> {
+ try {
+ // 토큰(API 키) 가져오기
+ const apiKey = await getSEDPToken();
+
const response = await fetch(
`${SEDP_API_BASE_URL}/Attributes/GetByID`,
{
@@ -197,11 +394,13 @@ async function getAttributeById(projectCode: string, attributeId: string): Promi
'ProjectNo': projectCode
},
body: JSON.stringify({
- ATT_ID: attributeId
+ ProjectNo: projectCode,
+ ATT_ID: attributeId,
+ ContainDeleted: false
})
}
);
-
+
if (!response.ok) {
if (response.status === 404) {
console.warn(`속성 ID ${attributeId}를 찾을 수 없음`);
@@ -209,20 +408,96 @@ async function getAttributeById(projectCode: string, attributeId: string): Promi
}
throw new Error(`속성 요청 실패: ${response.status} ${response.statusText}`);
}
-
- return response.json();
+
+ // 안전하게 JSON 파싱
+ try {
+ const data = await response.json();
+ return data;
+ } catch (parseError) {
+ console.error(`속성 ID ${attributeId} ${register} ${projectCode} 응답 파싱 실패:`, parseError);
+ // 응답 내용 로깅
+ try {
+ const text = await response.clone().text();
+ console.log(`응답 내용: ${text.substring(0, 200)}${text.length > 200 ? '...' : ''}`);
+ } catch (textError) {
+ console.error('응답 내용 로깅 실패:', textError);
+ }
+ return null;
+ }
} catch (error) {
console.error(`속성 ID ${attributeId} 가져오기 실패:`, error);
return null;
}
}
-// 특정 코드 리스트 가져오기
+// 프로젝트의 모든 코드 리스트를 가져와 맵으로 반환
+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();
+ }
+}
+
+// 특정 코드 리스트 가져오기 (하위 호환성을 위해 유지)
async function getCodeListById(projectCode: string, codeListId: string): Promise<CodeList | null> {
try {
// 토큰(API 키) 가져오기
const apiKey = await getSEDPToken();
-
+
const response = await fetch(
`${SEDP_API_BASE_URL}/CodeList/GetByID`,
{
@@ -234,11 +509,13 @@ async function getCodeListById(projectCode: string, codeListId: string): Promise
'ProjectNo': projectCode
},
body: JSON.stringify({
- CL_ID: codeListId
+ ProjectNo: projectCode,
+ CL_ID: codeListId,
+ ContainDeleted: false
})
}
);
-
+
if (!response.ok) {
if (response.status === 404) {
console.warn(`코드 리스트 ID ${codeListId}를 찾을 수 없음`);
@@ -246,20 +523,96 @@ async function getCodeListById(projectCode: string, codeListId: string): Promise
}
throw new Error(`코드 리스트 요청 실패: ${response.status} ${response.statusText}`);
}
-
- return response.json();
+
+ // 안전하게 JSON 파싱
+ try {
+ const data = await response.json();
+ return data;
+ } catch (parseError) {
+ console.error(`코드 리스트 ID ${codeListId} 응답 파싱 실패:`, parseError);
+ // 응답 내용 로깅
+ try {
+ const text = await response.clone().text();
+ console.log(`응답 내용: ${text.substring(0, 200)}${text.length > 200 ? '...' : ''}`);
+ } catch (textError) {
+ console.error('응답 내용 로깅 실패:', textError);
+ }
+ return null;
+ }
} catch (error) {
console.error(`코드 리스트 ID ${codeListId} 가져오기 실패:`, error);
return null;
}
}
-// UOM 가져오기
+// 프로젝트의 모든 UOM을 가져와 맵으로 반환
+async function getUOMs(projectCode: string): Promise<Map<string, UOM>> {
+ try {
+ // 토큰(API 키) 가져오기
+ const apiKey = await getSEDPToken();
+
+ const response = await fetch(
+ `${SEDP_API_BASE_URL}/UOM/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(`UOM 요청 실패: ${response.status} ${response.statusText}`);
+ }
+
+ // 안전하게 JSON 파싱
+ try {
+ const data = await response.json();
+
+ // 데이터가 배열인지 확인
+ const uoms: UOM[] = Array.isArray(data) ? data : [data];
+
+ // UOM_ID로 효율적인 조회를 위한 맵 생성
+ const uomMap = new Map<string, UOM>();
+ for (const uom of uoms) {
+ if (!uom.DELETED) {
+ uomMap.set(uom.UOM_ID, uom);
+ }
+ }
+
+ console.log(`프로젝트 ${projectCode}에서 ${uomMap.size}개의 UOM을 가져왔습니다`);
+ return uomMap;
+
+ } catch (parseError) {
+ console.error(`프로젝트 ${projectCode}의 UOM 응답 파싱 실패:`, 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}의 UOM 가져오기 실패:`, error);
+ return new Map();
+ }
+}
+
+// UOM 가져오기 (하위 호환성을 위해 유지)
async function getUomById(projectCode: string, uomId: string): Promise<UOM | null> {
try {
// 토큰(API 키) 가져오기
const apiKey = await getSEDPToken();
-
+
const response = await fetch(
`${SEDP_API_BASE_URL}/UOM/GetByID`,
{
@@ -271,11 +624,13 @@ async function getUomById(projectCode: string, uomId: string): Promise<UOM | nul
'ProjectNo': projectCode
},
body: JSON.stringify({
- UOM_ID: uomId
+ UOMID: uomId, // API 명세서에 따라 UOMID 사용
+ ProjectNo: projectCode,
+ ContainDeleted: false
})
}
);
-
+
if (!response.ok) {
if (response.status === 404) {
console.warn(`UOM ID ${uomId}를 찾을 수 없음`);
@@ -283,90 +638,215 @@ async function getUomById(projectCode: string, uomId: string): Promise<UOM | nul
}
throw new Error(`UOM 요청 실패: ${response.status} ${response.statusText}`);
}
-
- return response.json();
+
+ // 안전하게 JSON 파싱
+ try {
+ const data = await response.json();
+ return data;
+ } catch (parseError) {
+ console.error(`UOM ID ${uomId} 응답 파싱 실패:`, parseError);
+ // 응답 내용 로깅
+ try {
+ const text = await response.clone().text();
+ console.log(`응답 내용: ${text.substring(0, 200)}${text.length > 200 ? '...' : ''}`);
+ } catch (textError) {
+ console.error('응답 내용 로깅 실패:', textError);
+ }
+ return null;
+ }
} catch (error) {
console.error(`UOM ID ${uomId} 가져오기 실패:`, error);
return null;
}
}
+// contractItemId 조회 함수
+async function getContractItemsByItemCodes(itemCodes: string[]): Promise<Map<string, number>> {
+ try {
+ if (!itemCodes.length) return new Map();
+
+ // 먼저 itemCodes에 해당하는 item 레코드를 조회
+ const itemRecords = await db.select({
+ id: items.id,
+ itemCode: items.itemCode
+ })
+ .from(items)
+ .where(inArray(items.itemCode, itemCodes));
+
+ if (!itemRecords.length) {
+ console.log(`No items found for itemCodes: ${itemCodes.join(', ')}`);
+ return new Map();
+ }
+
+ // item ID 목록 추출
+ const itemIds = itemRecords.map(item => item.id);
+
+ // contractItems 조회
+ const contractItemRecords = await db.select({
+ id: contractItems.id,
+ itemId: contractItems.itemId
+ })
+ .from(contractItems)
+ .where(inArray(contractItems.itemId, itemIds));
+
+ // itemCode와 contractItemId의 매핑 생성
+ const itemCodeToContractItemId = new Map<string, number>();
+
+ for (const item of itemRecords) {
+ // itemCode가 null이 아닌 경우에만 처리
+ if (item.itemCode) {
+ const matchedContractItems = contractItemRecords.filter(ci => ci.itemId === item.id);
+ if (matchedContractItems.length > 0) {
+ // 일치하는 첫 번째 contractItem 사용
+ itemCodeToContractItemId.set(item.itemCode, matchedContractItems[0].id);
+ }
+ }
+ }
+
+ return itemCodeToContractItemId;
+ } catch (error) {
+ console.error('ContractItems 조회 중 오류 발생:', error);
+ return new Map();
+ }
+}
+
// 데이터베이스에 태그 타입 클래스 폼 매핑 및 폼 메타 저장
async function saveFormMappingsAndMetas(
- projectId: number,
+ projectId: number,
projectCode: string,
registers: Register[]
): Promise<number> {
try {
- // 프로젝트와 관련된 태그 타입 및 클래스 가져오기
+ // 프로젝트의 태그 타입과 클래스 가져오기
const tagTypeRecords = await db.select()
.from(tagTypes)
.where(eq(tagTypes.projectId, projectId));
-
+
const tagClassRecords = await db.select()
.from(tagClasses)
.where(eq(tagClasses.projectId, projectId));
-
- // 태그 타입과 클래스를 매핑
+
+ // 태그 타입과 클래스 매핑
const tagTypeMap = new Map(tagTypeRecords.map(type => [type.code, type]));
const tagClassMap = new Map(tagClassRecords.map(cls => [cls.code, cls]));
+
+ // 모든 속성, 코드 리스트, UOM을 한 번에 가져와 반복 API 호출 방지
+ const attributeMap = await getAttributes(projectCode);
+ const codeListMap = await getCodeLists(projectCode);
+ const uomMap = await getUOMs(projectCode);
- // 저장할 매핑 목록과 폼 메타 정보
- const mappingsToSave = [];
- const formMetasToSave = [];
+ // 기본 속성 가져오기
+ const defaultAttributes = await getDefaulTAttributes();
+
+ // 모든 register에서 itemCode를 추출하여 한 번에 조회
+ const allItemCodes: string[] = [];
+ registers.forEach(register => {
+ if (register.REMARK) {
+ const itemCodes = extractItemCodes(register.REMARK);
+ allItemCodes.push(...itemCodes);
+ }
+ });
+
+ // 중복 제거
+ const uniqueItemCodes = [...new Set(allItemCodes)];
- // 각 레지스터 처리
+ // 모든 itemCode에 대한 contractItemId 조회
+ const itemCodeToContractItemId = await getContractItemsByItemCodes(uniqueItemCodes);
+
+ console.log(`${uniqueItemCodes.length}개의 고유 itemCode 중 ${itemCodeToContractItemId.size}개의 contractItem을 찾았습니다`);
+
+ // 저장할 데이터 준비
+ const mappingsToSave: TagTypeClassFormMapping[] = [];
+ const formMetasToSave: FormMeta[] = [];
+ const formsToSave: FormRecord[] = [];
+
+ // 폼이 있는 contractItemId 트래킹
+ const contractItemIdsWithForms = new Set<number>();
+
+ // 각 register 처리
for (const register of registers) {
- // 삭제된 레지스터는 건너뜀
+ // 삭제된 register 건너뛰기
if (register.DELETED) continue;
-
- // 폼 메타 데이터를 위한 컬럼 정보 구성
+
+ // REMARK에서 itemCodes 추출
+ const itemCodes = extractItemCodes(register.REMARK || '');
+ if (!itemCodes.length) {
+ console.log(`Register ${register.TYPE_ID} (${register.DESC})의 REMARK에 유효한 itemCode가 없습니다`);
+ continue;
+ }
+
+ // 폼 메타용 columns 구성
const columns: FormColumn[] = [];
-
- // 각 속성 정보 수집
+
for (const linkAtt of register.LNK_ATT) {
- // 속성 가져오기
- const attribute = await getAttributeById(projectCode, linkAtt.ATT_ID);
-
- if (!attribute) continue;
-
- // 기본 컬럼 정보
+ let attribute = null;
+
+ // 기본 속성인지 확인
+ if (defaultAttributes && defaultAttributes.includes(linkAtt.ATT_ID)) {
+ // 기본 속성에 대한 기본 attribute 객체 생성
+ attribute = {
+ DESC: linkAtt.ATT_ID,
+ VAL_TYPE: 'STRING'
+ };
+ } else {
+ // 맵에서 속성 조회
+ attribute = attributeMap.get(linkAtt.ATT_ID);
+
+ // 속성을 찾지 못한 경우 다음으로 넘어감
+ if (!attribute) continue;
+ }
+
+ // 컬럼 정보 생성
const column: FormColumn = {
key: linkAtt.ATT_ID,
- label: linkAtt.CPY_DESC,
- type: attribute.VAL_TYPE || 'STRING'
+ label: attribute.DESC,
+ type: (attribute.VAL_TYPE === 'LIST' || attribute.VAL_TYPE === 'DYNAMICLIST')
+ ? 'LIST'
+ : (attribute.VAL_TYPE || 'STRING'),
+ shi: attribute.REMARK?.toLocaleLowerCase() === "shi"
};
-
- // 리스트 타입인 경우 옵션 추가
- if ((attribute.VAL_TYPE === 'LIST' || attribute.VAL_TYPE === 'DYNAMICLIST') && attribute.CL_ID) {
- const codeList = await getCodeListById(projectCode, attribute.CL_ID);
+
+ // 리스트 타입에 대한 옵션 추가 (기본 속성이 아닌 경우)
+ if (!defaultAttributes.includes(linkAtt.ATT_ID) &&
+ (attribute.VAL_TYPE === 'LIST' || attribute.VAL_TYPE === 'DYNAMICLIST') &&
+ attribute.CL_ID) {
+ // 맵에서 코드 리스트 조회
+ const codeList = codeListMap.get(attribute.CL_ID);
+
if (codeList && codeList.VALUES) {
- // 유효한 옵션만 필터링
- const options = codeList.VALUES
- .filter(value => value.USE_YN)
- .map(value => value.DESC);
-
+ const options = [...new Set(
+ codeList.VALUES
+ .filter(value => value.USE_YN)
+ .map(value => value.VALUE)
+ )];
+
if (options.length > 0) {
column.options = options;
}
}
}
-
+
// UOM 정보 추가
if (linkAtt.UOM_ID) {
- const uom = await getUomById(projectCode, linkAtt.UOM_ID);
-
+ const uom = uomMap.get(linkAtt.UOM_ID);
+
if (uom) {
column.uom = uom.SYMBOL;
column.uomId = uom.UOM_ID;
}
}
-
+
columns.push(column);
}
-
- // 폼 메타 정보 저장
+
+ // 컬럼이 없으면 건너뛰기
+ if (columns.length === 0) {
+ console.log(`폼 ${register.TYPE_ID} (${register.DESC})에 컬럼이 없어 건너뜁니다`);
+ continue;
+ }
+
+ // 폼 메타 데이터 준비
formMetasToSave.push({
projectId,
formCode: register.TYPE_ID,
@@ -375,25 +855,24 @@ async function saveFormMappingsAndMetas(
createdAt: new Date(),
updatedAt: new Date()
});
-
- // 관련된 클래스 매핑 처리
+
+ // 클래스 매핑 처리
for (const classId of register.MAP_CLS_ID) {
- // 해당 클래스와 태그 타입 확인
const tagClass = tagClassMap.get(classId);
-
+
if (!tagClass) {
- console.warn(`클래스 ID ${classId}를 프로젝트 ID ${projectId}에서 찾을 수 없음`);
+ console.warn(`프로젝트 ID ${projectId}에서 클래스 ID ${classId}를 찾을 수 없습니다`);
continue;
}
-
+
const tagTypeCode = tagClass.tagTypeCode;
const tagType = tagTypeMap.get(tagTypeCode);
-
+
if (!tagType) {
- console.warn(`태그 타입 ${tagTypeCode}를 프로젝트 ID ${projectId}에서 찾을 수 없음`);
+ console.warn(`프로젝트 ID ${projectId}에서 태그 타입 ${tagTypeCode}를 찾을 수 없습니다`);
continue;
}
-
+
// 매핑 정보 저장
mappingsToSave.push({
projectId,
@@ -401,32 +880,71 @@ async function saveFormMappingsAndMetas(
classLabel: tagClass.label,
formCode: register.TYPE_ID,
formName: register.DESC,
+ remark: register.REMARK,
+ ep: register.EP_ID,
+ createdAt: new Date(),
+ updatedAt: new Date()
+ });
+ }
+
+ // 폼 레코드 준비
+ for (const itemCode of itemCodes) {
+ const contractItemId = itemCodeToContractItemId.get(itemCode);
+
+ if (!contractItemId) {
+ console.warn(`itemCode: ${itemCode}에 대한 contractItemId를 찾을 수 없습니다`);
+ continue;
+ }
+
+ // 폼이 있는 contractItemId 추적
+ contractItemIdsWithForms.add(contractItemId);
+
+ formsToSave.push({
+ contractItemId,
+ formCode: register.TYPE_ID,
+ formName: register.DESC,
+ eng: true,
createdAt: new Date(),
updatedAt: new Date()
});
}
}
-
- // 기존 데이터 삭제 후 새로 저장
- await db.delete(tagTypeClassFormMappings).where(eq(tagTypeClassFormMappings.projectId, projectId));
- await db.delete(formMetas).where(eq(formMetas.projectId, projectId));
-
+
+ // 트랜잭션으로 모든 작업 처리
let totalSaved = 0;
-
- // 매핑 정보 저장
- if (mappingsToSave.length > 0) {
- await db.insert(tagTypeClassFormMappings).values(mappingsToSave);
- totalSaved += mappingsToSave.length;
- console.log(`프로젝트 ID ${projectId}에 ${mappingsToSave.length}개의 태그 타입-클래스-폼 매핑 저장 완료`);
- }
-
- // 폼 메타 정보 저장
- if (formMetasToSave.length > 0) {
- await db.insert(formMetas).values(formMetasToSave);
- totalSaved += formMetasToSave.length;
- console.log(`프로젝트 ID ${projectId}에 ${formMetasToSave.length}개의 폼 메타 정보 저장 완료`);
- }
-
+
+ await db.transaction(async (tx) => {
+ // 기존 데이터 삭제
+ await tx.delete(tagTypeClassFormMappings).where(eq(tagTypeClassFormMappings.projectId, projectId));
+ await tx.delete(formMetas).where(eq(formMetas.projectId, projectId));
+
+ // 해당 contractItemId에 대한 기존 폼 삭제
+ if (contractItemIdsWithForms.size > 0) {
+ await tx.delete(forms).where(inArray(forms.contractItemId, [...contractItemIdsWithForms]));
+ }
+
+ // 매핑 저장
+ if (mappingsToSave.length > 0) {
+ await tx.insert(tagTypeClassFormMappings).values(mappingsToSave);
+ totalSaved += mappingsToSave.length;
+ console.log(`프로젝트 ID ${projectId}에 대해 ${mappingsToSave.length}개의 태그 타입-클래스-폼 매핑을 저장했습니다`);
+ }
+
+ // 폼 메타 저장
+ if (formMetasToSave.length > 0) {
+ await tx.insert(formMetas).values(formMetasToSave);
+ totalSaved += formMetasToSave.length;
+ console.log(`프로젝트 ID ${projectId}에 대해 ${formMetasToSave.length}개의 폼 메타 레코드를 저장했습니다`);
+ }
+
+ // 폼 레코드 저장
+ if (formsToSave.length > 0) {
+ await tx.insert(forms).values(formsToSave);
+ totalSaved += formsToSave.length;
+ console.log(`프로젝트 ID ${projectId}에 대해 ${formsToSave.length}개의 폼 레코드를 저장했습니다`);
+ }
+ });
+
return totalSaved;
} catch (error) {
console.error(`폼 매핑 및 메타 저장 실패 (프로젝트 ID: ${projectId}):`, error);
@@ -438,39 +956,39 @@ async function saveFormMappingsAndMetas(
export async function syncTagFormMappings() {
try {
console.log('태그 폼 매핑 동기화 시작:', new Date().toISOString());
-
+
// 모든 프로젝트 가져오기
const allProjects = await db.select().from(projects);
-
+
// 각 프로젝트에 대해 폼 매핑 동기화
const results = await Promise.allSettled(
allProjects.map(async (project: Project) => {
try {
// 레지스터 데이터 가져오기
const registers = await getRegisters(project.code);
-
+
// 데이터베이스에 저장
const count = await saveFormMappingsAndMetas(project.id, project.code, registers);
- return {
- project: project.code,
- success: true,
- count
+ return {
+ project: project.code,
+ success: true,
+ count
} as SyncResult;
} catch (error) {
console.error(`프로젝트 ${project.code} 폼 매핑 동기화 실패:`, error);
- return {
- project: project.code,
- success: false,
- error: error instanceof Error ? error.message : String(error)
+ return {
+ project: project.code,
+ success: false,
+ error: error instanceof Error ? error.message : String(error)
} as SyncResult;
}
})
);
-
+
// 결과 처리를 위한 배열 준비
const successfulResults: SyncResult[] = [];
const failedResults: SyncResult[] = [];
-
+
// 결과 분류
results.forEach((result) => {
if (result.status === 'fulfilled') {
@@ -488,19 +1006,19 @@ export async function syncTagFormMappings() {
});
}
});
-
+
const successCount = successfulResults.length;
const failCount = failedResults.length;
-
+
// 이제 안전하게 count 속성에 접근 가능
- const totalItems = successfulResults.reduce((sum, result) =>
+ const totalItems = successfulResults.reduce((sum, result) =>
sum + (result.count || 0), 0
);
-
+
console.log(`태그 폼 매핑 동기화 완료: ${successCount}개 프로젝트 성공 (총 ${totalItems}개 항목), ${failCount}개 프로젝트 실패`);
-
- return {
- success: successCount,
+
+ return {
+ success: successCount,
failed: failCount,
items: totalItems,
timestamp: new Date().toISOString()
diff --git a/lib/sedp/sync-object-class.ts b/lib/sedp/sync-object-class.ts
index 1cf0c23b..0a76c592 100644
--- a/lib/sedp/sync-object-class.ts
+++ b/lib/sedp/sync-object-class.ts
@@ -40,7 +40,12 @@ interface SyncResult {
count?: number;
error?: string;
}
-
+interface TagType {
+ TYPE_ID: string;
+ DESC: string;
+ PROJ_NO: string;
+ // 기타 필드들...
+}
// 오브젝트 클래스 데이터 가져오기
async function getObjectClasses(projectCode: string, token:string): Promise<ObjectClass[]> {
try {
@@ -55,7 +60,8 @@ async function getObjectClasses(projectCode: string, token:string): Promise<Obje
'ProjectNo': projectCode
},
body: JSON.stringify({
- ContainDeleted: true
+ ProjectNo:projectCode,
+ ContainDeleted: false
})
}
);
@@ -95,11 +101,171 @@ async function verifyTagTypes(projectId: number, tagTypeCodes: string[]): Promis
}
}
-// 데이터베이스에 오브젝트 클래스 저장 (upsert 사용)
-async function saveObjectClassesToDatabase(projectId: number, classes: ObjectClass[]): Promise<number> {
+async function saveTagTypesToDatabase(allTagTypes: TagType[], projectCode: string): Promise<void> {
+ try {
+ if (allTagTypes.length === 0) {
+ console.log(`프로젝트 ${projectCode}에 저장할 태그 타입이 없습니다.`);
+ return;
+ }
+
+ // 프로젝트 코드로 프로젝트 ID 조회
+ const projectResult = await db.select({ id: projects.id })
+ .from(projects)
+ .where(eq(projects.code, projectCode))
+ .limit(1);
+
+ if (projectResult.length === 0) {
+ throw new Error(`프로젝트 코드 ${projectCode}에 해당하는 프로젝트를 찾을 수 없습니다.`);
+ }
+
+ const projectId = projectResult[0].id;
+
+ // 현재 프로젝트의 모든 태그 타입 조회
+ const existingTagTypes = await db.select()
+ .from(tagTypes)
+ .where(eq(tagTypes.projectId, projectId));
+
+ // 코드 기준으로 맵 생성
+ const existingTagTypeMap = new Map(
+ existingTagTypes.map(type => [type.code, type])
+ );
+
+ // API에 있는 코드 목록
+ const apiTagTypeCodes = new Set(allTagTypes.map(type => type.TYPE_ID));
+
+ // 삭제할 코드 목록
+ const codesToDelete = existingTagTypes
+ .map(type => type.code)
+ .filter(code => !apiTagTypeCodes.has(code));
+
+ // 새로 추가할 항목
+ const toInsert = [];
+
+ // 업데이트할 항목
+ const toUpdate = [];
+
+ // 태그 타입 데이터 처리
+ for (const tagType of allTagTypes) {
+ // 데이터베이스 레코드 준비
+ const record = {
+ code: tagType.TYPE_ID,
+ projectId: projectId,
+ description: tagType.DESC,
+ updatedAt: new Date()
+ };
+
+ // 이미 존재하는 코드인지 확인
+ if (existingTagTypeMap.has(tagType.TYPE_ID)) {
+ // 업데이트 항목에 추가
+ toUpdate.push(record);
+ } else {
+ // 새로 추가할 항목에 추가 (createdAt 필드 추가)
+ toInsert.push({
+ ...record,
+ createdAt: new Date()
+ });
+ }
+ }
+
+ // 트랜잭션 실행
+
+ // 1. 새 항목 삽입
+ if (toInsert.length > 0) {
+ await db.insert(tagTypes).values(toInsert);
+ console.log(`프로젝트 ID ${projectId}에 ${toInsert.length}개의 새 태그 타입 추가 완료`);
+ }
+
+ // 2. 기존 항목 업데이트
+ for (const item of toUpdate) {
+ await db.update(tagTypes)
+ .set({
+ description: item.description,
+ updatedAt: item.updatedAt
+ })
+ .where(
+ and(
+ eq(tagTypes.code, item.code),
+ eq(tagTypes.projectId, item.projectId)
+ )
+ );
+ }
+
+ if (toUpdate.length > 0) {
+ console.log(`프로젝트 ID ${projectId}의 ${toUpdate.length}개 태그 타입 업데이트 완료`);
+ }
+
+ // 3. 더 이상 존재하지 않는 항목 삭제
+ if (codesToDelete.length > 0) {
+ for (const code of codesToDelete) {
+ await db.delete(tagTypes)
+ .where(
+ and(
+ eq(tagTypes.code, code),
+ eq(tagTypes.projectId, projectId)
+ )
+ );
+ }
+ console.log(`프로젝트 ID ${projectId}에서 ${codesToDelete.length}개의 태그 타입 삭제 완료`);
+ }
+
+ console.log(`프로젝트 ${projectCode}(ID: ${projectId})의 태그 타입 동기화 완료`);
+ } catch (error) {
+ console.error(`태그 타입 저장 실패 (프로젝트: ${projectCode}):`, error);
+ throw error;
+ }
+}
+
+async function getAllTagTypes(projectCode: string, token: string): Promise<TagType[]> {
+ try {
+ console.log(`프로젝트 ${projectCode}의 모든 태그 타입 가져오기 시작`);
+
+ const response = await fetch(
+ `${SEDP_API_BASE_URL}/TagType/Get`,
+ {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ 'accept': '*/*',
+ 'ApiKey': token,
+ 'ProjectNo': projectCode
+ },
+ body: JSON.stringify({
+ ProjectNo: projectCode,
+ ContainDeleted: false
+ })
+ }
+ );
+
+ if (!response.ok) {
+ throw new Error(`태그 타입 요청 실패: ${response.status} ${response.statusText}`);
+ }
+
+ const data = await response.json();
+
+ // 결과가 배열인지 확인
+ if (Array.isArray(data)) {
+ return data;
+ } else {
+ // 단일 객체인 경우 배열로 변환
+ return [data];
+ }
+ } catch (error) {
+ console.error('태그 타입 목록 가져오기 실패:', error);
+ throw error;
+ }
+}
+
+// 4. 기존 함수 수정 - saveObjectClassesToDatabase
+async function saveObjectClassesToDatabase(
+ projectId: number,
+ classes: ObjectClass[],
+ projectCode: string,
+ token: string,
+ skipTagTypeSync: boolean = false // 태그 타입 동기화를 건너뛸지 여부
+): Promise<number> {
try {
// null이 아닌 TAG_TYPE_ID만 필터링
- const validClasses = classes.filter(cls => cls.TAG_TYPE_ID !== null);
+ const validClasses = classes.filter(cls => cls.TAG_TYPE_ID !== null && cls.TAG_TYPE_ID !== "") ;
if (validClasses.length === 0) {
console.log(`프로젝트 ID ${projectId}에 저장할 유효한 오브젝트 클래스가 없습니다.`);
@@ -109,6 +275,25 @@ async function saveObjectClassesToDatabase(projectId: number, classes: ObjectCla
// 모든 태그 타입 ID 목록 추출
const tagTypeCodes = validClasses.map(cls => cls.TAG_TYPE_ID!);
+ // skipTagTypeSync가 true인 경우 태그 타입 동기화 단계 건너뜀
+ if (!skipTagTypeSync) {
+ // 태그 타입이 없는 경우를 대비해 태그 타입 정보 먼저 가져와서 저장
+ console.log(`프로젝트 ID ${projectId}의 태그 타입 동기화 시작...`);
+
+ try {
+ // 프로젝트의 모든 태그 타입 가져오기
+ const allTagTypes = await getAllTagTypes(projectCode, token);
+
+ // 태그 타입 저장
+ await saveTagTypesToDatabase(allTagTypes, projectCode);
+ } catch (error) {
+ console.error(`프로젝트 ${projectCode}의 태그 타입 동기화 실패:`, error);
+ // 에러가 발생해도 계속 진행
+ }
+
+ console.log(`프로젝트 ID ${projectId}의 태그 타입 동기화 완료`);
+ }
+
// 존재하는 태그 타입 확인
const existingTagTypeCodes = await verifyTagTypes(projectId, tagTypeCodes);
@@ -122,6 +307,7 @@ async function saveObjectClassesToDatabase(projectId: number, classes: ObjectCla
return 0;
}
+ // 이하 기존 코드와 동일
// 현재 프로젝트의 오브젝트 클래스 코드 가져오기
const existingClasses = await db.select()
.from(tagClasses)
@@ -223,7 +409,7 @@ async function saveObjectClassesToDatabase(projectId: number, classes: ObjectCla
}
}
-// 메인 동기화 함수
+// 5. 메인 동기화 함수 수정
export async function syncObjectClasses() {
try {
console.log('오브젝트 클래스 동기화 시작:', new Date().toISOString());
@@ -234,15 +420,55 @@ export async function syncObjectClasses() {
// 2. 모든 프로젝트 가져오기
const allProjects = await db.select().from(projects);
- // 3. 각 프로젝트에 대해 오브젝트 클래스 동기화
+ // 3. 모든 프로젝트에 대해 먼저 태그 타입 동기화 (바로 이 부분이 추가됨)
+ console.log('모든 프로젝트의 태그 타입 동기화 시작...');
+
+ const tagTypeResults = await Promise.allSettled(
+ allProjects.map(async (project: Project) => {
+ try {
+ console.log(`프로젝트 ${project.code}의 태그 타입 동기화 시작...`);
+ // 프로젝트의 모든 태그 타입 가져오기
+ const allTagTypes = await getAllTagTypes(project.code, token);
+
+ // 태그 타입 저장
+ await saveTagTypesToDatabase(allTagTypes, project.code);
+ console.log(`프로젝트 ${project.code}의 태그 타입 동기화 완료`);
+
+ return {
+ project: project.code,
+ success: true,
+ count: allTagTypes.length
+ };
+ } catch (error) {
+ console.error(`프로젝트 ${project.code}의 태그 타입 동기화 실패:`, error);
+ return {
+ project: project.code,
+ success: false,
+ error: error instanceof Error ? error.message : String(error)
+ };
+ }
+ })
+ );
+
+ // 태그 타입 동기화 결과 집계
+ const tagTypeSuccessCount = tagTypeResults.filter(
+ result => result.status === 'fulfilled' && result.value.success
+ ).length;
+
+ const tagTypeFailCount = tagTypeResults.length - tagTypeSuccessCount;
+
+ console.log(`모든 프로젝트의 태그 타입 동기화 완료: ${tagTypeSuccessCount}개 성공, ${tagTypeFailCount}개 실패`);
+
+ // 4. 각 프로젝트에 대해 오브젝트 클래스 동기화 (태그 타입 동기화는 건너뜀)
const results = await Promise.allSettled(
allProjects.map(async (project: Project) => {
try {
// 오브젝트 클래스 데이터 가져오기
const objectClasses = await getObjectClasses(project.code, token);
- // 데이터베이스에 저장
- const count = await saveObjectClassesToDatabase(project.id, objectClasses);
+ // 데이터베이스에 저장 (skipTagTypeSync를 true로 설정하여 태그 타입 동기화 건너뜀)
+ const count = await saveObjectClassesToDatabase(project.id, objectClasses, project.code, token, true);
+
return {
project: project.code,
success: true,
@@ -291,10 +517,17 @@ export async function syncObjectClasses() {
console.log(`오브젝트 클래스 동기화 완료: ${successCount}개 프로젝트 성공 (총 ${totalItems}개 항목), ${failCount}개 프로젝트 실패`);
+ // 전체 결과에 태그 타입 동기화 결과도 포함
return {
- success: successCount,
- failed: failCount,
- items: totalItems,
+ tagTypeSync: {
+ success: tagTypeSuccessCount,
+ failed: tagTypeFailCount
+ },
+ objectClassSync: {
+ success: successCount,
+ failed: failCount,
+ items: totalItems
+ },
timestamp: new Date().toISOString()
};
} catch (error) {
diff --git a/lib/sedp/sync-projects.ts b/lib/sedp/sync-projects.ts
index 1094b55f..0f5ed2a8 100644
--- a/lib/sedp/sync-projects.ts
+++ b/lib/sedp/sync-projects.ts
@@ -38,15 +38,12 @@ async function getProjects(): Promise<Project[]> {
const response = await fetch(
`${SEDP_API_BASE_URL}/Project/Get`,
{
- method: 'POST',
+ method: 'GET',
headers: {
'Content-Type': 'application/json',
'accept': '*/*',
'ApiKey': apiKey
- },
- body: JSON.stringify({
- ContainDeleted: true
- })
+ }
}
);
diff --git a/lib/sedp/sync-tag-types.ts b/lib/sedp/sync-tag-types.ts
index 2d19fc19..8233badd 100644
--- a/lib/sedp/sync-tag-types.ts
+++ b/lib/sedp/sync-tag-types.ts
@@ -118,7 +118,7 @@ async function getTagTypes(projectCode: string, token: string): Promise<TagType[
},
body: JSON.stringify({
ProjectNo: projectCode,
- ContainDeleted: true
+ ContainDeleted: false
})
}
);
@@ -149,7 +149,7 @@ async function getAttributes(projectCode: string, token: string): Promise<Attrib
},
body: JSON.stringify({
ProjectNo: projectCode,
- ContainDeleted: true
+ ContainDeleted: false
})
}
);
@@ -170,7 +170,7 @@ async function getAttributes(projectCode: string, token: string): Promise<Attrib
async function getCodeList(projectCode: string, codeListId: string, token: string): Promise<CodeList | null> {
try {
const response = await fetch(
- `${SEDP_API_BASE_URL}/CodeList/Get`,
+ `${SEDP_API_BASE_URL}/CodeList/GetByID`,
{
method: 'POST',
headers: {
@@ -182,7 +182,7 @@ async function getCodeList(projectCode: string, codeListId: string, token: strin
body: JSON.stringify({
ProjectNo: projectCode,
CL_ID: codeListId,
- ContainDeleted: true
+ ContainDeleted: false
})
}
);
@@ -299,34 +299,64 @@ async function processAndSaveTagSubfields(
// 1. 새 서브필드 삽입
if (toInsert.length > 0) {
- await db.insert(tagSubfields).values(toInsert);
- totalChanged += toInsert.length;
- console.log(`프로젝트 ID ${projectId}에 ${toInsert.length}개의 새 태그 서브필드 추가 완료`);
+ // 중복 제거를 위한 Map 생성 (마지막 항목만 유지)
+ const uniqueInsertMap = new Map();
+
+ for (const item of toInsert) {
+ const compositeKey = `${item.projectId}:${item.tagTypeCode}:${item.attributesId}`;
+ uniqueInsertMap.set(compositeKey, item);
+ }
+
+ // 중복이 제거된 배열 생성
+ const deduplicatedInserts = Array.from(uniqueInsertMap.values());
+
+ // 중복 제거된 항목만 삽입
+ await db.insert(tagSubfields).values(deduplicatedInserts);
+
+ // 중복 제거 전후 개수 로그
+ console.log(`프로젝트 ID ${projectId}에 ${deduplicatedInserts.length}개의 새 태그 서브필드 추가 완료 (중복 제거 전: ${toInsert.length}개)`);
+ totalChanged += deduplicatedInserts.length;
}
- // 2. 기존 서브필드 업데이트
- for (const item of toUpdate) {
- await db.update(tagSubfields)
- .set({
- attributesDescription: item.attributesDescription,
- expression: item.expression,
- delimiter: item.delimiter,
- sortOrder: item.sortOrder,
- updatedAt: item.updatedAt
- })
- .where(
- and(
- eq(tagSubfields.projectId, item.projectId),
- eq(tagSubfields.tagTypeCode, item.tagTypeCode),
- eq(tagSubfields.attributesId, item.attributesId)
- )
- );
- totalChanged += 1;
- }
- if (toUpdate.length > 0) {
- console.log(`프로젝트 ID ${projectId}의 ${toUpdate.length}개 태그 서브필드 업데이트 완료`);
- }
+ // 2. 기존 서브필드 업데이트
+if (toUpdate.length > 0) {
+ // 중복 제거를 위한 Map 생성 (마지막 항목만 유지)
+ const uniqueUpdateMap = new Map();
+
+ for (const item of toUpdate) {
+ const compositeKey = `${item.projectId}:${item.tagTypeCode}:${item.attributesId}`;
+ uniqueUpdateMap.set(compositeKey, item);
+ }
+
+ // 중복이 제거된 배열 생성
+ const deduplicatedUpdates = Array.from(uniqueUpdateMap.values());
+
+ // 중복 제거 전후 개수 로그
+ console.log(`프로젝트 ID ${projectId}의 ${deduplicatedUpdates.length}개 태그 서브필드 업데이트 시작 (중복 제거 전: ${toUpdate.length}개)`);
+
+ // 각 항목 개별 업데이트
+ for (const item of deduplicatedUpdates) {
+ await db.update(tagSubfields)
+ .set({
+ attributesDescription: item.attributesDescription,
+ expression: item.expression,
+ delimiter: item.delimiter,
+ sortOrder: item.sortOrder,
+ updatedAt: item.updatedAt
+ })
+ .where(
+ and(
+ eq(tagSubfields.projectId, item.projectId),
+ eq(tagSubfields.tagTypeCode, item.tagTypeCode),
+ eq(tagSubfields.attributesId, item.attributesId)
+ )
+ );
+ totalChanged += 1;
+ }
+
+ console.log(`프로젝트 ID ${projectId}의 ${deduplicatedUpdates.length}개 태그 서브필드 업데이트 완료`);
+}
// 3. 더 이상 존재하지 않는 서브필드 삭제
if (keysToDelete.length > 0) {