diff --git a/backend-node/src/routes/dataRoutes.ts b/backend-node/src/routes/dataRoutes.ts index f87aa5d6..f9d88d92 100644 --- a/backend-node/src/routes/dataRoutes.ts +++ b/backend-node/src/routes/dataRoutes.ts @@ -698,6 +698,7 @@ router.post( try { const { tableName } = req.params; const filterConditions = req.body; + const userCompany = req.user?.companyCode; if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(tableName)) { return res.status(400).json({ @@ -706,11 +707,12 @@ router.post( }); } - console.log(`๐Ÿ—‘๏ธ ๊ทธ๋ฃน ์‚ญ์ œ:`, { tableName, filterConditions }); + console.log(`๐Ÿ—‘๏ธ ๊ทธ๋ฃน ์‚ญ์ œ:`, { tableName, filterConditions, userCompany }); const result = await dataService.deleteGroupRecords( tableName, - filterConditions + filterConditions, + userCompany // ํšŒ์‚ฌ ์ฝ”๋“œ ์ „๋‹ฌ ); if (!result.success) { diff --git a/backend-node/src/services/dataService.ts b/backend-node/src/services/dataService.ts index a1a494f2..75c57673 100644 --- a/backend-node/src/services/dataService.ts +++ b/backend-node/src/services/dataService.ts @@ -1189,6 +1189,13 @@ class DataService { [tableName] ); + console.log(`๐Ÿ” ํ…Œ์ด๋ธ” ${tableName}์˜ Primary Key ์กฐํšŒ ๊ฒฐ๊ณผ:`, { + pkColumns: pkResult.map((r) => r.attname), + pkCount: pkResult.length, + inputId: typeof id === "object" ? JSON.stringify(id).substring(0, 200) + "..." : id, + inputIdType: typeof id, + }); + let whereClauses: string[] = []; let params: any[] = []; @@ -1216,17 +1223,31 @@ class DataService { params.push(typeof id === "object" ? id[pkColumn] : id); } - const queryText = `DELETE FROM "${tableName}" WHERE ${whereClauses.join(" AND ")}`; + const queryText = `DELETE FROM "${tableName}" WHERE ${whereClauses.join(" AND ")} RETURNING *`; console.log(`๐Ÿ—‘๏ธ ์‚ญ์ œ ์ฟผ๋ฆฌ:`, queryText, params); const result = await query(queryText, params); + // ์‚ญ์ œ๋œ ํ–‰์ด ์—†์œผ๋ฉด ์‹คํŒจ ์ฒ˜๋ฆฌ + if (result.length === 0) { + console.warn( + `โš ๏ธ ๋ ˆ์ฝ”๋“œ ์‚ญ์ œ ์‹คํŒจ: ${tableName}, ํ•ด๋‹น ์กฐ๊ฑด์— ๋งž๋Š” ๋ ˆ์ฝ”๋“œ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.`, + { whereClauses, params } + ); + return { + success: false, + message: "์‚ญ์ œํ•  ๋ ˆ์ฝ”๋“œ๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. ์ด๋ฏธ ์‚ญ์ œ๋˜์—ˆ๊ฑฐ๋‚˜ ๊ถŒํ•œ์ด ์—†์Šต๋‹ˆ๋‹ค.", + error: "RECORD_NOT_FOUND", + }; + } + console.log( `โœ… ๋ ˆ์ฝ”๋“œ ์‚ญ์ œ ์™„๋ฃŒ: ${tableName}, ์˜ํ–ฅ๋ฐ›์€ ํ–‰: ${result.length}` ); return { success: true, + data: result[0], // ์‚ญ์ œ๋œ ๋ ˆ์ฝ”๋“œ ์ •๋ณด ๋ฐ˜ํ™˜ }; } catch (error) { console.error(`๋ ˆ์ฝ”๋“œ ์‚ญ์ œ ์˜ค๋ฅ˜ (${tableName}):`, error); @@ -1240,10 +1261,14 @@ class DataService { /** * ์กฐ๊ฑด์— ๋งž๋Š” ๋ชจ๋“  ๋ ˆ์ฝ”๋“œ ์‚ญ์ œ (๊ทธ๋ฃน ์‚ญ์ œ) + * @param tableName ํ…Œ์ด๋ธ”๋ช… + * @param filterConditions ์‚ญ์ œ ์กฐ๊ฑด + * @param userCompany ์‚ฌ์šฉ์ž ํšŒ์‚ฌ ์ฝ”๋“œ (๋ฉ€ํ‹ฐํ…Œ๋„Œ์‹œ ํ•„ํ„ฐ๋ง) */ async deleteGroupRecords( tableName: string, - filterConditions: Record + filterConditions: Record, + userCompany?: string ): Promise> { try { const validation = await this.validateTableAccess(tableName); @@ -1255,6 +1280,7 @@ class DataService { const whereValues: any[] = []; let paramIndex = 1; + // ์‚ฌ์šฉ์ž ํ•„ํ„ฐ ์กฐ๊ฑด ์ถ”๊ฐ€ for (const [key, value] of Object.entries(filterConditions)) { whereConditions.push(`"${key}" = $${paramIndex}`); whereValues.push(value); @@ -1269,10 +1295,24 @@ class DataService { }; } + // ๐Ÿ”’ ๋ฉ€ํ‹ฐํ…Œ๋„Œ์‹œ: company_code ํ•„ํ„ฐ๋ง (์ตœ๊ณ  ๊ด€๋ฆฌ์ž ์ œ์™ธ) + const hasCompanyCode = await this.checkColumnExists(tableName, "company_code"); + if (hasCompanyCode && userCompany && userCompany !== "*") { + whereConditions.push(`"company_code" = $${paramIndex}`); + whereValues.push(userCompany); + paramIndex++; + console.log(`๐Ÿ”’ ๋ฉ€ํ‹ฐํ…Œ๋„Œ์‹œ ํ•„ํ„ฐ ์ ์šฉ: company_code = ${userCompany}`); + } + const whereClause = whereConditions.join(" AND "); const deleteQuery = `DELETE FROM "${tableName}" WHERE ${whereClause} RETURNING *`; - console.log(`๐Ÿ—‘๏ธ ๊ทธ๋ฃน ์‚ญ์ œ:`, { tableName, conditions: filterConditions }); + console.log(`๐Ÿ—‘๏ธ ๊ทธ๋ฃน ์‚ญ์ œ:`, { + tableName, + conditions: filterConditions, + userCompany, + whereClause, + }); const result = await pool.query(deleteQuery, whereValues); diff --git a/frontend/lib/registry/components/split-panel-layout/SplitPanelLayoutComponent.tsx b/frontend/lib/registry/components/split-panel-layout/SplitPanelLayoutComponent.tsx index ad7f5302..afc5c13e 100644 --- a/frontend/lib/registry/components/split-panel-layout/SplitPanelLayoutComponent.tsx +++ b/frontend/lib/registry/components/split-panel-layout/SplitPanelLayoutComponent.tsx @@ -1613,47 +1613,89 @@ export const SplitPanelLayoutComponent: React.FC try { console.log("๐Ÿ—‘๏ธ ๋ฐ์ดํ„ฐ ์‚ญ์ œ:", { tableName, primaryKey }); - // ๐Ÿ” ์ค‘๋ณต ์ œ๊ฑฐ ์„ค์ • ๋””๋ฒ„๊น… - console.log("๐Ÿ” ์ค‘๋ณต ์ œ๊ฑฐ ๋””๋ฒ„๊น…:", { + // ๐Ÿ” ๊ทธ๋ฃน ์‚ญ์ œ ์„ค์ • ํ™•์ธ (editButton.groupByColumns ๋˜๋Š” deduplication) + const groupByColumns = componentConfig.rightPanel?.editButton?.groupByColumns || []; + const deduplication = componentConfig.rightPanel?.dataFilter?.deduplication; + + console.log("๐Ÿ” ์‚ญ์ œ ์„ค์ • ๋””๋ฒ„๊น…:", { panel: deleteModalPanel, - dataFilter: componentConfig.rightPanel?.dataFilter, - deduplication: componentConfig.rightPanel?.dataFilter?.deduplication, - enabled: componentConfig.rightPanel?.dataFilter?.deduplication?.enabled, + groupByColumns, + deduplication, + deduplicationEnabled: deduplication?.enabled, }); let result; - // ๐Ÿ”ง ์ค‘๋ณต ์ œ๊ฑฐ๊ฐ€ ํ™œ์„ฑํ™”๋œ ๊ฒฝ์šฐ, groupByColumn ๊ธฐ์ค€์œผ๋กœ ๋ชจ๋“  ๊ด€๋ จ ๋ ˆ์ฝ”๋“œ ์‚ญ์ œ - if (deleteModalPanel === "right" && componentConfig.rightPanel?.dataFilter?.deduplication?.enabled) { - const deduplication = componentConfig.rightPanel.dataFilter.deduplication; - const groupByColumn = deduplication.groupByColumn; - - if (groupByColumn && deleteModalItem[groupByColumn]) { - const groupValue = deleteModalItem[groupByColumn]; - console.log(`๐Ÿ”— ์ค‘๋ณต ์ œ๊ฑฐ ํ™œ์„ฑํ™”: ${groupByColumn} = ${groupValue} ๊ธฐ์ค€์œผ๋กœ ๋ชจ๋“  ๋ ˆ์ฝ”๋“œ ์‚ญ์ œ`); - - // groupByColumn ๊ฐ’์œผ๋กœ ํ•„ํ„ฐ๋งํ•˜์—ฌ ์‚ญ์ œ - const filterConditions: Record = { - [groupByColumn]: groupValue, - }; - - // ์ขŒ์ธก ํŒจ๋„์˜ ์„ ํƒ๋œ ํ•ญ๋ชฉ ์ •๋ณด๋„ ํฌํ•จ (customer_id ๋“ฑ) - if (selectedLeftItem && componentConfig.rightPanel?.mode === "join") { - const leftColumn = componentConfig.rightPanel.join.leftColumn; - const rightColumn = componentConfig.rightPanel.join.rightColumn; - filterConditions[rightColumn] = selectedLeftItem[leftColumn]; + // ๐Ÿ”ง ์šฐ์ธก ํŒจ๋„ ์‚ญ์ œ ์‹œ ๊ทธ๋ฃน ์‚ญ์ œ ์กฐ๊ฑด ํ™•์ธ + if (deleteModalPanel === "right") { + // 1. groupByColumns๊ฐ€ ์„ค์ •๋œ ๊ฒฝ์šฐ (ํŒจ๋„ ์„ค์ •์—์„œ ์„ ํƒ๋œ ์ปฌ๋Ÿผ๋“ค) + if (groupByColumns.length > 0) { + const filterConditions: Record = {}; + + // ์„ ํƒ๋œ ์ปฌ๋Ÿผ๋“ค์˜ ๊ฐ’์„ ํ•„ํ„ฐ ์กฐ๊ฑด์œผ๋กœ ์ถ”๊ฐ€ + for (const col of groupByColumns) { + if (deleteModalItem[col] !== undefined && deleteModalItem[col] !== null) { + filterConditions[col] = deleteModalItem[col]; + } } - console.log("๐Ÿ—‘๏ธ ๊ทธ๋ฃน ์‚ญ์ œ ์กฐ๊ฑด:", filterConditions); + // ๐Ÿ”’ ์•ˆ์ „์žฅ์น˜: ์กฐ์ธ ๋ชจ๋“œ์—์„œ ์ขŒ์ธก ํŒจ๋„์˜ ํ‚ค ๊ฐ’๋„ ํ•„ํ„ฐ ์กฐ๊ฑด์— ํฌํ•จ + // (๋‹ค๋ฅธ ๊ฑฐ๋ž˜์ฒ˜์˜ ๊ฐ™์€ ํ’ˆ๋ชฉ์ด ์‚ญ์ œ๋˜๋Š” ๊ฒƒ์„ ๋ฐฉ์ง€) + if (selectedLeftItem && componentConfig.rightPanel?.mode === "join") { + const leftColumn = componentConfig.rightPanel.join?.leftColumn; + const rightColumn = componentConfig.rightPanel.join?.rightColumn; + if (leftColumn && rightColumn && selectedLeftItem[leftColumn]) { + // rightColumn์ด filterConditions์— ์—†์œผ๋ฉด ์ถ”๊ฐ€ + if (!filterConditions[rightColumn]) { + filterConditions[rightColumn] = selectedLeftItem[leftColumn]; + console.log(`๐Ÿ”’ ์•ˆ์ „์žฅ์น˜: ${rightColumn} = ${selectedLeftItem[leftColumn]} ์ถ”๊ฐ€`); + } + } + } - // ๊ทธ๋ฃน ์‚ญ์ œ API ํ˜ธ์ถœ - result = await dataApi.deleteGroupRecords(tableName, filterConditions); - } else { - // ๋‹จ์ผ ๋ ˆ์ฝ”๋“œ ์‚ญ์ œ + // ํ•„ํ„ฐ ์กฐ๊ฑด์ด ์žˆ์œผ๋ฉด ๊ทธ๋ฃน ์‚ญ์ œ + if (Object.keys(filterConditions).length > 0) { + console.log(`๐Ÿ”— ๊ทธ๋ฃน ์‚ญ์ œ (groupByColumns): ${groupByColumns.join(", ")} ๊ธฐ์ค€`); + console.log("๐Ÿ—‘๏ธ ๊ทธ๋ฃน ์‚ญ์ œ ์กฐ๊ฑด:", filterConditions); + + result = await dataApi.deleteGroupRecords(tableName, filterConditions); + } else { + // ํ•„ํ„ฐ ์กฐ๊ฑด์ด ์—†์œผ๋ฉด ๋‹จ์ผ ์‚ญ์ œ + console.log("โš ๏ธ groupByColumns ๊ฐ’์ด ์—†์–ด ๋‹จ์ผ ์‚ญ์ œ๋กœ ์ „ํ™˜"); + result = await dataApi.deleteRecord(tableName, primaryKey); + } + } + // 2. ์ค‘๋ณต ์ œ๊ฑฐ(deduplication)๊ฐ€ ํ™œ์„ฑํ™”๋œ ๊ฒฝ์šฐ + else if (deduplication?.enabled && deduplication?.groupByColumn) { + const groupByColumn = deduplication.groupByColumn; + const groupValue = deleteModalItem[groupByColumn]; + + if (groupValue) { + console.log(`๐Ÿ”— ์ค‘๋ณต ์ œ๊ฑฐ ํ™œ์„ฑํ™”: ${groupByColumn} = ${groupValue} ๊ธฐ์ค€์œผ๋กœ ๋ชจ๋“  ๋ ˆ์ฝ”๋“œ ์‚ญ์ œ`); + + const filterConditions: Record = { + [groupByColumn]: groupValue, + }; + + // ์ขŒ์ธก ํŒจ๋„์˜ ์„ ํƒ๋œ ํ•ญ๋ชฉ ์ •๋ณด๋„ ํฌํ•จ (customer_id ๋“ฑ) + if (selectedLeftItem && componentConfig.rightPanel?.mode === "join") { + const leftColumn = componentConfig.rightPanel.join.leftColumn; + const rightColumn = componentConfig.rightPanel.join.rightColumn; + filterConditions[rightColumn] = selectedLeftItem[leftColumn]; + } + + console.log("๐Ÿ—‘๏ธ ๊ทธ๋ฃน ์‚ญ์ œ ์กฐ๊ฑด:", filterConditions); + result = await dataApi.deleteGroupRecords(tableName, filterConditions); + } else { + result = await dataApi.deleteRecord(tableName, primaryKey); + } + } + // 3. ๊ทธ ์™ธ: ๋‹จ์ผ ๋ ˆ์ฝ”๋“œ ์‚ญ์ œ + else { result = await dataApi.deleteRecord(tableName, primaryKey); } } else { - // ๋‹จ์ผ ๋ ˆ์ฝ”๋“œ ์‚ญ์ œ + // ์ขŒ์ธก ํŒจ๋„: ๋‹จ์ผ ๋ ˆ์ฝ”๋“œ ์‚ญ์ œ result = await dataApi.deleteRecord(tableName, primaryKey); }