From 0512a3214c259c125d2ca47ee2cba0b39b4a726d Mon Sep 17 00:00:00 2001 From: DDD1542 Date: Wed, 11 Feb 2026 18:05:32 +0900 Subject: [PATCH] Merge branch 'jskim-node' of http://39.117.244.52:3000/kjs/ERP-node into gbpark-node --- .../src/services/nodeFlowExecutionService.ts | 85 ++++++- .../panels/properties/ConditionProperties.tsx | 209 +++++++++++++++++- frontend/types/node-editor.ts | 12 +- 3 files changed, 297 insertions(+), 9 deletions(-) diff --git a/backend-node/src/services/nodeFlowExecutionService.ts b/backend-node/src/services/nodeFlowExecutionService.ts index 9bc59d97..a2a8aef1 100644 --- a/backend-node/src/services/nodeFlowExecutionService.ts +++ b/backend-node/src/services/nodeFlowExecutionService.ts @@ -2830,12 +2830,12 @@ export class NodeFlowExecutionService { inputData: any, context: ExecutionContext ): Promise { - const { conditions, logic } = node.data; + const { conditions, logic, targetLookup } = node.data; logger.info( `๐Ÿ” ์กฐ๊ฑด ๋…ธ๋“œ ์‹คํ–‰ - inputData ํƒ€์ž…: ${typeof inputData}, ๋ฐฐ์—ด ์—ฌ๋ถ€: ${Array.isArray(inputData)}, ๊ธธ์ด: ${Array.isArray(inputData) ? inputData.length : "N/A"}` ); - logger.info(`๐Ÿ” ์กฐ๊ฑด ๊ฐœ์ˆ˜: ${conditions?.length || 0}, ๋กœ์ง: ${logic}`); + logger.info(`๐Ÿ” ์กฐ๊ฑด ๊ฐœ์ˆ˜: ${conditions?.length || 0}, ๋กœ์ง: ${logic}, ํƒ€๊ฒŸ์กฐํšŒ: ${targetLookup ? targetLookup.tableName : "์—†์Œ"}`); if (inputData) { console.log( @@ -2865,6 +2865,9 @@ export class NodeFlowExecutionService { // ๋ฐฐ์—ด์˜ ๊ฐ ํ•ญ๋ชฉ์— ๋Œ€ํ•ด ์กฐ๊ฑด ํ‰๊ฐ€ (EXISTS ์กฐ๊ฑด์€ ๋น„๋™๊ธฐ) for (const item of inputData) { + // ํƒ€๊ฒŸ ํ…Œ์ด๋ธ” ์กฐํšŒ (DB ๊ธฐ์กด๊ฐ’ ๋น„๊ต์šฉ) + const targetRow = await this.lookupTargetRow(targetLookup, item, context); + const results: boolean[] = []; for (const condition of conditions) { @@ -2887,9 +2890,14 @@ export class NodeFlowExecutionService { `๐Ÿ” EXISTS ์กฐ๊ฑด: ${condition.field} (${fieldValue}) ${condition.operator} ${condition.lookupTable}.${condition.lookupField} => ${existsResult}` ); } else { - // ์ผ๋ฐ˜ ์—ฐ์‚ฐ์ž ์ฒ˜๋ฆฌ + // ๋น„๊ต๊ฐ’ ๊ฒฐ์ •: static(๊ณ ์ •๊ฐ’) / field(๊ฐ™์€ ๋ฐ์ดํ„ฐ ๋‚ด ํ•„๋“œ) / target(DB ๊ธฐ์กด๊ฐ’) let compareValue = condition.value; - if (condition.valueType === "field") { + if (condition.valueType === "target" && targetRow) { + compareValue = targetRow[condition.value]; + logger.info( + `๐ŸŽฏ ํƒ€๊ฒŸ(DB) ๋น„๊ต: ${condition.field} (${fieldValue}) vs DB.${condition.value} (${compareValue})` + ); + } else if (condition.valueType === "field") { compareValue = item[condition.value]; logger.info( `๐Ÿ”„ ํ•„๋“œ ์ฐธ์กฐ ๋น„๊ต: ${condition.field} (${fieldValue}) vs ${condition.value} (${compareValue})` @@ -2931,6 +2939,9 @@ export class NodeFlowExecutionService { } // ๋‹จ์ผ ๊ฐ์ฒด์ธ ๊ฒฝ์šฐ + // ํƒ€๊ฒŸ ํ…Œ์ด๋ธ” ์กฐํšŒ (DB ๊ธฐ์กด๊ฐ’ ๋น„๊ต์šฉ) + const targetRow = await this.lookupTargetRow(targetLookup, inputData, context); + const results: boolean[] = []; for (const condition of conditions) { @@ -2953,9 +2964,14 @@ export class NodeFlowExecutionService { `๐Ÿ” EXISTS ์กฐ๊ฑด: ${condition.field} (${fieldValue}) ${condition.operator} ${condition.lookupTable}.${condition.lookupField} => ${existsResult}` ); } else { - // ์ผ๋ฐ˜ ์—ฐ์‚ฐ์ž ์ฒ˜๋ฆฌ + // ๋น„๊ต๊ฐ’ ๊ฒฐ์ •: static(๊ณ ์ •๊ฐ’) / field(๊ฐ™์€ ๋ฐ์ดํ„ฐ ๋‚ด ํ•„๋“œ) / target(DB ๊ธฐ์กด๊ฐ’) let compareValue = condition.value; - if (condition.valueType === "field") { + if (condition.valueType === "target" && targetRow) { + compareValue = targetRow[condition.value]; + logger.info( + `๐ŸŽฏ ํƒ€๊ฒŸ(DB) ๋น„๊ต: ${condition.field} (${fieldValue}) vs DB.${condition.value} (${compareValue})` + ); + } else if (condition.valueType === "field") { compareValue = inputData[condition.value]; logger.info( `๐Ÿ”„ ํ•„๋“œ ์ฐธ์กฐ ๋น„๊ต: ${condition.field} (${fieldValue}) vs ${condition.value} (${compareValue})` @@ -2990,6 +3006,63 @@ export class NodeFlowExecutionService { }; } + /** + * ์กฐ๊ฑด ๋…ธ๋“œ์˜ ํƒ€๊ฒŸ ํ…Œ์ด๋ธ” ์กฐํšŒ (DB ๊ธฐ์กด๊ฐ’ ๋น„๊ต์šฉ) + * targetLookup ์„ค์ •์ด ์žˆ์„ ๋•Œ, ์†Œ์Šค ๋ฐ์ดํ„ฐ์˜ ํ‚ค๊ฐ’์œผ๋กœ DB์—์„œ ๊ธฐ์กด ๋ ˆ์ฝ”๋“œ๋ฅผ ์กฐํšŒ + */ + private static async lookupTargetRow( + targetLookup: any, + sourceRow: any, + context: ExecutionContext + ): Promise { + if (!targetLookup?.tableName || !targetLookup?.lookupKeys?.length) { + return null; + } + + try { + const whereConditions = targetLookup.lookupKeys + .map((key: any, idx: number) => `"${key.targetField}" = $${idx + 1}`) + .join(" AND "); + + const lookupValues = targetLookup.lookupKeys.map( + (key: any) => sourceRow[key.sourceField] + ); + + // ํ‚ค๊ฐ’์ด ๋น„์–ด์žˆ์œผ๋ฉด ์กฐํšŒ ๋ถˆํ•„์š” + if (lookupValues.some((v: any) => v === null || v === undefined || v === "")) { + logger.info(`โš ๏ธ ์กฐ๊ฑด ๋…ธ๋“œ ํƒ€๊ฒŸ ์กฐํšŒ: ํ‚ค๊ฐ’์ด ๋น„์–ด์žˆ์–ด ์Šคํ‚ต`); + return null; + } + + // company_code ํ•„ํ„ฐ๋ง (๋ฉ€ํ‹ฐํ…Œ๋„Œ์‹œ) + const companyCode = context.buttonContext?.companyCode || sourceRow.company_code; + let sql = `SELECT * FROM "${targetLookup.tableName}" WHERE ${whereConditions}`; + const params = [...lookupValues]; + + if (companyCode && companyCode !== "*") { + sql += ` AND company_code = $${params.length + 1}`; + params.push(companyCode); + } + + sql += " LIMIT 1"; + + logger.info(`๐ŸŽฏ ์กฐ๊ฑด ๋…ธ๋“œ ํƒ€๊ฒŸ ์กฐํšŒ: ${targetLookup.tableName}, ์กฐ๊ฑด: ${whereConditions}, ๊ฐ’: ${JSON.stringify(lookupValues)}`); + + const targetRow = await queryOne(sql, params); + + if (targetRow) { + logger.info(`๐ŸŽฏ ํƒ€๊ฒŸ ๋ฐ์ดํ„ฐ ์กฐํšŒ ์„ฑ๊ณต`); + } else { + logger.info(`๐ŸŽฏ ํƒ€๊ฒŸ ๋ฐ์ดํ„ฐ ์—†์Œ (์‹ ๊ทœ ๋ ˆ์ฝ”๋“œ)`); + } + + return targetRow; + } catch (error: any) { + logger.warn(`โš ๏ธ ์กฐ๊ฑด ๋…ธ๋“œ ํƒ€๊ฒŸ ์กฐํšŒ ์‹คํŒจ: ${error.message}`); + return null; + } + } + /** * EXISTS_IN / NOT_EXISTS_IN ์กฐ๊ฑด ํ‰๊ฐ€ * ๋‹ค๋ฅธ ํ…Œ์ด๋ธ”์— ๊ฐ’์ด ์กด์žฌํ•˜๋Š”์ง€ ํ™•์ธ diff --git a/frontend/components/dataflow/node-editor/panels/properties/ConditionProperties.tsx b/frontend/components/dataflow/node-editor/panels/properties/ConditionProperties.tsx index a2d060d4..76354925 100644 --- a/frontend/components/dataflow/node-editor/panels/properties/ConditionProperties.tsx +++ b/frontend/components/dataflow/node-editor/panels/properties/ConditionProperties.tsx @@ -251,6 +251,14 @@ export function ConditionProperties({ nodeId, data }: ConditionPropertiesProps) const [logic, setLogic] = useState<"AND" | "OR">(data.logic || "AND"); const [availableFields, setAvailableFields] = useState([]); + // ํƒ€๊ฒŸ ์กฐํšŒ ์„ค์ • (DB ๊ธฐ์กด๊ฐ’ ๋น„๊ต์šฉ) + const [targetLookup, setTargetLookup] = useState<{ + tableName: string; + tableLabel?: string; + lookupKeys: Array<{ sourceField: string; targetField: string; sourceFieldLabel?: string }>; + } | undefined>(data.targetLookup); + const [targetLookupColumns, setTargetLookupColumns] = useState([]); + // EXISTS ์—ฐ์‚ฐ์ž์šฉ ์ƒํƒœ const [allTables, setAllTables] = useState([]); const [tableColumnsCache, setTableColumnsCache] = useState>({}); @@ -262,8 +270,20 @@ export function ConditionProperties({ nodeId, data }: ConditionPropertiesProps) setDisplayName(data.displayName || "์กฐ๊ฑด ๋ถ„๊ธฐ"); setConditions(data.conditions || []); setLogic(data.logic || "AND"); + setTargetLookup(data.targetLookup); }, [data]); + // targetLookup ํ…Œ์ด๋ธ” ๋ณ€๊ฒฝ ์‹œ ์ปฌ๋Ÿผ ๋ชฉ๋ก ๋กœ๋“œ + useEffect(() => { + if (targetLookup?.tableName) { + loadTableColumns(targetLookup.tableName).then((cols) => { + setTargetLookupColumns(cols); + }); + } else { + setTargetLookupColumns([]); + } + }, [targetLookup?.tableName]); + // ์ „์ฒด ํ…Œ์ด๋ธ” ๋ชฉ๋ก ๋กœ๋“œ (EXISTS ์—ฐ์‚ฐ์ž์šฉ) useEffect(() => { const loadAllTables = async () => { @@ -559,6 +579,47 @@ export function ConditionProperties({ nodeId, data }: ConditionPropertiesProps) }); }; + // ํƒ€๊ฒŸ ์กฐํšŒ ํ…Œ์ด๋ธ” ๋ณ€๊ฒฝ + const handleTargetLookupTableChange = async (tableName: string) => { + await ensureTablesLoaded(); + const tableInfo = allTables.find((t) => t.tableName === tableName); + const newLookup = { + tableName, + tableLabel: tableInfo?.tableLabel || tableName, + lookupKeys: targetLookup?.lookupKeys || [], + }; + setTargetLookup(newLookup); + updateNode(nodeId, { targetLookup: newLookup }); + + // ์ปฌ๋Ÿผ ๋กœ๋“œ + const cols = await loadTableColumns(tableName); + setTargetLookupColumns(cols); + }; + + // ํƒ€๊ฒŸ ์กฐํšŒ ํ‚ค ํ•„๋“œ ๋ณ€๊ฒฝ + const handleTargetLookupKeyChange = (sourceField: string, targetField: string) => { + if (!targetLookup) return; + const sourceFieldInfo = availableFields.find((f) => f.name === sourceField); + const newLookup = { + ...targetLookup, + lookupKeys: [{ sourceField, targetField, sourceFieldLabel: sourceFieldInfo?.label || sourceField }], + }; + setTargetLookup(newLookup); + updateNode(nodeId, { targetLookup: newLookup }); + }; + + // ํƒ€๊ฒŸ ์กฐํšŒ ์ œ๊ฑฐ + const handleRemoveTargetLookup = () => { + setTargetLookup(undefined); + updateNode(nodeId, { targetLookup: undefined }); + // target ํƒ€์ž… ์กฐ๊ฑด๋“ค์„ field๋กœ ๋ณ€๊ฒฝ + const newConditions = conditions.map((c) => + (c as any).valueType === "target" ? { ...c, valueType: "field" } : c + ); + setConditions(newConditions); + updateNode(nodeId, { conditions: newConditions }); + }; + return (
@@ -597,6 +658,119 @@ export function ConditionProperties({ nodeId, data }: ConditionPropertiesProps)
+ {/* ํƒ€๊ฒŸ ์กฐํšŒ (DB ๊ธฐ์กด๊ฐ’ ๋น„๊ต) */} +
+
+

