diff options
Diffstat (limited to 'lib/sedp/sync-form.ts')
| -rw-r--r-- | lib/sedp/sync-form.ts | 756 |
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() |
