diff options
Diffstat (limited to 'lib')
| -rw-r--r-- | lib/forms-plant/services.ts | 186 | ||||
| -rw-r--r-- | lib/forms/services.ts | 144 |
2 files changed, 288 insertions, 42 deletions
diff --git a/lib/forms-plant/services.ts b/lib/forms-plant/services.ts index 99e7c35b..fae38446 100644 --- a/lib/forms-plant/services.ts +++ b/lib/forms-plant/services.ts @@ -1469,7 +1469,7 @@ async function transformDataToSEDPFormat( SCOPE: packageCode, TOOLID: "eVCP", // Changed from VDCS ITM_NO: row.TAG_NO || "", - OP_DELETE: false, + OP_DELETE: row.status === "Deleted", // Set OP_DELETE based on status MAIN_YN: true, LAST_REV_YN: true, CRTER_NO: designerNo, @@ -1627,7 +1627,22 @@ export async function sendDataToSEDP( // Define the API base URL const SEDP_API_BASE_URL = process.env.SEDP_API_BASE_URL || 'http://sedpwebapi.ship.samsung.co.kr/api'; - console.log("Sending data to SEDP:", JSON.stringify(sedpData, null, 2)); + console.log("=".repeat(80)); + console.log("[SEDP Overwrite API] 요청 시작"); + console.log(`[SEDP Overwrite API] URL: ${SEDP_API_BASE_URL}/AdapterData/Overwrite`); + console.log(`[SEDP Overwrite API] ProjectNo: ${projectCode}`); + console.log("[SEDP Overwrite API] Request Body (전체):"); + console.log(JSON.stringify(sedpData, null, 2)); + + // Check if there are any items with OP_DELETE=true + const deleteItems = sedpData.filter(item => item.OP_DELETE === true); + if (deleteItems.length > 0) { + console.log("=".repeat(80)); + console.log(`[SEDP DELETE] ⚠️ OP_DELETE=true인 항목 ${deleteItems.length}개 발견`); + console.log("[SEDP DELETE] 삭제 요청 항목만 추출:"); + console.log(JSON.stringify(deleteItems, null, 2)); + } + console.log("=".repeat(80)); // Make the API call const response = await fetch( @@ -1747,6 +1762,11 @@ export async function sendFormDataToSEDP( // Update status for sent tags const updatedDataArray = dataArray.map(item => { if (item.TAG_NO && sentTagNumbers.has(item.TAG_NO)) { + // 삭제된 항목(status="Deleted")은 status를 유지하고, + // 나머지 항목만 "Sent to S-EDP"로 변경 + if (item.status === "Deleted") { + return item; // Keep status="Deleted" unchanged + } return { ...item, status: "Sent to S-EDP" // SEDP로 전송된 데이터임을 표시 @@ -1793,15 +1813,19 @@ export async function deleteFormDataByTags({ formCode, contractItemId, tagIdxs, + projectId, }: { formCode: string contractItemId: number tagIdxs: string[] + projectId?: number }): Promise<{ error?: string success?: boolean deletedCount?: number deletedTagsCount?: number + sedpDeleteSuccess?: boolean + sedpDeleteError?: string }> { try { // 입력 검증 @@ -1811,11 +1835,36 @@ export async function deleteFormDataByTags({ } } - console.log(`[DELETE ACTION] Deleting tags for formCode: ${formCode}, contractItemId: ${contractItemId}, tagNos:`, tagIdxs) + console.log(`[DELETE ACTION] Deleting tags for formCode: ${formCode}, contractItemId: ${contractItemId}, tagIdxs:`, tagIdxs) - // 트랜잭션으로 안전하게 처리 + // 1. 트랜잭션 전에 삭제할 항목들을 미리 조회하여 저장 (S-EDP 전송용) + const entryForSedp = await db + .select() + .from(formEntries) + .where( + and( + eq(formEntries.formCode, formCode), + eq(formEntries.contractItemId, contractItemId) + ) + ) + .orderBy(desc(formEntries.updatedAt)) + .limit(1) + + let itemsToSendToSedp: Record<string, unknown>[] = [] + + if (entryForSedp.length > 0) { + const dataForSedp = Array.isArray(entryForSedp[0].data) ? entryForSedp[0].data : [] + // 삭제할 항목들만 추출 (S-EDP로 OP_DELETE=true 전송용) + itemsToSendToSedp = dataForSedp.filter((item: Record<string, unknown>) => + tagIdxs.includes(item.TAG_IDX as string) + ) + + console.log(`[DELETE ACTION] Found ${itemsToSendToSedp.length} items to send to SEDP before deletion`) + } + + // 2. 트랜잭션으로 안전하게 처리 (완전 삭제) const result = await db.transaction(async (tx) => { - // 1. 현재 formEntry 데이터 가져오기 + // 2-1. 현재 formEntry 데이터 가져오기 const currentEntryResult = await tx .select() .from(formEntries) @@ -1833,25 +1882,25 @@ export async function deleteFormDataByTags({ } const currentEntry = currentEntryResult[0] - let currentData = Array.isArray(currentEntry.data) ? currentEntry.data : [] + const currentData = Array.isArray(currentEntry.data) ? currentEntry.data : [] console.log(`[DELETE ACTION] Current data count: ${currentData.length}`) - // 2. 삭제할 항목들 필터링 (formEntries에서) - const updatedData = currentData.filter((item: any) => - !tagIdxs.includes(item.TAG_IDX) + // 2-2. 삭제할 항목들을 완전히 제거 (status 마킹이 아님) + const updatedData = currentData.filter((item: Record<string, unknown>) => + !tagIdxs.includes(item.TAG_IDX as string) ) const deletedFromFormEntries = currentData.length - updatedData.length console.log(`[DELETE ACTION] Updated data count: ${updatedData.length}`) - console.log(`[DELETE ACTION] Deleted ${deletedFromFormEntries} items from formEntries`) + console.log(`[DELETE ACTION] Completely removed ${deletedFromFormEntries} items from formEntries`) if (deletedFromFormEntries === 0) { throw new Error("No items were found to delete in formEntries") } - // 3. tags 테이블에서 해당 태그들 삭제 + // 2-3. tags 테이블에서 해당 태그들 삭제 const deletedTagsResult = await tx .delete(tags) .where( @@ -1867,7 +1916,7 @@ export async function deleteFormDataByTags({ console.log(`[DELETE ACTION] Deleted ${deletedTagsCount} items from tags table`) console.log(`[DELETE ACTION] Deleted tag numbers:`, deletedTagsResult.map(t => t.tagNo)) - // 4. formEntries 데이터 업데이트 + // 2-4. formEntries 데이터 업데이트 (삭제된 항목 제외) await tx .update(formEntries) .set({ @@ -1888,7 +1937,115 @@ export async function deleteFormDataByTags({ } }) - // 5. 캐시 무효화 + // 3. SEDP에 OP_DELETE=true로 전송 (projectId가 제공된 경우) + let sedpDeleteSuccess = false + let sedpDeleteError: string | undefined + + if (projectId) { + try { + console.log(`[DELETE ACTION] Attempting to send deleted items to SEDP for projectId: ${projectId}`) + + // 미리 저장해둔 삭제 항목들 사용 + const deletedItems = itemsToSendToSedp + + if (deletedItems.length > 0) { + console.log(`[DELETE ACTION] Found ${deletedItems.length} deleted items before deduplication`) + + // 삭제 항목들에 status="Deleted" 마킹 (S-EDP에서 OP_DELETE=true로 전송하기 위해) + const deletedItemsWithStatus = deletedItems.map((item: Record<string, unknown>) => ({ + ...item, + status: "Deleted" + } as Record<string, unknown>)) + + console.log(`[DELETE ACTION] Deleted items TAG_NO list:`, deletedItemsWithStatus.map(item => ({ + TAG_NO: item.TAG_NO, + TAG_IDX: item.TAG_IDX + }))) + + // Deduplicate by TAG_NO: keep only the oldest TAG_IDX for each TAG_NO + // S-EDP has a unique key on (PROJ_NO, TOOLID, SCOPE, REV_NO, ITM_NO) + // ITM_NO is mapped to TAG_NO, so we need to ensure only ONE item per TAG_NO + const tagNoMap = new Map<string, Record<string, unknown>>(); + + for (const item of deletedItemsWithStatus) { + const tagNo = item.TAG_NO as string; + const tagIdx = item.TAG_IDX as string; + + if (!tagNo) { + console.warn(`[DELETE ACTION] Skipping item without TAG_NO`); + continue; + } + + if (!tagIdx) { + console.warn(`[DELETE ACTION] Skipping item without TAG_IDX for TAG_NO "${tagNo}"`); + continue; + } + + if (!tagNoMap.has(tagNo)) { + console.log(`[DELETE ACTION] TAG_NO "${tagNo}": Adding first occurrence with TAG_IDX ${tagIdx}`); + tagNoMap.set(tagNo, item); + } else { + // Compare TAG_IDX (MongoDB ObjectID) - keep the oldest one + const existingItem = tagNoMap.get(tagNo)!; + const existingTagIdx = existingItem.TAG_IDX as string; + + // MongoDB ObjectID는 시간순으로 정렬 가능 (사전순으로 작은 값이 더 오래됨) + if (tagIdx < existingTagIdx) { + console.log(`[DELETE ACTION] TAG_NO "${tagNo}": Replacing TAG_IDX ${existingTagIdx} with older ${tagIdx}`); + tagNoMap.set(tagNo, item); + } else { + console.log(`[DELETE ACTION] TAG_NO "${tagNo}": Skipping TAG_IDX ${tagIdx} (keeping older ${existingTagIdx})`); + } + } + } + + const uniqueDeletedItems = Array.from(tagNoMap.values()); + console.log(`[DELETE ACTION] ✓ Deduplication complete: ${deletedItemsWithStatus.length} items → ${uniqueDeletedItems.length} unique items`) + console.log(`[DELETE ACTION] Final items to send to SEDP:`, uniqueDeletedItems.map(item => ({ + TAG_NO: item.TAG_NO, + TAG_IDX: item.TAG_IDX + }))) + + // Get form meta to get columns + const formMetaResult = await db.query.formMetas.findFirst({ + where: and( + eq(formMetas.formCode, formCode), + eq(formMetas.projectId, projectId) + ) + }) + + if (formMetaResult && formMetaResult.columns) { + // Send deleted items to SEDP (will have OP_DELETE=true) + const sedpResult = await sendFormDataToSEDP( + formCode, + projectId, + contractItemId, + uniqueDeletedItems as GenericData[], + formMetaResult.columns as DataTableColumnJSON[] + ) + + if (sedpResult.success) { + sedpDeleteSuccess = true + console.log(`[DELETE ACTION] Successfully sent ${uniqueDeletedItems.length} unique deleted items to SEDP with OP_DELETE=true`) + } else { + sedpDeleteError = sedpResult.message + console.error(`[DELETE ACTION] Failed to send deleted items to SEDP:`, sedpResult.message) + } + } else { + sedpDeleteError = "Form meta or columns not found" + console.warn(`[DELETE ACTION] Form meta not found for formCode: ${formCode}, projectId: ${projectId}`) + } + } else { + console.warn(`[DELETE ACTION] No deleted items found to send to SEDP`) + } + } catch (sedpError) { + sedpDeleteError = sedpError instanceof Error ? sedpError.message : "SEDP delete failed" + console.error(`[DELETE ACTION] SEDP delete error:`, sedpError) + // SEDP 삭제 실패는 경고로만 처리 (로컬 삭제는 성공했으므로) + } + } + + // 4. 캐시 무효화 const cacheKey = `form-data-${formCode}-${contractItemId}` revalidateTag(cacheKey) revalidateTag(`tags-${contractItemId}`) @@ -1898,11 +2055,14 @@ export async function deleteFormDataByTags({ console.log(`[DELETE ACTION] Transaction completed successfully`) console.log(`[DELETE ACTION] FormEntries deleted: ${result.deletedFromFormEntries}`) console.log(`[DELETE ACTION] Tags deleted: ${result.deletedTagsCount}`) + console.log(`[DELETE ACTION] SEDP delete success: ${sedpDeleteSuccess}`) return { success: true, deletedCount: result.deletedFromFormEntries, deletedTagsCount: result.deletedTagsCount, + sedpDeleteSuccess, + sedpDeleteError, } } catch (error) { diff --git a/lib/forms/services.ts b/lib/forms/services.ts index 0d5b2d6a..49255cdc 100644 --- a/lib/forms/services.ts +++ b/lib/forms/services.ts @@ -1627,7 +1627,22 @@ export async function sendDataToSEDP( // Define the API base URL const SEDP_API_BASE_URL = process.env.SEDP_API_BASE_URL || 'http://sedpwebapi.ship.samsung.co.kr/api'; - console.log("Sending data to SEDP:", JSON.stringify(sedpData, null, 2)); + console.log("=".repeat(80)); + console.log("[SEDP Overwrite API] 요청 시작"); + console.log(`[SEDP Overwrite API] URL: ${SEDP_API_BASE_URL}/AdapterData/Overwrite`); + console.log(`[SEDP Overwrite API] ProjectNo: ${projectCode}`); + console.log("[SEDP Overwrite API] Request Body (전체):"); + console.log(JSON.stringify(sedpData, null, 2)); + + // Check if there are any items with OP_DELETE=true + const deleteItems = sedpData.filter(item => item.OP_DELETE === true); + if (deleteItems.length > 0) { + console.log("=".repeat(80)); + console.log(`[SEDP DELETE] ⚠️ OP_DELETE=true인 항목 ${deleteItems.length}개 발견`); + console.log("[SEDP DELETE] 삭제 요청 항목만 추출:"); + console.log(JSON.stringify(deleteItems, null, 2)); + } + console.log("=".repeat(80)); // Make the API call const response = await fetch( @@ -1822,9 +1837,34 @@ export async function deleteFormDataByTags({ console.log(`[DELETE ACTION] Deleting tags for formCode: ${formCode}, contractItemId: ${contractItemId}, tagIdxs:`, tagIdxs) - // 트랜잭션으로 안전하게 처리 + // 1. 트랜잭션 전에 삭제할 항목들을 미리 조회하여 저장 (S-EDP 전송용) + const entryForSedp = await db + .select() + .from(formEntries) + .where( + and( + eq(formEntries.formCode, formCode), + eq(formEntries.contractItemId, contractItemId) + ) + ) + .orderBy(desc(formEntries.updatedAt)) + .limit(1) + + let itemsToSendToSedp: Record<string, unknown>[] = [] + + if (entryForSedp.length > 0) { + const dataForSedp = Array.isArray(entryForSedp[0].data) ? entryForSedp[0].data : [] + // 삭제할 항목들만 추출 (S-EDP로 OP_DELETE=true 전송용) + itemsToSendToSedp = dataForSedp.filter((item: Record<string, unknown>) => + tagIdxs.includes(item.TAG_IDX as string) + ) + + console.log(`[DELETE ACTION] Found ${itemsToSendToSedp.length} items to send to SEDP before deletion`) + } + + // 2. 트랜잭션으로 안전하게 처리 (완전 삭제) const result = await db.transaction(async (tx) => { - // 1. 현재 formEntry 데이터 가져오기 + // 2-1. 현재 formEntry 데이터 가져오기 const currentEntryResult = await tx .select() .from(formEntries) @@ -1846,29 +1886,21 @@ export async function deleteFormDataByTags({ console.log(`[DELETE ACTION] Current data count: ${currentData.length}`) - // 2. 삭제할 항목들을 status="Deleted"로 마킹 (formEntries에서) - const updatedData = currentData.map((item: Record<string, unknown>) => { - if (tagIdxs.includes(item.TAG_IDX as string)) { - return { - ...item, - status: "Deleted" - } - } - return item - }) + // 2-2. 삭제할 항목들을 완전히 제거 (status 마킹이 아님) + const updatedData = currentData.filter((item: Record<string, unknown>) => + !tagIdxs.includes(item.TAG_IDX as string) + ) - const deletedFromFormEntries = currentData.filter((item: Record<string, unknown>) => - tagIdxs.includes(item.TAG_IDX as string) - ).length + const deletedFromFormEntries = currentData.length - updatedData.length console.log(`[DELETE ACTION] Updated data count: ${updatedData.length}`) - console.log(`[DELETE ACTION] Marked ${deletedFromFormEntries} items as Deleted in formEntries`) + console.log(`[DELETE ACTION] Completely removed ${deletedFromFormEntries} items from formEntries`) if (deletedFromFormEntries === 0) { throw new Error("No items were found to delete in formEntries") } - // 3. tags 테이블에서 해당 태그들 삭제 + // 2-3. tags 테이블에서 해당 태그들 삭제 const deletedTagsResult = await tx .delete(tags) .where( @@ -1884,7 +1916,7 @@ export async function deleteFormDataByTags({ console.log(`[DELETE ACTION] Deleted ${deletedTagsCount} items from tags table`) console.log(`[DELETE ACTION] Deleted tag numbers:`, deletedTagsResult.map(t => t.tagNo)) - // 4. formEntries 데이터 업데이트 + // 2-4. formEntries 데이터 업데이트 (삭제된 항목 제외) await tx .update(formEntries) .set({ @@ -1901,12 +1933,11 @@ export async function deleteFormDataByTags({ return { deletedFromFormEntries, deletedTagsCount, - deletedTagNumbers: deletedTagsResult.map(t => t.tagNo), - updatedData // Return updatedData for SEDP sync + deletedTagNumbers: deletedTagsResult.map(t => t.tagNo) } }) - // 5. SEDP에 OP_DELETE=true로 전송 (projectId가 제공된 경우) + // 3. SEDP에 OP_DELETE=true로 전송 (projectId가 제공된 경우) let sedpDeleteSuccess = false let sedpDeleteError: string | undefined @@ -1914,12 +1945,67 @@ export async function deleteFormDataByTags({ try { console.log(`[DELETE ACTION] Attempting to send deleted items to SEDP for projectId: ${projectId}`) - // Get the deleted items (items with status="Deleted") - const deletedItems = result.updatedData.filter((item: Record<string, unknown>) => - item.status === "Deleted" - ) + // 미리 저장해둔 삭제 항목들 사용 + const deletedItems = itemsToSendToSedp if (deletedItems.length > 0) { + console.log(`[DELETE ACTION] Found ${deletedItems.length} deleted items before deduplication`) + + // 삭제 항목들에 status="Deleted" 마킹 (S-EDP에서 OP_DELETE=true로 전송하기 위해) + const deletedItemsWithStatus = deletedItems.map((item: Record<string, unknown>) => ({ + ...item, + status: "Deleted" + } as Record<string, unknown>)) + + console.log(`[DELETE ACTION] Deleted items TAG_NO list:`, deletedItemsWithStatus.map(item => ({ + TAG_NO: item.TAG_NO, + TAG_IDX: item.TAG_IDX + }))) + + // Deduplicate by TAG_NO: keep only the oldest TAG_IDX for each TAG_NO + // S-EDP has a unique key on (PROJ_NO, TOOLID, SCOPE, REV_NO, ITM_NO) + // ITM_NO is mapped to TAG_NO, so we need to ensure only ONE item per TAG_NO + const tagNoMap = new Map<string, Record<string, unknown>>(); + + for (const item of deletedItemsWithStatus) { + const tagNo = item.TAG_NO as string; + const tagIdx = item.TAG_IDX as string; + + if (!tagNo) { + console.warn(`[DELETE ACTION] Skipping item without TAG_NO`); + continue; + } + + if (!tagIdx) { + console.warn(`[DELETE ACTION] Skipping item without TAG_IDX for TAG_NO "${tagNo}"`); + continue; + } + + if (!tagNoMap.has(tagNo)) { + console.log(`[DELETE ACTION] TAG_NO "${tagNo}": Adding first occurrence with TAG_IDX ${tagIdx}`); + tagNoMap.set(tagNo, item); + } else { + // Compare TAG_IDX (MongoDB ObjectID) - keep the oldest one + const existingItem = tagNoMap.get(tagNo)!; + const existingTagIdx = existingItem.TAG_IDX as string; + + // MongoDB ObjectID는 시간순으로 정렬 가능 (사전순으로 작은 값이 더 오래됨) + if (tagIdx < existingTagIdx) { + console.log(`[DELETE ACTION] TAG_NO "${tagNo}": Replacing TAG_IDX ${existingTagIdx} with older ${tagIdx}`); + tagNoMap.set(tagNo, item); + } else { + console.log(`[DELETE ACTION] TAG_NO "${tagNo}": Skipping TAG_IDX ${tagIdx} (keeping older ${existingTagIdx})`); + } + } + } + + const uniqueDeletedItems = Array.from(tagNoMap.values()); + console.log(`[DELETE ACTION] ✓ Deduplication complete: ${deletedItemsWithStatus.length} items → ${uniqueDeletedItems.length} unique items`) + console.log(`[DELETE ACTION] Final items to send to SEDP:`, uniqueDeletedItems.map(item => ({ + TAG_NO: item.TAG_NO, + TAG_IDX: item.TAG_IDX + }))) + // Get form meta to get columns const formMetaResult = await db.query.formMetas.findFirst({ where: and( @@ -1934,13 +2020,13 @@ export async function deleteFormDataByTags({ formCode, projectId, contractItemId, - deletedItems as GenericData[], + uniqueDeletedItems as GenericData[], formMetaResult.columns as DataTableColumnJSON[] ) if (sedpResult.success) { sedpDeleteSuccess = true - console.log(`[DELETE ACTION] Successfully sent ${deletedItems.length} deleted items to SEDP with OP_DELETE=true`) + console.log(`[DELETE ACTION] Successfully sent ${uniqueDeletedItems.length} unique deleted items to SEDP with OP_DELETE=true`) } else { sedpDeleteError = sedpResult.message console.error(`[DELETE ACTION] Failed to send deleted items to SEDP:`, sedpResult.message) @@ -1959,7 +2045,7 @@ export async function deleteFormDataByTags({ } } - // 6. 캐시 무효화 + // 4. 캐시 무효화 const cacheKey = `form-data-${formCode}-${contractItemId}` revalidateTag(cacheKey) revalidateTag(`tags-${contractItemId}`) |