+ + ํƒ€๊ฒŸ ์กฐํšŒ (DB ๊ธฐ์กด๊ฐ’) +

+
+ + {!targetLookup ? ( +
+
+ DB์˜ ๊ธฐ์กด๊ฐ’๊ณผ ๋น„๊ตํ•˜๋ ค๋ฉด ํƒ€๊ฒŸ ํ…Œ์ด๋ธ”์„ ์„ค์ •ํ•˜์„ธ์š”. +
+ +
+ ) : ( +
+
+ ํƒ€๊ฒŸ ํ…Œ์ด๋ธ” + +
+ + {/* ํ…Œ์ด๋ธ” ์„ ํƒ */} + {allTables.length > 0 ? ( + + ) : ( +
+ ํ…Œ์ด๋ธ” ๋กœ๋”ฉ ์ค‘... +
+ )} + + {/* ํ‚ค ํ•„๋“œ ๋งคํ•‘ */} + {targetLookup.tableName && ( +
+ +
+ + = + {targetLookupColumns.length > 0 ? ( + + ) : ( +
+ ์ปฌ๋Ÿผ ๋กœ๋”ฉ ์ค‘... +
+ )} +
+
+ ๋น„๊ต ๊ฐ’ ํƒ€์ž…์—์„œ "ํƒ€๊ฒŸ ํ•„๋“œ (DB ๊ธฐ์กด๊ฐ’)"์„ ์„ ํƒํ•˜๋ฉด ์ด ํ…Œ์ด๋ธ”์˜ ๊ธฐ์กด๊ฐ’๊ณผ ๋น„๊ตํ•ฉ๋‹ˆ๋‹ค. +
+
+ )} +
+ )} +
+ {/* ์กฐ๊ฑด์‹ */}
@@ -738,15 +912,46 @@ export function ConditionProperties({ nodeId, data }: ConditionPropertiesProps) ๊ณ ์ •๊ฐ’ ํ•„๋“œ ์ฐธ์กฐ + {targetLookup?.tableName && ( + ํƒ€๊ฒŸ ํ•„๋“œ (DB ๊ธฐ์กด๊ฐ’) + )}
- {(condition as any).valueType === "field" ? ( + {(condition as any).valueType === "target" ? ( + // ํƒ€๊ฒŸ ํ•„๋“œ (DB ๊ธฐ์กด๊ฐ’): ํƒ€๊ฒŸ ํ…Œ์ด๋ธ” ์ปฌ๋Ÿผ์—์„œ ์„ ํƒ + targetLookupColumns.length > 0 ? ( + + ) : ( +
+ ํƒ€๊ฒŸ ์กฐํšŒ๋ฅผ ๋จผ์ € ์„ค์ •ํ•˜์„ธ์š” +
+ ) + ) : (condition as any).valueType === "field" ? ( // ํ•„๋“œ ์ฐธ์กฐ: ๋“œ๋กญ๋‹ค์šด์œผ๋กœ ์„ ํƒ availableFields.length > 0 ? (