diff --git a/backend-node/src/routes/dataRoutes.ts b/backend-node/src/routes/dataRoutes.ts index 574f1cf8..a7757397 100644 --- a/backend-node/src/routes/dataRoutes.ts +++ b/backend-node/src/routes/dataRoutes.ts @@ -606,7 +606,7 @@ router.get( }); } - const { enableEntityJoin, groupByColumns } = req.query; + const { enableEntityJoin, groupByColumns, primaryKeyColumn } = req.query; const enableEntityJoinFlag = enableEntityJoin === "true" || (typeof enableEntityJoin === "boolean" && enableEntityJoin); @@ -626,17 +626,22 @@ router.get( } } + // ๐Ÿ†• primaryKeyColumn ํŒŒ์‹ฑ + const primaryKeyColumnStr = typeof primaryKeyColumn === "string" ? primaryKeyColumn : undefined; + console.log(`๐Ÿ” ๋ ˆ์ฝ”๋“œ ์ƒ์„ธ ์กฐํšŒ: ${tableName}/${id}`, { enableEntityJoin: enableEntityJoinFlag, groupByColumns: groupByColumnsArray, + primaryKeyColumn: primaryKeyColumnStr, }); - // ๋ ˆ์ฝ”๋“œ ์ƒ์„ธ ์กฐํšŒ (Entity Join ์˜ต์…˜ + ๊ทธ๋ฃนํ•‘ ์˜ต์…˜ ํฌํ•จ) + // ๋ ˆ์ฝ”๋“œ ์ƒ์„ธ ์กฐํšŒ (Entity Join ์˜ต์…˜ + ๊ทธ๋ฃนํ•‘ ์˜ต์…˜ + Primary Key ์ปฌ๋Ÿผ ํฌํ•จ) const result = await dataService.getRecordDetail( tableName, id, enableEntityJoinFlag, - groupByColumnsArray + groupByColumnsArray, + primaryKeyColumnStr // ๐Ÿ†• Primary Key ์ปฌ๋Ÿผ๋ช… ์ „๋‹ฌ ); if (!result.success) { diff --git a/backend-node/src/services/dataService.ts b/backend-node/src/services/dataService.ts index 8c6e63f0..60de20db 100644 --- a/backend-node/src/services/dataService.ts +++ b/backend-node/src/services/dataService.ts @@ -490,7 +490,8 @@ class DataService { tableName: string, id: string | number, enableEntityJoin: boolean = false, - groupByColumns: string[] = [] + groupByColumns: string[] = [], + primaryKeyColumn?: string // ๐Ÿ†• ํด๋ผ์ด์–ธํŠธ์—์„œ ์ „๋‹ฌํ•œ Primary Key ์ปฌ๋Ÿผ๋ช… ): Promise> { try { // ํ…Œ์ด๋ธ” ์ ‘๊ทผ ๊ฒ€์ฆ @@ -499,20 +500,30 @@ class DataService { return validation.error!; } - // Primary Key ์ปฌ๋Ÿผ ์ฐพ๊ธฐ - const pkResult = await query<{ attname: string }>( - `SELECT a.attname - FROM pg_index i - JOIN pg_attribute a ON a.attrelid = i.indrelid AND a.attnum = ANY(i.indkey) - WHERE i.indrelid = $1::regclass AND i.indisprimary`, - [tableName] - ); + // ๐Ÿ†• ํด๋ผ์ด์–ธํŠธ์—์„œ ์ „๋‹ฌํ•œ Primary Key ์ปฌ๋Ÿผ์ด ์žˆ์œผ๋ฉด ์šฐ์„  ์‚ฌ์šฉ + let pkColumn = primaryKeyColumn || ""; + + // Primary Key ์ปฌ๋Ÿผ์ด ์—†์œผ๋ฉด ์ž๋™ ๊ฐ์ง€ + if (!pkColumn) { + const pkResult = await query<{ attname: string }>( + `SELECT a.attname + FROM pg_index i + JOIN pg_attribute a ON a.attrelid = i.indrelid AND a.attnum = ANY(i.indkey) + WHERE i.indrelid = $1::regclass AND i.indisprimary`, + [tableName] + ); - let pkColumn = "id"; // ๊ธฐ๋ณธ๊ฐ’ - if (pkResult.length > 0) { - pkColumn = pkResult[0].attname; + pkColumn = "id"; // ๊ธฐ๋ณธ๊ฐ’ + if (pkResult.length > 0) { + pkColumn = pkResult[0].attname; + } + console.log(`๐Ÿ”‘ [getRecordDetail] ์ž๋™ ๊ฐ์ง€๋œ Primary Key:`, pkResult); + } else { + console.log(`๐Ÿ”‘ [getRecordDetail] ํด๋ผ์ด์–ธํŠธ ์ œ๊ณต Primary Key: ${pkColumn}`); } + console.log(`๐Ÿ”‘ [getRecordDetail] ํ…Œ์ด๋ธ”: ${tableName}, Primary Key ์ปฌ๋Ÿผ: ${pkColumn}, ์กฐํšŒ ID: ${id}`); + // ๐Ÿ†• Entity Join์ด ํ™œ์„ฑํ™”๋œ ๊ฒฝ์šฐ if (enableEntityJoin) { const { EntityJoinService } = await import("./entityJoinService"); diff --git a/backend-node/src/services/entityJoinService.ts b/backend-node/src/services/entityJoinService.ts index 96f005a0..f13cca19 100644 --- a/backend-node/src/services/entityJoinService.ts +++ b/backend-node/src/services/entityJoinService.ts @@ -334,6 +334,10 @@ export class EntityJoinService { ); }); + // ๐Ÿ”ง _label ๋ณ„์นญ ์ค‘๋ณต ๋ฐฉ์ง€๋ฅผ ์œ„ํ•œ Set + // ๊ฐ™์€ sourceColumn์—์„œ ์—ฌ๋Ÿฌ ์กฐ์ธ ์„ค์ •์ด ์žˆ์„ ๋•Œ _label์€ ์ฒซ ๋ฒˆ์งธ๋งŒ ์ƒ์„ฑ + const generatedLabelAliases = new Set(); + const joinColumns = joinConfigs .map((config) => { const aliasKey = `${config.referenceTable}:${config.sourceColumn}`; @@ -368,16 +372,26 @@ export class EntityJoinService { // _label ํ•„๋“œ๋„ ํ•จ๊ป˜ SELECT (ํ”„๋ก ํŠธ์—”๋“œ getColumnUniqueValues์šฉ) // sourceColumn_label ํ˜•์‹์œผ๋กœ ์ถ”๊ฐ€ - resultColumns.push( - `COALESCE(${alias}.${col}::TEXT, '') AS ${config.sourceColumn}_label` - ); + // ๐Ÿ”ง ์ค‘๋ณต ๋ฐฉ์ง€: ๊ฐ™์€ sourceColumn์—์„œ _label์€ ์ฒซ ๋ฒˆ์งธ๋งŒ ์ƒ์„ฑ + const labelAlias = `${config.sourceColumn}_label`; + if (!generatedLabelAliases.has(labelAlias)) { + resultColumns.push( + `COALESCE(${alias}.${col}::TEXT, '') AS ${labelAlias}` + ); + generatedLabelAliases.add(labelAlias); + } // ๐Ÿ†• referenceColumn (PK)๋„ ํ•ญ์ƒ SELECT (parentDataMapping์šฉ) // ์˜ˆ: customer_code, item_number ๋“ฑ // col๊ณผ ๋™์ผํ•ด๋„ ๋ณ„๋„์˜ alias๋กœ ์ถ”๊ฐ€ (customer_code as customer_code) - resultColumns.push( - `COALESCE(${alias}.${config.referenceColumn}::TEXT, '') AS ${config.referenceColumn}` - ); + // ๐Ÿ”ง ์ค‘๋ณต ๋ฐฉ์ง€: referenceColumn๋„ ํ•œ ๋ฒˆ๋งŒ ์ถ”๊ฐ€ + const refColAlias = config.referenceColumn; + if (!generatedLabelAliases.has(refColAlias)) { + resultColumns.push( + `COALESCE(${alias}.${config.referenceColumn}::TEXT, '') AS ${refColAlias}` + ); + generatedLabelAliases.add(refColAlias); + } } else { resultColumns.push( `COALESCE(main.${col}::TEXT, '') AS ${config.aliasColumn}` @@ -392,6 +406,11 @@ export class EntityJoinService { const individualAlias = `${config.sourceColumn}_${col}`; + // ๐Ÿ”ง ์ค‘๋ณต ๋ฐฉ์ง€: ๊ฐ™์€ alias๊ฐ€ ์ด๋ฏธ ์ƒ์„ฑ๋˜์—ˆ์œผ๋ฉด ์Šคํ‚ต + if (generatedLabelAliases.has(individualAlias)) { + return; + } + if (isJoinTableColumn) { // ์กฐ์ธ ํ…Œ์ด๋ธ” ์ปฌ๋Ÿผ์€ ์กฐ์ธ ๋ณ„์นญ ์‚ฌ์šฉ resultColumns.push( @@ -403,6 +422,7 @@ export class EntityJoinService { `COALESCE(main.${col}::TEXT, '') AS ${individualAlias}` ); } + generatedLabelAliases.add(individualAlias); }); // ๐Ÿ†• referenceColumn (PK)๋„ ํ•จ๊ป˜ SELECT (parentDataMapping์šฉ) @@ -410,11 +430,13 @@ export class EntityJoinService { config.referenceTable && config.referenceTable !== tableName; if ( isJoinTableColumn && - !displayColumns.includes(config.referenceColumn) + !displayColumns.includes(config.referenceColumn) && + !generatedLabelAliases.has(config.referenceColumn) // ๐Ÿ”ง ์ค‘๋ณต ๋ฐฉ์ง€ ) { resultColumns.push( `COALESCE(${alias}.${config.referenceColumn}::TEXT, '') AS ${config.referenceColumn}` ); + generatedLabelAliases.add(config.referenceColumn); } } diff --git a/frontend/components/common/ScreenModal.tsx b/frontend/components/common/ScreenModal.tsx index 8531d643..68fa0cb1 100644 --- a/frontend/components/common/ScreenModal.tsx +++ b/frontend/components/common/ScreenModal.tsx @@ -335,6 +335,9 @@ export const ScreenModal: React.FC = ({ className }) => { const editId = urlParams.get("editId"); const tableName = urlParams.get("tableName") || screenInfo.tableName; const groupByColumnsParam = urlParams.get("groupByColumns"); + const primaryKeyColumn = urlParams.get("primaryKeyColumn"); // ๐Ÿ†• Primary Key ์ปฌ๋Ÿผ๋ช… + + console.log("๐Ÿ“‹ URL ํŒŒ๋ผ๋ฏธํ„ฐ ํ™•์ธ:", { mode, editId, tableName, groupByColumnsParam, primaryKeyColumn }); // ์ˆ˜์ • ๋ชจ๋“œ์ด๊ณ  editId๊ฐ€ ์žˆ์œผ๋ฉด ํ•ด๋‹น ๋ ˆ์ฝ”๋“œ ์กฐํšŒ if (mode === "edit" && editId && tableName) { @@ -357,6 +360,16 @@ export const ScreenModal: React.FC = ({ className }) => { if (groupByColumns.length > 0) { params.groupByColumns = JSON.stringify(groupByColumns); } + // ๐Ÿ†• Primary Key ์ปฌ๋Ÿผ๋ช… ์ „๋‹ฌ (๋ฐฑ์—”๋“œ ์ž๋™ ๊ฐ์ง€ ์‹คํŒจ ์‹œ ์‚ฌ์šฉ) + if (primaryKeyColumn) { + params.primaryKeyColumn = primaryKeyColumn; + console.log("โœ… [ScreenModal] primaryKeyColumn์„ params์— ์ถ”๊ฐ€:", primaryKeyColumn); + } + + console.log("๐Ÿ“ก [ScreenModal] ์‹ค์ œ API ์š”์ฒญ:", { + url: `/data/${tableName}/${editId}`, + params, + }); const apiResponse = await apiClient.get(`/data/${tableName}/${editId}`, { params }); const response = apiResponse.data; @@ -582,66 +595,66 @@ export const ScreenModal: React.FC = ({ className }) => { ) : screenData ? ( - -
- {screenData.components.map((component) => { - // ํ™”๋ฉด ๊ด€๋ฆฌ ํ•ด์ƒ๋„๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ offset ์กฐ์ • ๋ถˆํ•„์š” - const offsetX = screenDimensions?.offsetX || 0; - const offsetY = screenDimensions?.offsetY || 0; + +
+ {screenData.components.map((component) => { + // ํ™”๋ฉด ๊ด€๋ฆฌ ํ•ด์ƒ๋„๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ offset ์กฐ์ • ๋ถˆํ•„์š” + const offsetX = screenDimensions?.offsetX || 0; + const offsetY = screenDimensions?.offsetY || 0; - // offset์ด 0์ด๋ฉด ์›๋ณธ ์œ„์น˜ ์‚ฌ์šฉ (ํ™”๋ฉด ๊ด€๋ฆฌ ํ•ด์ƒ๋„ ์‚ฌ์šฉ ์‹œ) - const adjustedComponent = - offsetX === 0 && offsetY === 0 - ? component - : { - ...component, - position: { - ...component.position, - x: parseFloat(component.position?.x?.toString() || "0") - offsetX, - y: parseFloat(component.position?.y?.toString() || "0") - offsetY, - }, - }; - - return ( - { - setFormData((prev) => { - const newFormData = { - ...prev, - [fieldName]: value, + // offset์ด 0์ด๋ฉด ์›๋ณธ ์œ„์น˜ ์‚ฌ์šฉ (ํ™”๋ฉด ๊ด€๋ฆฌ ํ•ด์ƒ๋„ ์‚ฌ์šฉ ์‹œ) + const adjustedComponent = + offsetX === 0 && offsetY === 0 + ? component + : { + ...component, + position: { + ...component.position, + x: parseFloat(component.position?.x?.toString() || "0") - offsetX, + y: parseFloat(component.position?.y?.toString() || "0") - offsetY, + }, }; - return newFormData; - }); - }} - onRefresh={() => { - // ๋ถ€๋ชจ ํ™”๋ฉด์˜ ํ…Œ์ด๋ธ” ์ƒˆ๋กœ๊ณ ์นจ ์ด๋ฒคํŠธ ๋ฐœ์†ก - window.dispatchEvent(new CustomEvent("refreshTable")); - }} - screenInfo={{ - id: modalState.screenId!, - tableName: screenData.screenInfo?.tableName, - }} - groupedData={selectedData} - userId={userId} - userName={userName} - companyCode={user?.companyCode} - /> - ); - })} -
-
+ + return ( + { + setFormData((prev) => { + const newFormData = { + ...prev, + [fieldName]: value, + }; + return newFormData; + }); + }} + onRefresh={() => { + // ๋ถ€๋ชจ ํ™”๋ฉด์˜ ํ…Œ์ด๋ธ” ์ƒˆ๋กœ๊ณ ์นจ ์ด๋ฒคํŠธ ๋ฐœ์†ก + window.dispatchEvent(new CustomEvent("refreshTable")); + }} + screenInfo={{ + id: modalState.screenId!, + tableName: screenData.screenInfo?.tableName, + }} + groupedData={selectedData} + userId={userId} + userName={userName} + companyCode={user?.companyCode} + /> + ); + })} +
+
) : (
diff --git a/frontend/components/webtypes/RepeaterInput.tsx b/frontend/components/webtypes/RepeaterInput.tsx index 7cd4b279..050b386b 100644 --- a/frontend/components/webtypes/RepeaterInput.tsx +++ b/frontend/components/webtypes/RepeaterInput.tsx @@ -309,18 +309,32 @@ export const RepeaterInput: React.FC = ({ _subDataMaxValue: maxValue, }; - // ์„ ํƒ๋œ ํ•˜์œ„ ๋ฐ์ดํ„ฐ์˜ ํ•„๋“œ ๊ฐ’์„ ์ƒ์œ„ item์— ๋ณต์‚ฌ (์„ค์ •๋œ ๊ฒฝ์šฐ) - // ์˜ˆ: warehouse_code, location_code ๋“ฑ - if (subDataLookup.lookup.displayColumns) { - subDataLookup.lookup.displayColumns.forEach((col) => { - if (selectedItem[col] !== undefined) { - // ํ•„๋“œ๊ฐ€ ์ •์˜๋˜์–ด ์žˆ์œผ๋ฉด ๋ณต์‚ฌ - const fieldDef = fields.find((f) => f.name === col); - if (fieldDef || col.includes("_code") || col.includes("_id")) { - newItems[itemIndex][col] = selectedItem[col]; + // fieldMappings๊ฐ€ ์„ค์ •๋˜์–ด ์žˆ์œผ๋ฉด ๋งคํ•‘์— ๋”ฐ๋ผ ๊ฐ’ ๋ณต์‚ฌ + if (subDataLookup.lookup.fieldMappings && subDataLookup.lookup.fieldMappings.length > 0) { + subDataLookup.lookup.fieldMappings.forEach((mapping) => { + if (mapping.targetField && mapping.targetField !== "") { + // ๋งคํ•‘๋œ ํƒ€๊ฒŸ ํ•„๋“œ์— ์†Œ์Šค ์ปฌ๋Ÿผ ๊ฐ’ ๋ณต์‚ฌ + const sourceValue = selectedItem[mapping.sourceColumn]; + if (sourceValue !== undefined) { + newItems[itemIndex][mapping.targetField] = sourceValue; } } }); + } else { + // fieldMappings๊ฐ€ ์—†์œผ๋ฉด ๊ธฐ์กด ๋กœ์ง (ํ•˜์œ„ ํ˜ธํ™˜์„ฑ) + // ์„ ํƒ๋œ ํ•˜์œ„ ๋ฐ์ดํ„ฐ์˜ ํ•„๋“œ ๊ฐ’์„ ์ƒ์œ„ item์— ๋ณต์‚ฌ (์„ค์ •๋œ ๊ฒฝ์šฐ) + // ์˜ˆ: warehouse_code, location_code ๋“ฑ + if (subDataLookup.lookup.displayColumns) { + subDataLookup.lookup.displayColumns.forEach((col) => { + if (selectedItem[col] !== undefined) { + // ํ•„๋“œ๊ฐ€ ์ •์˜๋˜์–ด ์žˆ์œผ๋ฉด ๋ณต์‚ฌ + const fieldDef = fields.find((f) => f.name === col); + if (fieldDef || col.includes("_code") || col.includes("_id")) { + newItems[itemIndex][col] = selectedItem[col]; + } + } + }); + } } setItems(newItems); @@ -893,6 +907,10 @@ export const RepeaterInput: React.FC = ({ const renderGridLayout = () => { // ํ•˜์œ„ ๋ฐ์ดํ„ฐ ์กฐํšŒ ์„ค์ •์ด ์žˆ์œผ๋ฉด ์—ฐ๊ฒฐ ์ปฌ๋Ÿผ ์ฐพ๊ธฐ const linkColumn = subDataLookup?.lookup?.linkColumn; + + // hidden์ด ์•„๋‹Œ ํ•„๋“œ๋งŒ ํ‘œ์‹œ + // isHidden์ด true์ด๊ฑฐ๋‚˜ displayMode๊ฐ€ hidden์ธ ํ•„๋“œ๋Š” ์ œ์™ธ (ํ•˜์œ„ ํ˜ธํ™˜์„ฑ ์œ ์ง€) + const visibleFields = fields.filter((f) => !f.isHidden && f.displayMode !== "hidden"); return (
@@ -905,7 +923,7 @@ export const RepeaterInput: React.FC = ({ {allowReorder && ( )} - {fields.map((field) => ( + {visibleFields.map((field) => ( {field.label} {field.required && *} @@ -944,8 +962,8 @@ export const RepeaterInput: React.FC = ({ )} - {/* ํ•„๋“œ๋“ค */} - {fields.map((field) => ( + {/* ํ•„๋“œ๋“ค (hidden ์ œ์™ธ) */} + {visibleFields.map((field) => ( {renderField(field, itemIndex, item[field.name])} @@ -973,7 +991,7 @@ export const RepeaterInput: React.FC = ({ @@ -1002,6 +1020,10 @@ export const RepeaterInput: React.FC = ({ const renderCardLayout = () => { // ํ•˜์œ„ ๋ฐ์ดํ„ฐ ์กฐํšŒ ์„ค์ •์ด ์žˆ์œผ๋ฉด ์—ฐ๊ฒฐ ์ปฌ๋Ÿผ ์ฐพ๊ธฐ const linkColumn = subDataLookup?.lookup?.linkColumn; + + // hidden์ด ์•„๋‹Œ ํ•„๋“œ๋งŒ ํ‘œ์‹œ + // isHidden์ด true์ด๊ฑฐ๋‚˜ displayMode๊ฐ€ hidden์ธ ํ•„๋“œ๋Š” ์ œ์™ธ (ํ•˜์œ„ ํ˜ธํ™˜์„ฑ ์œ ์ง€) + const visibleFields = fields.filter((f) => !f.isHidden && f.displayMode !== "hidden"); return ( <> @@ -1070,7 +1092,7 @@ export const RepeaterInput: React.FC = ({ {!isCollapsed && (
- {fields.map((field) => ( + {visibleFields.map((field) => (
- ) : ( - // ์„ ํƒ ์—†์Œ -
-
-

์ขŒ์ธก์—์„œ ํ•ญ๋ชฉ์„ ์„ ํƒํ•˜์„ธ์š”

-

์„ ํƒํ•œ ํ•ญ๋ชฉ์˜ ์ƒ์„ธ ์ •๋ณด๊ฐ€ ์—ฌ๊ธฐ์— ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค

-
-
+ ) : ( + // ์„ ํƒ ์—†์Œ +
+
+

์ขŒ์ธก์—์„œ ํ•ญ๋ชฉ์„ ์„ ํƒํ•˜์„ธ์š”

+

์„ ํƒํ•œ ํ•ญ๋ชฉ์˜ ์ƒ์„ธ ์ •๋ณด๊ฐ€ ์—ฌ๊ธฐ์— ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค

+
+
+ )} + )}
diff --git a/frontend/lib/registry/components/table-list/TableListComponent.tsx b/frontend/lib/registry/components/table-list/TableListComponent.tsx index 72fd5330..3d6521c3 100644 --- a/frontend/lib/registry/components/table-list/TableListComponent.tsx +++ b/frontend/lib/registry/components/table-list/TableListComponent.tsx @@ -2721,19 +2721,41 @@ export const TableListComponent: React.FC = ({ const value = row[mappedColumnName]; // ์นดํ…Œ๊ณ ๋ฆฌ ๋งคํ•‘๋œ ๊ฐ’ ์ฒ˜๋ฆฌ - if (categoryMappings[col.columnName] && value !== null && value !== undefined) { - const mapping = categoryMappings[col.columnName][String(value)]; - if (mapping) { - return mapping.label; + if (value !== null && value !== undefined) { + const valueStr = String(value); + + // ๋””๋ฒ„๊ทธ ๋กœ๊ทธ (์นดํ…Œ๊ณ ๋ฆฌ ๊ฐ’์ธ ๊ฒฝ์šฐ๋งŒ) + if (valueStr.startsWith("CATEGORY_")) { + console.log("๐Ÿ” [์—‘์…€๋‹ค์šด๋กœ๋“œ] ์นดํ…Œ๊ณ ๋ฆฌ ๋ณ€ํ™˜ ์‹œ๋„:", { + columnName: col.columnName, + value: valueStr, + hasMappings: !!categoryMappings[col.columnName], + mappingsKeys: categoryMappings[col.columnName] ? Object.keys(categoryMappings[col.columnName]).slice(0, 5) : [], + }); } + + if (categoryMappings[col.columnName]) { + // ์‰ผํ‘œ๋กœ ๊ตฌ๋ถ„๋œ ์ค‘๋ณต ๊ฐ’ ์ฒ˜๋ฆฌ + if (valueStr.includes(",")) { + const values = valueStr.split(",").map((v) => v.trim()).filter((v) => v); + const labels = values.map((v) => { + const mapping = categoryMappings[col.columnName][v]; + return mapping ? mapping.label : v; + }); + return labels.join(", "); + } + // ๋‹จ์ผ ๊ฐ’ ์ฒ˜๋ฆฌ + const mapping = categoryMappings[col.columnName][valueStr]; + if (mapping) { + return mapping.label; + } + } + + return value; } // null/undefined ์ฒ˜๋ฆฌ - if (value === null || value === undefined) { - return ""; - } - - return value; + return ""; }); }); diff --git a/frontend/lib/utils/buttonActions.ts b/frontend/lib/utils/buttonActions.ts index 65aef991..8420e9c3 100644 --- a/frontend/lib/utils/buttonActions.ts +++ b/frontend/lib/utils/buttonActions.ts @@ -712,12 +712,19 @@ export class ButtonActionExecutor { if (repeaterJsonKeys.length > 0) { console.log("๐Ÿ”„ [handleSave] RepeaterFieldGroup JSON ๋ฌธ์ž์—ด ๊ฐ์ง€:", repeaterJsonKeys); - + // ๐ŸŽฏ ์ฑ„๋ฒˆ ๊ทœ์น™ ํ• ๋‹น ์ฒ˜๋ฆฌ (RepeaterFieldGroup ์ €์žฅ ์ „์— ์‹คํ–‰) - console.log("๐Ÿ” [handleSave-RepeaterFieldGroup] ์ฑ„๋ฒˆ ๊ทœ์น™ ํ• ๋‹น ์ฒดํฌ ์‹œ์ž‘"); - + // ๐Ÿ”ง ์ˆ˜์ • ๋ชจ๋“œ ์ฒดํฌ: formData.id๊ฐ€ ์กด์žฌํ•˜๋ฉด UPDATE ๋ชจ๋“œ์ด๋ฏ€๋กœ ์ฑ„๋ฒˆ ์ฝ”๋“œ ์žฌํ• ๋‹น ๊ธˆ์ง€ + const isEditModeRepeater = + context.formData.id !== undefined && context.formData.id !== null && context.formData.id !== ""; + + console.log("๐Ÿ” [handleSave-RepeaterFieldGroup] ์ฑ„๋ฒˆ ๊ทœ์น™ ํ• ๋‹น ์ฒดํฌ ์‹œ์ž‘", { + isEditMode: isEditModeRepeater, + formDataId: context.formData.id, + }); + const fieldsWithNumberingRepeater: Record = {}; - + // formData์—์„œ ์ฑ„๋ฒˆ ๊ทœ์น™์ด ์„ค์ •๋œ ํ•„๋“œ ์ฐพ๊ธฐ for (const [key, value] of Object.entries(context.formData)) { if (key.endsWith("_numberingRuleId") && value) { @@ -726,22 +733,27 @@ export class ButtonActionExecutor { console.log(`๐ŸŽฏ [handleSave-RepeaterFieldGroup] ์ฑ„๋ฒˆ ํ•„๋“œ ๋ฐœ๊ฒฌ: ${fieldName} โ†’ ๊ทœ์น™ ${value}`); } } - + console.log("๐Ÿ“‹ [handleSave-RepeaterFieldGroup] ์ฑ„๋ฒˆ ๊ทœ์น™์ด ์„ค์ •๋œ ํ•„๋“œ:", fieldsWithNumberingRepeater); - - // ์ฑ„๋ฒˆ ๊ทœ์น™์ด ์žˆ๋Š” ํ•„๋“œ์— ๋Œ€ํ•ด allocateCode ํ˜ธ์ถœ - if (Object.keys(fieldsWithNumberingRepeater).length > 0) { - console.log("๐ŸŽฏ [handleSave-RepeaterFieldGroup] ์ฑ„๋ฒˆ ๊ทœ์น™ ํ• ๋‹น ์‹œ์ž‘ (allocateCode ํ˜ธ์ถœ)"); + + // ๐Ÿ”ง ์ˆ˜์ • ๋ชจ๋“œ์—์„œ๋Š” ์ฑ„๋ฒˆ ์ฝ”๋“œ ํ• ๋‹น ๊ฑด๋„ˆ๋›ฐ๊ธฐ (๊ธฐ์กด ๋ฒˆํ˜ธ ์œ ์ง€) + // ์‹ ๊ทœ ๋“ฑ๋ก ๋ชจ๋“œ์—์„œ๋งŒ allocateCode ํ˜ธ์ถœํ•˜์—ฌ ์ƒˆ ๋ฒˆํ˜ธ ํ• ๋‹น + if (Object.keys(fieldsWithNumberingRepeater).length > 0 && !isEditModeRepeater) { + console.log( + "๐ŸŽฏ [handleSave-RepeaterFieldGroup] ์‹ ๊ทœ ๋“ฑ๋ก ๋ชจ๋“œ - ์ฑ„๋ฒˆ ๊ทœ์น™ ํ• ๋‹น ์‹œ์ž‘ (allocateCode ํ˜ธ์ถœ)", + ); const { allocateNumberingCode } = await import("@/lib/api/numberingRule"); - + for (const [fieldName, ruleId] of Object.entries(fieldsWithNumberingRepeater)) { try { console.log(`๐Ÿ”„ [handleSave-RepeaterFieldGroup] ${fieldName} ํ•„๋“œ์— ๋Œ€ํ•ด allocateCode ํ˜ธ์ถœ: ${ruleId}`); const allocateResult = await allocateNumberingCode(ruleId); - + if (allocateResult.success && allocateResult.data?.generatedCode) { const newCode = allocateResult.data.generatedCode; - console.log(`โœ… [handleSave-RepeaterFieldGroup] ${fieldName} ์ƒˆ ์ฝ”๋“œ ํ• ๋‹น: ${context.formData[fieldName]} โ†’ ${newCode}`); + console.log( + `โœ… [handleSave-RepeaterFieldGroup] ${fieldName} ์ƒˆ ์ฝ”๋“œ ํ• ๋‹น: ${context.formData[fieldName]} โ†’ ${newCode}`, + ); context.formData[fieldName] = newCode; } else { console.warn(`โš ๏ธ [handleSave-RepeaterFieldGroup] ${fieldName} ์ฝ”๋“œ ํ• ๋‹น ์‹คํŒจ:`, allocateResult.error); @@ -750,9 +762,11 @@ export class ButtonActionExecutor { console.error(`โŒ [handleSave-RepeaterFieldGroup] ${fieldName} ์ฝ”๋“œ ํ• ๋‹น ์˜ค๋ฅ˜:`, allocateError); } } + } else if (isEditModeRepeater) { + console.log("โญ๏ธ [handleSave-RepeaterFieldGroup] ์ˆ˜์ • ๋ชจ๋“œ - ์ฑ„๋ฒˆ ์ฝ”๋“œ ํ• ๋‹น ๊ฑด๋„ˆ๋œ€ (๊ธฐ์กด ๋ฒˆํ˜ธ ์œ ์ง€)"); } - - console.log("โœ… [handleSave-RepeaterFieldGroup] ์ฑ„๋ฒˆ ๊ทœ์น™ ํ• ๋‹น ์™„๋ฃŒ"); + + console.log("โœ… [handleSave-RepeaterFieldGroup] ์ฑ„๋ฒˆ ๊ทœ์น™ ํ• ๋‹น ์ฒ˜๋ฆฌ ์™„๋ฃŒ"); // ๐Ÿ†• ์ƒ๋‹จ ํผ ๋ฐ์ดํ„ฐ(๋งˆ์Šคํ„ฐ ์ •๋ณด) ์ถ”์ถœ // RepeaterFieldGroup JSON๊ณผ ์ปดํฌ๋„ŒํŠธ ํ‚ค๋ฅผ ์ œ์™ธํ•œ ๋‚˜๋จธ์ง€๊ฐ€ ๋งˆ์Šคํ„ฐ ์ •๋ณด @@ -808,7 +822,7 @@ export class ButtonActionExecutor { for (const item of parsedData) { // ๋ฉ”ํƒ€ ํ•„๋“œ ์ œ๊ฑฐ - const { _targetTable, _isNewItem, _existingRecord, _originalItemIds, _deletedItemIds, _repeaterFields, ...itemData } = item; + const { _targetTable, _isNewItem, _existingRecord, _originalItemIds, _deletedItemIds, _repeaterFields, _subDataSelection, _subDataMaxValue, ...itemData } = item; // ๐Ÿ”ง ํ’ˆ๋ชฉ ๊ณ ์œ  ํ•„๋“œ๋งŒ ์ถ”์ถœ (RepeaterFieldGroup ์„ค์ • ๊ธฐ๋ฐ˜) const itemOnlyData: Record = {}; @@ -817,6 +831,42 @@ export class ButtonActionExecutor { itemOnlyData[field] = itemData[field]; } }); + + // ๐Ÿ†• ํ•˜์œ„ ๋ฐ์ดํ„ฐ ์„ ํƒ์—์„œ ๊ฐ’ ์ถ”์ถœ (subDataSource ์„ค์ • ๊ธฐ๋ฐ˜) + // ํ•„๋“œ ์ •์˜์—์„œ subDataSource.enabled๊ฐ€ true์ด๊ณ  sourceColumn์ด ์„ค์ •๋œ ํ•„๋“œ๋งŒ ์ฒ˜๋ฆฌ + if (_subDataSelection && typeof _subDataSelection === 'object') { + // _repeaterFieldsConfig์—์„œ subDataSource ์„ค์ • ํ™•์ธ + const fieldsConfig = item._repeaterFieldsConfig as Array<{ + name: string; + subDataSource?: { enabled: boolean; sourceColumn: string }; + }> | undefined; + + if (fieldsConfig && Array.isArray(fieldsConfig)) { + fieldsConfig.forEach((fieldConfig) => { + if (fieldConfig.subDataSource?.enabled && fieldConfig.subDataSource?.sourceColumn) { + const targetField = fieldConfig.name; // ํ•„๋“œ๋ช… = ์ €์žฅํ•  ์ปฌ๋Ÿผ๋ช… + const sourceColumn = fieldConfig.subDataSource.sourceColumn; + const sourceValue = _subDataSelection[sourceColumn]; + + if (sourceValue !== undefined && sourceValue !== null) { + itemOnlyData[targetField] = sourceValue; + console.log(`๐Ÿ“‹ [handleSave] ํ•˜์œ„ ๋ฐ์ดํ„ฐ ๊ฐ’ ๋งคํ•‘: ${sourceColumn} โ†’ ${targetField} = ${sourceValue}`); + } + } + }); + } else { + // ํ•˜์œ„ ํ˜ธํ™˜์„ฑ: fieldsConfig๊ฐ€ ์—†์œผ๋ฉด ๊ธฐ์กด ๋ฐฉ์‹ ์‚ฌ์šฉ + Object.keys(_subDataSelection).forEach((subDataKey) => { + if (itemOnlyData[subDataKey] === undefined || itemOnlyData[subDataKey] === null || itemOnlyData[subDataKey] === '') { + const subDataValue = _subDataSelection[subDataKey]; + if (subDataValue !== undefined && subDataValue !== null) { + itemOnlyData[subDataKey] = subDataValue; + console.log(`๐Ÿ“‹ [handleSave] ํ•˜์œ„ ๋ฐ์ดํ„ฐ ์„ ํƒ ๊ฐ’ ์ถ”๊ฐ€ (๋ ˆ๊ฑฐ์‹œ): ${subDataKey} = ${subDataValue}`); + } + } + }); + } + } // ๐Ÿ”ง ๋งˆ์Šคํ„ฐ ์ •๋ณด + ํ’ˆ๋ชฉ ๊ณ ์œ  ์ •๋ณด ๋ณ‘ํ•ฉ // masterFields: ์ƒ๋‹จ ํผ์—์„œ ์ˆ˜์ •ํ•œ ์ตœ์‹  ๋งˆ์Šคํ„ฐ ์ •๋ณด @@ -1967,7 +2017,16 @@ export class ButtonActionExecutor { } // ๐ŸŽฏ ์ฑ„๋ฒˆ ๊ทœ์น™ ํ• ๋‹น ์ฒ˜๋ฆฌ (์ €์žฅ ์‹œ์ ์— ์‹ค์ œ ์ˆœ๋ฒˆ ์ฆ๊ฐ€) - console.log("๐Ÿ” [handleUniversalFormModalTableSectionSave] ์ฑ„๋ฒˆ ๊ทœ์น™ ํ• ๋‹น ์ฒดํฌ ์‹œ์ž‘"); + // ๐Ÿ”ง ์ˆ˜์ • ๋ชจ๋“œ ์ฒดํฌ: formData.id ๋˜๋Š” originalGroupedData๊ฐ€ ์žˆ์œผ๋ฉด UPDATE ๋ชจ๋“œ + const isEditModeUniversal = + (formData.id !== undefined && formData.id !== null && formData.id !== "") || + originalGroupedData.length > 0; + + console.log("๐Ÿ” [handleUniversalFormModalTableSectionSave] ์ฑ„๋ฒˆ ๊ทœ์น™ ํ• ๋‹น ์ฒดํฌ ์‹œ์ž‘", { + isEditMode: isEditModeUniversal, + formDataId: formData.id, + originalGroupedDataCount: originalGroupedData.length, + }); const fieldsWithNumbering: Record = {}; @@ -1993,9 +2052,12 @@ export class ButtonActionExecutor { console.log("๐Ÿ“‹ [handleUniversalFormModalTableSectionSave] ์ฑ„๋ฒˆ ๊ทœ์น™์ด ์„ค์ •๋œ ํ•„๋“œ:", fieldsWithNumbering); - // ๐Ÿ”ฅ ์ €์žฅ ์‹œ์ ์— allocateCode ํ˜ธ์ถœํ•˜์—ฌ ์‹ค์ œ ์ˆœ๋ฒˆ ์ฆ๊ฐ€ - if (Object.keys(fieldsWithNumbering).length > 0) { - console.log("๐ŸŽฏ [handleUniversalFormModalTableSectionSave] ์ฑ„๋ฒˆ ๊ทœ์น™ ํ• ๋‹น ์‹œ์ž‘ (allocateCode ํ˜ธ์ถœ)"); + // ๐Ÿ”ง ์ˆ˜์ • ๋ชจ๋“œ์—์„œ๋Š” ์ฑ„๋ฒˆ ์ฝ”๋“œ ํ• ๋‹น ๊ฑด๋„ˆ๋›ฐ๊ธฐ (๊ธฐ์กด ๋ฒˆํ˜ธ ์œ ์ง€) + // ์‹ ๊ทœ ๋“ฑ๋ก ๋ชจ๋“œ์—์„œ๋งŒ allocateCode ํ˜ธ์ถœํ•˜์—ฌ ์ƒˆ ๋ฒˆํ˜ธ ํ• ๋‹น + if (Object.keys(fieldsWithNumbering).length > 0 && !isEditModeUniversal) { + console.log( + "๐ŸŽฏ [handleUniversalFormModalTableSectionSave] ์‹ ๊ทœ ๋“ฑ๋ก ๋ชจ๋“œ - ์ฑ„๋ฒˆ ๊ทœ์น™ ํ• ๋‹น ์‹œ์ž‘ (allocateCode ํ˜ธ์ถœ)", + ); const { allocateNumberingCode } = await import("@/lib/api/numberingRule"); for (const [fieldName, ruleId] of Object.entries(fieldsWithNumbering)) { @@ -2022,6 +2084,8 @@ export class ButtonActionExecutor { // ์˜ค๋ฅ˜ ์‹œ ๊ธฐ์กด ๊ฐ’ ์œ ์ง€ } } + } else if (isEditModeUniversal) { + console.log("โญ๏ธ [handleUniversalFormModalTableSectionSave] ์ˆ˜์ • ๋ชจ๋“œ - ์ฑ„๋ฒˆ ์ฝ”๋“œ ํ• ๋‹น ๊ฑด๋„ˆ๋œ€ (๊ธฐ์กด ๋ฒˆํ˜ธ ์œ ์ง€)"); } console.log("โœ… [handleUniversalFormModalTableSectionSave] ์ฑ„๋ฒˆ ๊ทœ์น™ ํ• ๋‹น ์™„๋ฃŒ"); @@ -4949,7 +5013,24 @@ export class ButtonActionExecutor { const filteredRow: Record = {}; visibleColumns!.forEach((columnName: string) => { const label = columnLabels?.[columnName] || columnName; - filteredRow[label] = row[columnName]; + let value = row[columnName]; + + // ์นดํ…Œ๊ณ ๋ฆฌ ์ฝ”๋“œ๋ฅผ ๋ผ๋ฒจ๋กœ ๋ณ€ํ™˜ (CATEGORY_๋กœ ์‹œ์ž‘ํ•˜๋Š” ๊ฐ’) + if (value && typeof value === "string" && value.includes("CATEGORY_")) { + // ๋จผ์ € _label ํ•„๋“œ ํ™•์ธ (API์—์„œ ์ œ๊ณตํ•˜๋Š” ๊ฒฝ์šฐ) + const labelFieldName = `${columnName}_label`; + if (row[labelFieldName]) { + value = row[labelFieldName]; + } else { + // _value_label ํ•„๋“œ ํ™•์ธ + const valueLabelFieldName = `${columnName}_value_label`; + if (row[valueLabelFieldName]) { + value = row[valueLabelFieldName]; + } + } + } + + filteredRow[label] = value; }); return filteredRow; }); @@ -5222,8 +5303,15 @@ export class ButtonActionExecutor { value = row[`${columnName}_name`]; } // ์นดํ…Œ๊ณ ๋ฆฌ ํƒ€์ž… ํ•„๋“œ๋Š” ๋ผ๋ฒจ๋กœ ๋ณ€ํ™˜ (๋ฐฑ์—”๋“œ์—์„œ ์ •์˜๋œ ์ปฌ๋Ÿผ๋งŒ) - else if (categoryMap[columnName] && typeof value === "string" && categoryMap[columnName][value]) { - value = categoryMap[columnName][value]; + else if (categoryMap[columnName] && typeof value === "string") { + // ์‰ผํ‘œ๋กœ ๊ตฌ๋ถ„๋œ ๋‹ค์ค‘ ๊ฐ’ ์ฒ˜๋ฆฌ + if (value.includes(",")) { + const values = value.split(",").map((v) => v.trim()).filter((v) => v); + const labels = values.map((v) => categoryMap[columnName][v] || v); + value = labels.join(", "); + } else if (categoryMap[columnName][value]) { + value = categoryMap[columnName][value]; + } } filteredRow[label] = value; diff --git a/frontend/lib/utils/excelExport.ts b/frontend/lib/utils/excelExport.ts index 52c22f5a..6bd97624 100644 --- a/frontend/lib/utils/excelExport.ts +++ b/frontend/lib/utils/excelExport.ts @@ -116,8 +116,10 @@ export async function importFromExcel( return; } - // JSON์œผ๋กœ ๋ณ€ํ™˜ - const jsonData = XLSX.utils.sheet_to_json(worksheet); + // JSON์œผ๋กœ ๋ณ€ํ™˜ (๋นˆ ์…€๋„ ํฌํ•จํ•˜์—ฌ ๋ชจ๋“  ์ปฌ๋Ÿผ ํ‚ค ์œ ์ง€) + const jsonData = XLSX.utils.sheet_to_json(worksheet, { + defval: "", // ๋นˆ ์…€์— ๋นˆ ๋ฌธ์ž์—ด ํ• ๋‹น + }); console.log("โœ… ์—‘์…€ ๊ฐ€์ ธ์˜ค๊ธฐ ์™„๋ฃŒ:", { sheetName: targetSheetName, diff --git a/frontend/types/repeater.ts b/frontend/types/repeater.ts index 2362210b..c7f0fa98 100644 --- a/frontend/types/repeater.ts +++ b/frontend/types/repeater.ts @@ -43,9 +43,19 @@ export interface CalculationFormula { * ํ•„๋“œ ํ‘œ์‹œ ๋ชจ๋“œ * - input: ์ž…๋ ฅ ํ•„๋“œ๋กœ ํ‘œ์‹œ (ํŽธ์ง‘ ๊ฐ€๋Šฅ) * - readonly: ์ฝ๊ธฐ ์ „์šฉ ํ…์ŠคํŠธ๋กœ ํ‘œ์‹œ + * - hidden: ์ˆจ๊น€ (UI์— ํ‘œ์‹œ๋˜์ง€ ์•Š์ง€๋งŒ ๋ฐ์ดํ„ฐ์— ํฌํ•จ๋จ) * - (์นดํ…Œ๊ณ ๋ฆฌ ํƒ€์ž…์€ ์ž๋™์œผ๋กœ ๋ฐฐ์ง€๋กœ ํ‘œ์‹œ๋จ) */ -export type RepeaterFieldDisplayMode = "input" | "readonly"; +export type RepeaterFieldDisplayMode = "input" | "readonly" | "hidden"; + +/** + * ํ•˜์œ„ ๋ฐ์ดํ„ฐ ์กฐํšŒ ์†Œ์Šค ์„ค์ • + * ํ•„๋“œ ๊ฐ’์„ ํ•˜์œ„ ๋ฐ์ดํ„ฐ ์กฐํšŒ ๊ฒฐ๊ณผ์—์„œ ๊ฐ€์ ธ์˜ฌ ๋•Œ ์‚ฌ์šฉ + */ +export interface SubDataSourceConfig { + enabled: boolean; // ํ™œ์„ฑํ™” ์—ฌ๋ถ€ + sourceColumn: string; // ํ•˜์œ„ ๋ฐ์ดํ„ฐ ์กฐํšŒ ํ…Œ์ด๋ธ”์˜ ์†Œ์Šค ์ปฌ๋Ÿผ (์˜ˆ: lot_number) +} /** * ๋ฐ˜๋ณต ๊ทธ๋ฃน ๋‚ด ๊ฐœ๋ณ„ ํ•„๋“œ ์ •์˜ @@ -60,6 +70,8 @@ export interface RepeaterFieldDefinition { options?: Array<{ label: string; value: string }>; // select์šฉ width?: string; // ํ•„๋“œ ๋„ˆ๋น„ (์˜ˆ: "200px", "50%") displayMode?: RepeaterFieldDisplayMode; // ํ‘œ์‹œ ๋ชจ๋“œ: input(์ž…๋ ฅ), readonly(์ฝ๊ธฐ์ „์šฉ) + isHidden?: boolean; // ์ˆจ๊น€ ์—ฌ๋ถ€ (true๋ฉด ํ…Œ์ด๋ธ”์— ํ‘œ์‹œ ์•ˆ ํ•จ, ๋ฐ์ดํ„ฐ๋Š” ์ €์žฅ) + subDataSource?: SubDataSourceConfig; // ํ•˜์œ„ ๋ฐ์ดํ„ฐ ์กฐํšŒ์—์„œ ๊ฐ’ ๊ฐ€์ ธ์˜ค๊ธฐ ์„ค์ • categoryCode?: string; // category ํƒ€์ž…์ผ ๋•Œ ์‚ฌ์šฉํ•  ์นดํ…Œ๊ณ ๋ฆฌ ์ฝ”๋“œ formula?: CalculationFormula; // ๊ณ„์‚ฐ์‹ (type์ด "calculated"์ผ ๋•Œ ์‚ฌ์šฉ) numberFormat?: { @@ -113,6 +125,14 @@ export type RepeaterData = RepeaterItemData[]; // ํ’ˆ๋ชฉ ์„ ํƒ ์‹œ ์žฌ๊ณ /๋‹จ๊ฐ€ ๋“ฑ ๊ด€๋ จ ๋ฐ์ดํ„ฐ๋ฅผ ์กฐํšŒํ•˜๊ณ  ์„ ํƒํ•˜๋Š” ๊ธฐ๋Šฅ // ============================================================ +/** + * ์„ ํƒ ๋ฐ์ดํ„ฐ ํ•„๋“œ ๋งคํ•‘ ์„ค์ • + */ +export interface SubDataFieldMapping { + sourceColumn: string; // ์กฐํšŒ ํ…Œ์ด๋ธ” ์ปฌ๋Ÿผ (์˜ˆ: lot_number) + targetField: string; // ์ €์žฅ ํ…Œ์ด๋ธ” ์ปฌ๋Ÿผ (์˜ˆ: lot_number) ๋˜๋Š” "" (์„ ํƒ์•ˆํ•จ) +} + /** * ํ•˜์œ„ ๋ฐ์ดํ„ฐ ์กฐํšŒ ํ…Œ์ด๋ธ” ์„ค์ • */ @@ -121,6 +141,8 @@ export interface SubDataLookupSettings { linkColumn: string; // ์ƒ์œ„ ๋ฐ์ดํ„ฐ์™€ ์—ฐ๊ฒฐํ•  ์ปฌ๋Ÿผ (์˜ˆ: item_code) displayColumns: string[]; // ํ‘œ์‹œํ•  ์ปฌ๋Ÿผ๋“ค (์˜ˆ: ["warehouse_code", "location_code", "quantity"]) columnLabels?: Record; // ์ปฌ๋Ÿผ ๋ผ๋ฒจ (์˜ˆ: { warehouse_code: "์ฐฝ๊ณ " }) + columnOrder?: string[]; // ์ปฌ๋Ÿผ ํ‘œ์‹œ ์ˆœ์„œ (์—†์œผ๋ฉด displayColumns ์ˆœ์„œ ์‚ฌ์šฉ) + fieldMappings?: SubDataFieldMapping[]; // ์„ ํƒ ๋ฐ์ดํ„ฐ ์ €์žฅ ๋งคํ•‘ (์กฐํšŒ ์ปฌ๋Ÿผ โ†’ ์ €์žฅ ์ปฌ๋Ÿผ) additionalFilters?: Record; // ์ถ”๊ฐ€ ํ•„ํ„ฐ ์กฐ๊ฑด }