diff --git a/backend-node/src/services/nodeFlowExecutionService.ts b/backend-node/src/services/nodeFlowExecutionService.ts index 6f481198..40eada6e 100644 --- a/backend-node/src/services/nodeFlowExecutionService.ts +++ b/backend-node/src/services/nodeFlowExecutionService.ts @@ -2282,6 +2282,7 @@ export class NodeFlowExecutionService { UPDATE ${targetTable} SET ${setClauses.join(", ")} WHERE ${updateWhereConditions} + RETURNING * `; logger.info(`πŸ”„ UPDATE μ‹€ν–‰:`, { @@ -2292,8 +2293,14 @@ export class NodeFlowExecutionService { values: updateValues, }); - await txClient.query(updateSql, updateValues); + const updateResult = await txClient.query(updateSql, updateValues); updatedCount++; + + // πŸ†• UPDATE κ²°κ³Όλ₯Ό μž…λ ₯ 데이터에 병합 (λ‹€μŒ λ…Έλ“œμ—μ„œ id λ“± μ‚¬μš© κ°€λŠ₯) + if (updateResult.rows && updateResult.rows[0]) { + Object.assign(data, updateResult.rows[0]); + logger.info(` πŸ“¦ UPDATE κ²°κ³Ό 병합: id=${updateResult.rows[0].id}`); + } } else { // 3-B. μ—†μœΌλ©΄ INSERT const columns: string[] = []; @@ -2340,6 +2347,7 @@ export class NodeFlowExecutionService { const insertSql = ` INSERT INTO ${targetTable} (${columns.join(", ")}) VALUES (${placeholders}) + RETURNING * `; logger.info(`βž• INSERT μ‹€ν–‰:`, { @@ -2348,8 +2356,14 @@ export class NodeFlowExecutionService { conflictKeyValues, }); - await txClient.query(insertSql, values); + const insertResult = await txClient.query(insertSql, values); insertedCount++; + + // πŸ†• INSERT κ²°κ³Όλ₯Ό μž…λ ₯ 데이터에 병합 (λ‹€μŒ λ…Έλ“œμ—μ„œ id λ“± μ‚¬μš© κ°€λŠ₯) + if (insertResult.rows && insertResult.rows[0]) { + Object.assign(data, insertResult.rows[0]); + logger.info(` πŸ“¦ INSERT κ²°κ³Ό 병합: id=${insertResult.rows[0].id}`); + } } } @@ -2357,11 +2371,10 @@ export class NodeFlowExecutionService { `βœ… UPSERT μ™„λ£Œ (λ‚΄λΆ€ DB): ${targetTable}, INSERT ${insertedCount}건, UPDATE ${updatedCount}건` ); - return { - insertedCount, - updatedCount, - totalCount: insertedCount + updatedCount, - }; + // πŸ”₯ λ‹€μŒ λ…Έλ“œμ— 전달할 데이터 λ°˜ν™˜ + // dataArrayμ—λŠ” Object.assign으둜 UPSERT κ²°κ³Ό(id λ“±)κ°€ 이미 λ³‘ν•©λ˜μ–΄ 있음 + // 카운트 정보도 ν•¨κ»˜ λ°˜ν™˜ν•˜μ—¬ κΈ°μ‘΄ ν˜Έν™˜μ„± μœ μ§€ + return dataArray; }; // πŸ”₯ ν΄λΌμ΄μ–ΈνŠΈκ°€ μ „λ‹¬λ˜μ—ˆμœΌλ©΄ μ‚¬μš©, μ•„λ‹ˆλ©΄ 독립 νŠΈλžœμž­μ…˜ 생성 diff --git a/frontend/lib/utils/buttonActions.ts b/frontend/lib/utils/buttonActions.ts index 6daf17e9..29ff8f3f 100644 --- a/frontend/lib/utils/buttonActions.ts +++ b/frontend/lib/utils/buttonActions.ts @@ -304,6 +304,9 @@ export interface ButtonActionContext { selectedLeftData?: Record; refreshRightPanel?: () => void; }; + + // πŸ†• μ €μž₯된 데이터 (μ €μž₯ ν›„ μ œμ–΄ μ‹€ν–‰ μ‹œ ν”Œλ‘œμš°μ— 전달) + savedData?: any; } /** @@ -1251,7 +1254,49 @@ export class ButtonActionExecutor { // πŸ”₯ μ €μž₯ 성곡 ν›„ μ—°κ²°λœ μ œμ–΄ μ‹€ν–‰ (dataflowTiming이 'after'인 경우) if (config.enableDataflowControl && config.dataflowConfig) { console.log("🎯 μ €μž₯ ν›„ μ œμ–΄ μ‹€ν–‰ μ‹œμž‘:", config.dataflowConfig); - await this.executeAfterSaveControl(config, context); + + // ν…Œμ΄λΈ” μ„Ήμ…˜ 데이터 νŒŒμ‹± (comp_둜 μ‹œμž‘ν•˜λŠ” ν•„λ“œμ— JSON 배열이 μžˆλŠ” 경우) + // μž…κ³  ν™”λ©΄ λ“±μ—μ„œ ν’ˆλͺ© λͺ©λ‘μ΄ comp_xxx ν•„λ“œμ— JSON λ¬Έμžμ—΄λ‘œ μ €μž₯됨 + const formData: Record = (saveResult.data || context.formData || {}) as Record; + let parsedSectionData: any[] = []; + + // comp_둜 μ‹œμž‘ν•˜λŠ” ν•„λ“œμ—μ„œ ν…Œμ΄λΈ” μ„Ήμ…˜ 데이터 μ°ΎκΈ° + const compFieldKey = Object.keys(formData).find(key => + key.startsWith("comp_") && typeof formData[key] === "string" + ); + + if (compFieldKey) { + try { + const sectionData = JSON.parse(formData[compFieldKey]); + if (Array.isArray(sectionData) && sectionData.length > 0) { + // 곡톡 ν•„λ“œμ™€ μ„Ήμ…˜ 데이터 병합 + parsedSectionData = sectionData.map((item: any) => { + // μ„Ήμ…˜ λ°μ΄ν„°μ—μ„œ λΆˆν•„μš”ν•œ λ‚΄λΆ€ ν•„λ“œ 제거 + const { _isNewItem, _targetTable, _existingRecord, ...cleanItem } = item; + // 곡톡 ν•„λ“œ(comp_ ν•„λ“œ μ œμ™Έ) + μ„Ήμ…˜ μ•„μ΄ν…œ 병합 + const commonFields: Record = {}; + Object.keys(formData).forEach(key => { + if (!key.startsWith("comp_") && !key.endsWith("_numberingRuleId")) { + commonFields[key] = formData[key]; + } + }); + return { ...commonFields, ...cleanItem }; + }); + console.log(`πŸ“¦ [handleSave] ν…Œμ΄λΈ” μ„Ήμ…˜ 데이터 νŒŒμ‹± μ™„λ£Œ: ${parsedSectionData.length}건`, parsedSectionData[0]); + } + } catch (parseError) { + console.warn("⚠️ [handleSave] ν…Œμ΄λΈ” μ„Ήμ…˜ 데이터 νŒŒμ‹± μ‹€νŒ¨:", parseError); + } + } + + // μ €μž₯된 데이터λ₯Ό context에 μΆ”κ°€ν•˜μ—¬ ν”Œλ‘œμš°μ— 전달 + const contextWithSavedData = { + ...context, + savedData: formData, + // νŒŒμ‹±λœ μ„Ήμ…˜ 데이터가 있으면 selectedRowsData둜 전달 + selectedRowsData: parsedSectionData.length > 0 ? parsedSectionData : context.selectedRowsData, + }; + await this.executeAfterSaveControl(config, contextWithSavedData); } } else { throw new Error("μ €μž₯에 ν•„μš”ν•œ 정보가 λΆ€μ‘±ν•©λ‹ˆλ‹€. (ν…Œμ΄λΈ”λͺ… λ˜λŠ” ν™”λ©΄ID λˆ„λ½)"); @@ -3643,8 +3688,20 @@ export class ButtonActionExecutor { // λ…Έλ“œ ν”Œλ‘œμš° μ‹€ν–‰ API const { executeNodeFlow } = await import("@/lib/api/nodeFlows"); - // 데이터 μ†ŒμŠ€ μ€€λΉ„ - const sourceData: any = context.formData || {}; + // 데이터 μ†ŒμŠ€ μ€€λΉ„: context-data λͺ¨λ“œλŠ” 배열을 κΈ°λŒ€ν•¨ + // μš°μ„ μˆœμœ„: selectedRowsData > savedData > formData + // - selectedRowsData: ν…Œμ΄λΈ” μ„Ήμ…˜μ—μ„œ μ €μž₯된 ν•˜μœ„ ν•­λͺ©λ“€ (item_code, inbound_qty λ“± 포함) + // - savedData: μ €μž₯ API 응닡 데이터 + // - formData: 폼에 μž…λ ₯된 데이터 + let sourceData: any[]; + if (context.selectedRowsData && context.selectedRowsData.length > 0) { + sourceData = context.selectedRowsData; + console.log("πŸ“¦ [λ‹€μ€‘μ œμ–΄] selectedRowsData μ‚¬μš©:", sourceData.length, "건"); + } else { + const savedData = context.savedData || context.formData || {}; + sourceData = Array.isArray(savedData) ? savedData : [savedData]; + console.log("πŸ“¦ [λ‹€μ€‘μ œμ–΄] savedData/formData μ‚¬μš©:", sourceData.length, "건"); + } let allSuccess = true; const results: Array<{ flowId: number; flowName: string; success: boolean; message?: string }> = []; @@ -3751,8 +3808,20 @@ export class ButtonActionExecutor { // λ…Έλ“œ ν”Œλ‘œμš° μ‹€ν–‰ API 호좜 const { executeNodeFlow } = await import("@/lib/api/nodeFlows"); - // 데이터 μ†ŒμŠ€ μ€€λΉ„ - const sourceData: any = context.formData || {}; + // 데이터 μ†ŒμŠ€ μ€€λΉ„: context-data λͺ¨λ“œλŠ” 배열을 κΈ°λŒ€ν•¨ + // μš°μ„ μˆœμœ„: selectedRowsData > savedData > formData + // - selectedRowsData: ν…Œμ΄λΈ” μ„Ήμ…˜μ—μ„œ μ €μž₯된 ν•˜μœ„ ν•­λͺ©λ“€ (item_code, inbound_qty λ“± 포함) + // - savedData: μ €μž₯ API 응닡 데이터 + // - formData: 폼에 μž…λ ₯된 데이터 + let sourceData: any[]; + if (context.selectedRowsData && context.selectedRowsData.length > 0) { + sourceData = context.selectedRowsData; + console.log("πŸ“¦ [λ‹¨μΌμ œμ–΄] selectedRowsData μ‚¬μš©:", sourceData.length, "건"); + } else { + const savedData = context.savedData || context.formData || {}; + sourceData = Array.isArray(savedData) ? savedData : [savedData]; + console.log("πŸ“¦ [λ‹¨μΌμ œμ–΄] savedData/formData μ‚¬μš©:", sourceData.length, "건"); + } // repeat-screen-modal 데이터가 있으면 병합 const repeatScreenModalKeys = Object.keys(context.formData || {}).filter((key) => @@ -3765,7 +3834,8 @@ export class ButtonActionExecutor { console.log("πŸ“¦ λ…Έλ“œ ν”Œλ‘œμš°μ— 전달할 데이터:", { flowId, dataSourceType: controlDataSource, - sourceData, + sourceDataCount: sourceData.length, + sourceDataSample: sourceData[0], }); const result = await executeNodeFlow(flowId, {