summaryrefslogtreecommitdiff
path: root/lib/sedp/sync-form.ts
diff options
context:
space:
mode:
authordujinkim <dujin.kim@dtsolution.co.kr>2025-04-28 02:13:30 +0000
committerdujinkim <dujin.kim@dtsolution.co.kr>2025-04-28 02:13:30 +0000
commitef4c533ebacc2cdc97e518f30e9a9350004fcdfb (patch)
tree345251a3ed0f4429716fa5edaa31024d8f4cb560 /lib/sedp/sync-form.ts
parent9ceed79cf32c896f8a998399bf1b296506b2cd4a (diff)
~20250428 작업사항
Diffstat (limited to 'lib/sedp/sync-form.ts')
-rw-r--r--lib/sedp/sync-form.ts756
1 files changed, 637 insertions, 119 deletions
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()