1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
|
import db from "@/db/db";
import {
contractItems,
tags,
forms,
formEntries, // 추가
items,
tagTypeClassFormMappings,
projects,
tagTypes,
tagClasses,
contracts
} from "@/db/schema";
import { eq, and, like, inArray } from "drizzle-orm";
import { revalidateTag } from "next/cache"; // 추가
import { getSEDPToken } from "./sedp-token";
/**
* 태그 가져오기 서비스 함수
* contractItemId(packageId)를 기반으로 외부 시스템에서 태그 데이터를 가져와 DB에 저장
* TAG_IDX를 기준으로 태그를 식별합니다.
*
* @param packageId 계약 아이템 ID (contractItemId)
* @param progressCallback 진행 상황을 보고하기 위한 콜백 함수
* @returns 처리 결과 정보 (처리된 태그 수, 오류 목록 등)
*/
export async function importTagsFromSEDP(
packageId: number,
progressCallback?: (progress: number) => void,
mode?: string
): 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`);
}
// Step 1-2: 계약 아이템에서 계약 정보를 가져와 프로젝트 ID 획득
const contract = await db.query.contracts.findFirst({
where: eq(contracts.id, contractItem.contractId)
});
if (!contract) {
throw new Error(`Contract with ID ${contractItem.contractId} not found`);
}
// 프로젝트 ID 획득
const projectId = contract.projectId;
// 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);
// 기본 매핑 검색 - 모든 모드에서 사용
const baseMappings = await db.query.tagTypeClassFormMappings.findMany({
where: and(
like(tagTypeClassFormMappings.remark, `%${itemCode}%`),
eq(tagTypeClassFormMappings.projectId, projectId)
)
});
if (baseMappings.length === 0) {
throw new Error(`No mapping found for item code ${itemCode}`);
}
// Step 2: Find the mapping entries - 모드에 따라 다른 조건 적용
let mappings = [];
if (mode === 'IM') {
// IM 모드일 때는 먼저 SEDP에서 태그 데이터를 가져와 TAG_TYPE_ID 리스트 확보
// 프로젝트 코드 가져오기
const project = await db.query.projects.findFirst({
where: eq(projects.id, projectId)
});
if (!project) {
throw new Error(`Project with ID ${projectId} not found`);
}
// 각 매핑의 formCode에 대해 태그 데이터 조회
const tagTypeIds = new Set<string>();
for (const mapping of baseMappings) {
try {
// SEDP에서 태그 데이터 가져오기
const tagData = await fetchTagDataFromSEDP(project.code, mapping.formCode);
// 첫 번째 키를 테이블 이름으로 사용
const tableName = Object.keys(tagData)[0];
const tagEntries = tagData[tableName];
if (Array.isArray(tagEntries)) {
// 모든 태그에서 TAG_TYPE_ID 수집
for (const entry of tagEntries) {
if (entry.TAG_TYPE_ID && entry.TAG_TYPE_ID !== "") {
tagTypeIds.add(entry.TAG_TYPE_ID);
}
}
}
} catch (error) {
console.error(`Error fetching tag data for formCode ${mapping.formCode}:`, error);
}
}
if (tagTypeIds.size === 0) {
throw new Error('No valid TAG_TYPE_ID found in SEDP tag data');
}
// 수집된 TAG_TYPE_ID로 tagTypes에서 정보 조회
const tagTypeInfo = await db.query.tagTypes.findMany({
where: and(
inArray(tagTypes.code, Array.from(tagTypeIds)),
eq(tagTypes.projectId, projectId)
)
});
if (tagTypeInfo.length === 0) {
throw new Error('No matching tag types found for the collected TAG_TYPE_IDs');
}
// 태그 타입 설명 수집
const tagLabels = tagTypeInfo.map(tt => tt.description);
// IM 모드에 맞는 매핑 조회 - ep가 "IMEP"인 항목만
mappings = await db.query.tagTypeClassFormMappings.findMany({
where: and(
inArray(tagTypeClassFormMappings.tagTypeLabel, tagLabels),
eq(tagTypeClassFormMappings.projectId, projectId),
eq(tagTypeClassFormMappings.ep, "IMEP")
)
});
} else {
// ENG 모드 또는 기본 모드일 때 - 기본 매핑 사용
mappings = [...baseMappings];
// ENG 모드에서는 ep 필드가 "IMEP"가 아닌 매핑만 필터링
if (mode === 'ENG') {
mappings = mappings.filter(mapping => mapping.ep !== "IMEP");
}
}
// 매핑이 없는 경우 모드에 따라 다른 오류 메시지 사용
if (mappings.length === 0) {
if (mode === 'IM') {
throw new Error('No suitable mappings found for IM mode');
} else {
throw new Error(`No mapping found for item code ${itemCode}`);
}
}
// 진행 상황 보고
if (progressCallback) progressCallback(15);
// 결과 누적을 위한 변수들 초기화
let totalProcessedCount = 0;
let totalExcludedCount = 0;
let totalEntriesCount = 0;
const allErrors: string[] = [];
// 각 매핑에 대해 처리
for (let mappingIndex = 0; mappingIndex < mappings.length; mappingIndex++) {
const mapping = mappings[mappingIndex];
// Step 3: Get the project code
const project = await db.query.projects.findFirst({
where: eq(projects.id, mapping.projectId)
});
if (!project) {
allErrors.push(`Project with ID ${mapping.projectId} not found`);
continue; // 다음 매핑으로 진행
}
const projectCode = project.code;
// IM 모드에서는 baseMappings에서 같은 formCode를 가진 매핑을 찾음
let formCode = mapping.formCode;
if (mode === 'IM') {
// baseMapping에서 동일한 formCode를 가진 매핑 찾기
const originalMapping = baseMappings.find(
baseMapping => baseMapping.formCode === mapping.formCode
);
// 찾았으면 해당 formCode 사용, 못 찾았으면 현재 매핑의 formCode 유지
if (originalMapping) {
formCode = originalMapping.formCode;
}
}
// 진행 상황 보고 - 매핑별 진행률 조정
if (progressCallback) {
const baseProgress = 15;
const mappingProgress = Math.floor(15 * (mappingIndex + 1) / mappings.length);
progressCallback(baseProgress + mappingProgress);
}
// 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;
// If form doesn't exist, create it
if (!form) {
// 폼이 없는 경우 새로 생성 - 모드에 따른 필드 설정
const insertValues: any = {
contractItemId: packageId,
formCode: formCode,
formName: mapping.formName
};
// 모드 정보가 있으면 해당 필드 설정
if (mode) {
if (mode === "ENG") {
insertValues.eng = true;
} else if (mode === "IM") {
insertValues.im = true;
if (mapping.remark && mapping.remark.includes("VD_")) {
insertValues.eng = true;
}
}
}
const insertResult = await db.insert(forms).values(insertValues).returning({ id: forms.id });
if (insertResult.length === 0) {
allErrors.push(`Failed to create form record for formCode ${formCode}`);
continue; // 다음 매핑으로 진행
}
formId = insertResult[0].id;
} else {
// 폼이 이미 존재하는 경우 - 필요시 모드 필드 업데이트
formId = form.id;
if (mode) {
let shouldUpdate = false;
const updateValues: any = {};
if (mode === "ENG" && form.eng !== true) {
updateValues.eng = true;
shouldUpdate = true;
} else if (mode === "IM" && form.im !== true) {
updateValues.im = true;
shouldUpdate = true;
}
if (shouldUpdate) {
await db.update(forms)
.set({
...updateValues,
updatedAt: new Date()
})
.where(eq(forms.id, formId));
console.log(`Updated form ${formId} with ${mode} mode enabled`);
}
}
}
// 진행 상황 보고 - 매핑별 진행률 조정
if (progressCallback) {
const baseProgress = 30;
const mappingProgress = Math.floor(20 * (mappingIndex + 1) / mappings.length);
progressCallback(baseProgress + mappingProgress);
}
try {
// Step 5: Call the external API to get tag data
const tagData = await fetchTagDataFromSEDP(projectCode, baseMappings[0].formCode);
// 진행 상황 보고
if (progressCallback) {
const baseProgress = 50;
const mappingProgress = Math.floor(10 * (mappingIndex + 1) / mappings.length);
progressCallback(baseProgress + mappingProgress);
}
// Step 6: Process the data and insert into the tags table
let processedCount = 0;
let excludedCount = 0;
// Get the first key from the response as the table name
const tableName = Object.keys(tagData)[0];
const tagEntries = tagData[tableName];
if (!Array.isArray(tagEntries) || tagEntries.length === 0) {
allErrors.push(`No tag data found in the API response for formCode ${baseMappings[0].formCode}`);
continue; // 다음 매핑으로 진행
}
const entriesCount = tagEntries.length;
totalEntriesCount += entriesCount;
// formEntries를 위한 데이터 수집
const newTagsForFormEntry: Array<{
TAG_IDX: string; // 변경: TAG_NO → TAG_IDX
TAG_NO?: string; // TAG_NO도 함께 저장 (편집 가능한 필드)
TAG_DESC: string | null;
status: string;
[key: string]: any;
}> = [];
// Process each tag entry
for (let i = 0; i < tagEntries.length; i++) {
try {
const entry = tagEntries[i];
// TAG_IDX가 없는 경우 제외 (변경: TAG_NO → TAG_IDX 체크)
if (!entry.TAG_IDX) {
excludedCount++;
totalExcludedCount++;
// 주기적으로 진행 상황 보고 (건너뛰어도 진행률은 업데이트)
if (progressCallback && (i % 10 === 0 || i === tagEntries.length - 1)) {
const baseProgress = 60;
const entryProgress = Math.floor(30 * ((mappingIndex * entriesCount + i) / (mappings.length * entriesCount)));
progressCallback(baseProgress + entryProgress);
}
continue; // 이 항목은 건너뜀
}
// TAG_TYPE_ID가 null이거나 빈 문자열인 경우 제외
if (entry.TAG_TYPE_ID === null || entry.TAG_TYPE_ID === "") {
excludedCount++;
totalExcludedCount++;
// 주기적으로 진행 상황 보고 (건너뛰어도 진행률은 업데이트)
if (progressCallback && (i % 10 === 0 || i === tagEntries.length - 1)) {
const baseProgress = 60;
const entryProgress = Math.floor(30 * ((mappingIndex * entriesCount + i) / (mappings.length * entriesCount)));
progressCallback(baseProgress + entryProgress);
}
continue; // 이 항목은 건너뜀
}
// Get tag type description
const tagType = await db.query.tagTypes.findFirst({
where: and(
eq(tagTypes.code, entry.TAG_TYPE_ID),
eq(tagTypes.projectId, mapping.projectId)
)
});
// Get tag class label
const tagClass = await db.query.tagClasses.findFirst({
where: and(
eq(tagClasses.code, entry.CLS_ID),
eq(tagClasses.projectId, mapping.projectId)
)
});
// Insert or update the tag - tagIdx 필드 추가
await db.insert(tags).values({
contractItemId: packageId,
formId: formId,
tagIdx: entry.TAG_IDX, // 추가: SEDP 고유 식별자
tagNo: entry.TAG_NO || entry.TAG_IDX, // TAG_NO가 없으면 TAG_IDX 사용
tagType: tagType?.description || entry.TAG_TYPE_ID,
tagClassId: tagClass?.id,
class: tagClass?.label || entry.CLS_ID,
description: entry.TAG_DESC
}).onConflictDoUpdate({
target: [tags.contractItemId, tags.tagIdx], // 변경: tagNo → tagIdx
set: {
formId: formId,
tagNo: entry.TAG_NO || entry.TAG_IDX, // tagNo도 업데이트 가능
tagType: tagType?.description || entry.TAG_TYPE_ID,
class: tagClass?.label || entry.CLS_ID,
description: entry.TAG_DESC,
updatedAt: new Date()
}
});
// formEntries용 데이터 수집
const tagDataForFormEntry = {
TAG_IDX: entry.TAG_IDX, // 변경: TAG_NO → TAG_IDX
TAG_NO: entry.TAG_NO || entry.TAG_IDX, // TAG_NO도 함께 저장
TAG_DESC: entry.TAG_DESC || null,
status: "From S-EDP", // SEDP에서 가져온 데이터임을 표시
source: "S-EDP" // 태그 출처 (불변) - S-EDP에서 가져옴
};
// ATTRIBUTES가 있으면 추가 (SHI 필드들)
if (Array.isArray(entry.ATTRIBUTES)) {
for (const attr of entry.ATTRIBUTES) {
if (attr.ATT_ID && attr.VALUE !== null && attr.VALUE !== undefined) {
tagDataForFormEntry[attr.ATT_ID] = attr.VALUE;
}
}
}
newTagsForFormEntry.push(tagDataForFormEntry);
processedCount++;
totalProcessedCount++;
// 주기적으로 진행 상황 보고
if (progressCallback && (i % 10 === 0 || i === tagEntries.length - 1)) {
const baseProgress = 60;
const entryProgress = Math.floor(30 * ((mappingIndex * entriesCount + i) / (mappings.length * entriesCount)));
progressCallback(baseProgress + entryProgress);
}
} catch (error: any) {
console.error(`Error processing tag entry:`, error);
allErrors.push(error.message || 'Unknown error');
}
}
// Step 7: formEntries 업데이트 - TAG_IDX 기준으로 변경
if (newTagsForFormEntry.length > 0) {
try {
// 기존 formEntry 가져오기
const existingEntry = await db.query.formEntries.findFirst({
where: and(
eq(formEntries.formCode, formCode),
eq(formEntries.contractItemId, packageId)
)
});
if (existingEntry && existingEntry.id) {
// 기존 formEntry가 있는 경우
let existingData: Array<{
TAG_IDX?: string; // 추가: TAG_IDX 필드
TAG_NO?: string;
TAG_DESC?: string;
status?: string;
[key: string]: any;
}> = [];
if (Array.isArray(existingEntry.data)) {
existingData = existingEntry.data;
}
// 기존 TAG_IDX들 추출 (변경: TAG_NO → TAG_IDX)
const existingTagIdxs = new Set(
existingData
.map(item => item.TAG_IDX)
.filter(tagIdx => tagIdx !== undefined && tagIdx !== null)
);
// 중복되지 않은 새 태그들만 필터링 (변경: TAG_NO → TAG_IDX)
const newUniqueTagsData = newTagsForFormEntry.filter(
tagData => !existingTagIdxs.has(tagData.TAG_IDX)
);
// 기존 태그들의 status와 ATTRIBUTES 업데이트 (변경: TAG_NO → TAG_IDX)
const updatedExistingData = existingData.map(existingItem => {
const newTagData = newTagsForFormEntry.find(
newItem => newItem.TAG_IDX === existingItem.TAG_IDX
);
if (newTagData) {
// 기존 태그가 있으면 SEDP 데이터로 업데이트
return {
...existingItem,
...newTagData,
TAG_IDX: existingItem.TAG_IDX // TAG_IDX는 유지
};
}
return existingItem;
});
const finalData = [...updatedExistingData, ...newUniqueTagsData];
await db
.update(formEntries)
.set({
data: finalData,
updatedAt: new Date()
})
.where(eq(formEntries.id, existingEntry.id));
console.log(`[IMPORT SEDP] Updated formEntry with ${newUniqueTagsData.length} new tags, updated ${updatedExistingData.length - newUniqueTagsData.length} existing tags for form ${formCode}`);
} else {
// formEntry가 없는 경우 새로 생성
await db.insert(formEntries).values({
formCode: formCode,
contractItemId: packageId,
data: newTagsForFormEntry,
createdAt: new Date(),
updatedAt: new Date(),
});
console.log(`[IMPORT SEDP] Created new formEntry with ${newTagsForFormEntry.length} tags for form ${formCode}`);
}
// 캐시 무효화
revalidateTag(`form-data-${formCode}-${packageId}`);
} catch (formEntryError) {
console.error(`[IMPORT SEDP] Error updating formEntry for form ${formCode}:`, formEntryError);
allErrors.push(`Error updating formEntry for form ${formCode}: ${formEntryError}`);
}
}
} catch (error: any) {
console.error(`Error processing mapping for formCode ${formCode}:`, error);
allErrors.push(`Error with formCode ${formCode}: ${error.message || 'Unknown error'}`);
}
}
// 모든 매핑 처리 완료 - 진행률 100%
if (progressCallback) {
progressCallback(100);
}
// 최종 결과 반환
return {
processedCount: totalProcessedCount,
excludedCount: totalExcludedCount,
totalEntries: totalEntriesCount,
errors: allErrors.length > 0 ? allErrors : undefined
};
} catch (error: any) {
console.error("Tag import error:", error);
throw error;
}
}
/**
* SEDP API에서 태그 데이터 가져오기
*
* @param projectCode 프로젝트 코드
* @param formCode 양식 코드
* @returns API 응답 데이터
*/
async function fetchTagDataFromSEDP(projectCode: string, formCode: string): Promise<any> {
try {
// Get the token
const apiKey = await getSEDPToken();
// Define the API base URL
const SEDP_API_BASE_URL = process.env.SEDP_API_BASE_URL || 'http://sedpwebapi.ship.samsung.co.kr/api';
// Make the API call
const response = await fetch(
`${SEDP_API_BASE_URL}/Data/GetPubData`,
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
'accept': '*/*',
'ApiKey': apiKey,
'ProjectNo': projectCode
},
body: JSON.stringify({
ProjectNo: projectCode,
REG_TYPE_ID: formCode,
ContainDeleted: false
})
}
);
if (!response.ok) {
const errorText = await response.text();
throw new Error(`SEDP API request failed: ${response.status} ${response.statusText} - ${errorText}`);
}
const data = await response.json();
return data;
} catch (error: any) {
console.error('Error calling SEDP API:', error);
throw new Error(`Failed to fetch data from SEDP API: ${error.message || 'Unknown error'}`);
}
}
|