From 239e4800c716c4d1376801a5d35020310a11e48f Mon Sep 17 00:00:00 2001 From: kjs Date: Mon, 5 Jan 2026 13:37:39 +0900 Subject: [PATCH 1/2] =?UTF-8?q?=EC=A6=89=EC=8B=9C=EC=A0=80=EC=9E=A5=20?= =?UTF-8?q?=EC=95=A1=EC=85=98=20=ED=95=84=EC=88=98=ED=95=AD=EB=AA=A9=20?= =?UTF-8?q?=EC=B2=B4=ED=81=AC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/lib/utils/buttonActions.ts | 63 +++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/frontend/lib/utils/buttonActions.ts b/frontend/lib/utils/buttonActions.ts index 327cb87f..5587fc1a 100644 --- a/frontend/lib/utils/buttonActions.ts +++ b/frontend/lib/utils/buttonActions.ts @@ -5931,6 +5931,69 @@ export class ButtonActionExecutor { return false; } + // ✅ allComponents가 있으면 기존 필수 항목 검증 수행 + if (context.allComponents && context.allComponents.length > 0) { + console.log("🔍 [handleQuickInsert] 필수 항목 검증 시작:", { + hasAllComponents: !!context.allComponents, + allComponentsLength: context.allComponents?.length || 0, + }); + const requiredValidation = this.validateRequiredFields(context); + if (!requiredValidation.isValid) { + console.log("❌ [handleQuickInsert] 필수 항목 누락:", requiredValidation.missingFields); + toast.error(`필수 항목을 입력해주세요: ${requiredValidation.missingFields.join(", ")}`); + return false; + } + console.log("✅ [handleQuickInsert] 필수 항목 검증 통과"); + } + + // ✅ quickInsert 전용 검증: component 타입 매핑에서 값이 비어있는지 확인 + const mappingsForValidation = quickInsertConfig.columnMappings || []; + const missingMappingFields: string[] = []; + + for (const mapping of mappingsForValidation) { + // component 타입 매핑은 필수 입력으로 간주 + if (mapping.sourceType === "component" && mapping.sourceComponentId) { + let value: any = undefined; + + // 값 가져오기 (formData에서) + if (mapping.sourceColumnName) { + value = context.formData?.[mapping.sourceColumnName]; + } + if (value === undefined || value === null) { + value = context.formData?.[mapping.sourceComponentId]; + } + // allComponents에서 컴포넌트 찾아서 columnName으로 시도 + if ((value === undefined || value === null) && context.allComponents) { + const comp = context.allComponents.find((c: any) => c.id === mapping.sourceComponentId); + if (comp?.columnName) { + value = context.formData?.[comp.columnName]; + } + } + // targetColumn으로 폴백 + if ((value === undefined || value === null) && mapping.targetColumn) { + value = context.formData?.[mapping.targetColumn]; + } + + // 값이 비어있으면 필수 누락으로 처리 + if (value === undefined || value === null || (typeof value === "string" && value.trim() === "")) { + console.log("❌ [handleQuickInsert] component 매핑 값 누락:", { + targetColumn: mapping.targetColumn, + sourceComponentId: mapping.sourceComponentId, + sourceColumnName: mapping.sourceColumnName, + value, + }); + missingMappingFields.push(mapping.targetColumn); + } + } + } + + if (missingMappingFields.length > 0) { + console.log("❌ [handleQuickInsert] 필수 입력 항목 누락:", missingMappingFields); + toast.error(`다음 항목을 입력해주세요: ${missingMappingFields.join(", ")}`); + return false; + } + console.log("✅ [handleQuickInsert] quickInsert 매핑 검증 통과"); + const { formData, splitPanelContext, userId, userName, companyCode } = context; console.log("⚡ Quick Insert 상세 정보:", { From 914f3d57f33ffd2bc0aa14da820505d00c0c7814 Mon Sep 17 00:00:00 2001 From: SeongHyun Kim Date: Mon, 5 Jan 2026 13:58:13 +0900 Subject: [PATCH 2/2] =?UTF-8?q?fix:=20TableList=20=EC=B9=B4=ED=85=8C?= =?UTF-8?q?=EA=B3=A0=EB=A6=AC=20=EB=9D=BC=EB=B2=A8=20=ED=91=9C=EC=8B=9C?= =?UTF-8?q?=EB=A5=BC=20=EC=9C=84=ED=95=9C=20=EB=A9=80=ED=8B=B0=ED=85=8C?= =?UTF-8?q?=EB=84=8C=EC=8B=9C=20fallback=20=EB=A1=9C=EC=A7=81=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20getColumnInputTypes=20API=EC=97=90=EC=84=9C=20?= =?UTF-8?q?=ED=9A=8C=EC=82=AC=EB=B3=84=20=EC=84=A4=EC=A0=95=EC=9D=B4=20?= =?UTF-8?q?=EC=97=86=EC=9D=84=20=EB=95=8C=20=EA=B8=B0=EB=B3=B8=EC=84=A4?= =?UTF-8?q?=EC=A0=95()=20fallback=20=EC=A0=81=EC=9A=A9=20table=5Ftype=5Fco?= =?UTF-8?q?lumns,=20category=5Fcolumn=5Fmapping=20=EC=A1=B0=ED=9A=8C=20?= =?UTF-8?q?=EC=8B=9C=20DISTINCT=20ON=20+=20ORDER=20BY=20CASE=20WHEN=20?= =?UTF-8?q?=ED=8C=A8=ED=84=B4=20=EC=82=AC=EC=9A=A9=20=EC=98=81=ED=96=A5=20?= =?UTF-8?q?=EB=B2=94=EC=9C=84:=20=EB=AA=A8=EB=93=A0=20TableList=20?= =?UTF-8?q?=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=EC=9D=98=20=EC=B9=B4?= =?UTF-8?q?=ED=85=8C=EA=B3=A0=EB=A6=AC=20=EC=BB=AC=EB=9F=BC=20=ED=91=9C?= =?UTF-8?q?=EC=8B=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/services/tableManagementService.ts | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/backend-node/src/services/tableManagementService.ts b/backend-node/src/services/tableManagementService.ts index b714b186..def9a978 100644 --- a/backend-node/src/services/tableManagementService.ts +++ b/backend-node/src/services/tableManagementService.ts @@ -3930,9 +3930,10 @@ export class TableManagementService { `컬럼 입력타입 정보 조회: ${tableName}, company: ${companyCode}` ); - // table_type_columns에서 입력타입 정보 조회 (company_code 필터링) + // table_type_columns에서 입력타입 정보 조회 + // 회사별 설정 우선, 없으면 기본 설정(*) fallback const rawInputTypes = await query( - `SELECT + `SELECT DISTINCT ON (ttc.column_name) ttc.column_name as "columnName", COALESCE(cl.column_label, ttc.column_name) as "displayName", ttc.input_type as "inputType", @@ -3946,8 +3947,10 @@ export class TableManagementService { LEFT JOIN information_schema.columns ic ON ttc.table_name = ic.table_name AND ttc.column_name = ic.column_name WHERE ttc.table_name = $1 - AND ttc.company_code = $2 - ORDER BY ttc.display_order, ttc.column_name`, + AND ttc.company_code IN ($2, '*') + ORDER BY ttc.column_name, + CASE WHEN ttc.company_code = $2 THEN 0 ELSE 1 END, + ttc.display_order`, [tableName, companyCode] ); @@ -3961,17 +3964,20 @@ export class TableManagementService { const mappingTableExists = tableExistsResult[0]?.table_exists === true; // 카테고리 컬럼의 경우, 매핑된 메뉴 목록 조회 + // 회사별 설정 우선, 없으면 기본 설정(*) fallback let categoryMappings: Map = new Map(); if (mappingTableExists) { logger.info("카테고리 매핑 조회 시작", { tableName, companyCode }); const mappings = await query( - `SELECT + `SELECT DISTINCT ON (logical_column_name, menu_objid) logical_column_name as "columnName", menu_objid as "menuObjid" FROM category_column_mapping WHERE table_name = $1 - AND company_code = $2`, + AND company_code IN ($2, '*') + ORDER BY logical_column_name, menu_objid, + CASE WHEN company_code = $2 THEN 0 ELSE 1 END`, [tableName, companyCode] );