feat: 채번 규칙 테이블 기반 자동 필터링 구현

- 채번 규칙 scope_type을 table로 단순화
- 화면의 테이블명을 자동으로 감지하여 채번 규칙 필터링
- TextInputConfigPanel에 screenTableName prop 추가
- getAvailableNumberingRulesForScreen API로 테이블 기반 조회
- NumberingRuleDesigner에서 자동으로 테이블명 설정
- webTypeConfigConverter 유틸리티 추가 (기존 화면 호환성)
- AutoGenerationConfig 타입 개선 (enabled, options.numberingRuleId)
- 채번 규칙 선택 UI에서 ID 제거, 설명 추가
- 불필요한 console.log 제거

Backend:
- numberingRuleService: 테이블 기반 필터링 로직 구현
- numberingRuleController: available-for-screen 엔드포인트 수정

Frontend:
- TextInputConfigPanel: 테이블명 기반 채번 규칙 로드
- NumberingRuleDesigner: 적용 범위 UI 제거, 테이블명 자동 설정
- ScreenDesigner: webTypeConfig → autoGeneration 변환 로직 통합
- DetailSettingsPanel: autoGeneration 속성 매핑 개선
This commit is contained in:
kjs
2025-11-07 14:27:07 +09:00
parent 5b79bfb19d
commit 4294fbf608
23 changed files with 2941 additions and 135 deletions

View File

@@ -25,6 +25,7 @@ interface NumberingRuleDesignerProps {
maxRules?: number;
isPreview?: boolean;
className?: string;
currentTableName?: string; // 현재 화면의 테이블명 (자동 감지용)
}
export const NumberingRuleDesigner: React.FC<NumberingRuleDesignerProps> = ({
@@ -34,6 +35,7 @@ export const NumberingRuleDesigner: React.FC<NumberingRuleDesignerProps> = ({
maxRules = 6,
isPreview = false,
className = "",
currentTableName,
}) => {
const [savedRules, setSavedRules] = useState<NumberingRuleConfig[]>([]);
const [selectedRuleId, setSelectedRuleId] = useState<string | null>(null);
@@ -131,17 +133,32 @@ export const NumberingRuleDesigner: React.FC<NumberingRuleDesignerProps> = ({
try {
const existing = savedRules.find((r) => r.ruleId === currentRule.ruleId);
// 저장 전에 현재 화면의 테이블명 자동 설정
const ruleToSave = {
...currentRule,
scopeType: "table" as const, // 항상 table로 고정
tableName: currentTableName || currentRule.tableName || "", // 현재 테이블명 자동 설정
};
console.log("💾 채번 규칙 저장:", {
currentTableName,
"currentRule.tableName": currentRule.tableName,
"ruleToSave.tableName": ruleToSave.tableName,
"ruleToSave.scopeType": ruleToSave.scopeType,
ruleToSave
});
let response;
if (existing) {
response = await updateNumberingRule(currentRule.ruleId, currentRule);
response = await updateNumberingRule(ruleToSave.ruleId, ruleToSave);
} else {
response = await createNumberingRule(currentRule);
response = await createNumberingRule(ruleToSave);
}
if (response.success && response.data) {
setSavedRules((prev) => {
if (existing) {
return prev.map((r) => (r.ruleId === currentRule.ruleId ? response.data! : r));
return prev.map((r) => (r.ruleId === ruleToSave.ruleId ? response.data! : r));
} else {
return [...prev, response.data!];
}
@@ -160,7 +177,7 @@ export const NumberingRuleDesigner: React.FC<NumberingRuleDesignerProps> = ({
} finally {
setLoading(false);
}
}, [currentRule, savedRules, onSave]);
}, [currentRule, savedRules, onSave, currentTableName]);
const handleSelectRule = useCallback((rule: NumberingRuleConfig) => {
setSelectedRuleId(rule.ruleId);
@@ -196,6 +213,8 @@ export const NumberingRuleDesigner: React.FC<NumberingRuleDesignerProps> = ({
);
const handleNewRule = useCallback(() => {
console.log("📋 새 규칙 생성 - currentTableName:", currentTableName);
const newRule: NumberingRuleConfig = {
ruleId: `rule-${Date.now()}`,
ruleName: "새 채번 규칙",
@@ -203,14 +222,17 @@ export const NumberingRuleDesigner: React.FC<NumberingRuleDesignerProps> = ({
separator: "-",
resetPeriod: "none",
currentSequence: 1,
scopeType: "menu",
scopeType: "table", // 기본값을 table로 설정
tableName: currentTableName || "", // 현재 화면의 테이블명 자동 설정
};
console.log("📋 생성된 규칙 정보:", newRule);
setSelectedRuleId(newRule.ruleId);
setCurrentRule(newRule);
toast.success("새 규칙이 생성되었습니다");
}, []);
}, [currentTableName]);
return (
<div className={`flex h-full gap-4 ${className}`}>
@@ -312,20 +334,36 @@ export const NumberingRuleDesigner: React.FC<NumberingRuleDesignerProps> = ({
</Button>
</div>
<div className="flex items-center gap-3">
<div className="flex-1 space-y-2">
<Label className="text-sm font-medium"></Label>
<Input
value={currentRule.ruleName}
onChange={(e) => setCurrentRule((prev) => ({ ...prev!, ruleName: e.target.value }))}
className="h-9"
placeholder="예: 프로젝트 코드"
/>
</div>
<div className="flex-1 space-y-2">
<Label className="text-sm font-medium"></Label>
<NumberingRulePreview config={currentRule} />
<div className="space-y-3">
{/* 첫 번째 줄: 규칙명 + 미리보기 */}
<div className="flex items-center gap-3">
<div className="flex-1 space-y-2">
<Label className="text-sm font-medium"></Label>
<Input
value={currentRule.ruleName}
onChange={(e) => setCurrentRule((prev) => ({ ...prev!, ruleName: e.target.value }))}
className="h-9"
placeholder="예: 프로젝트 코드"
/>
</div>
<div className="flex-1 space-y-2">
<Label className="text-sm font-medium"></Label>
<NumberingRulePreview config={currentRule} />
</div>
</div>
{/* 두 번째 줄: 자동 감지된 테이블 정보 표시 */}
{currentTableName && (
<div className="space-y-2">
<Label className="text-sm font-medium"> </Label>
<div className="flex h-9 items-center rounded-md border border-input bg-muted px-3 text-sm text-muted-foreground">
{currentTableName}
</div>
<p className="text-muted-foreground text-xs">
({currentTableName})
</p>
</div>
)}
</div>
<div className="flex-1 overflow-y-auto">

View File

@@ -214,22 +214,11 @@ export const RealtimePreviewDynamic: React.FC<RealtimePreviewProps> = ({
if (component.componentConfig?.type === "table-list") {
// 디자인 해상도 기준으로 픽셀 반환
const screenWidth = 1920; // 기본 디자인 해상도
console.log("📏 [getWidth] table-list 픽셀 사용:", {
componentId: id,
label: component.label,
width: `${screenWidth}px`,
});
return `${screenWidth}px`;
}
// 모든 컴포넌트는 size.width 픽셀 사용
const width = `${size?.width || 100}px`;
console.log("📐 [getWidth] 픽셀 기준 통일:", {
componentId: id,
label: component.label,
width,
sizeWidth: size?.width,
});
return width;
};
@@ -286,33 +275,7 @@ export const RealtimePreviewDynamic: React.FC<RealtimePreviewProps> = ({
if (outerDivRef.current && innerDivRef.current) {
const outerRect = outerDivRef.current.getBoundingClientRect();
const innerRect = innerDivRef.current.getBoundingClientRect();
const computedOuter = window.getComputedStyle(outerDivRef.current);
const computedInner = window.getComputedStyle(innerDivRef.current);
console.log("📐 [DOM 실제 크기 상세]:", {
componentId: id,
label: component.label,
gridColumns: (component as any).gridColumns,
"1. baseStyle.width": baseStyle.width,
"2. 외부 div (파란 테두리)": {
width: `${outerRect.width}px`,
height: `${outerRect.height}px`,
computedWidth: computedOuter.width,
computedHeight: computedOuter.height,
},
"3. 내부 div (컨텐츠 래퍼)": {
width: `${innerRect.width}px`,
height: `${innerRect.height}px`,
computedWidth: computedInner.width,
computedHeight: computedInner.height,
className: innerDivRef.current.className,
inlineStyle: innerDivRef.current.getAttribute("style"),
},
"4. 너비 비교": {
"외부 / 내부": `${outerRect.width}px / ${innerRect.width}px`,
: `${((innerRect.width / outerRect.width) * 100).toFixed(2)}%`,
},
});
// 크기 측정 완료
}
}, [id, component.label, (component as any).gridColumns, baseStyle.width]);

View File

@@ -899,9 +899,14 @@ export default function ScreenDesigner({ selectedScreen, onBackToList }: ScreenD
layoutToUse = safeMigrateLayout(response, canvasWidth);
}
// 🔄 webTypeConfig를 autoGeneration으로 변환
const { convertLayoutComponents } = await import("@/lib/utils/webTypeConfigConverter");
const convertedComponents = convertLayoutComponents(layoutToUse.components);
// 기본 격자 설정 보장 (격자 표시와 스냅 기본 활성화)
const layoutWithDefaultGrid = {
...layoutToUse,
components: convertedComponents, // 변환된 컴포넌트 사용
gridSettings: {
columns: layoutToUse.gridSettings?.columns || 12, // DB 값 우선, 없으면 기본값 12
gap: layoutToUse.gridSettings?.gap ?? 16, // DB 값 우선, 없으면 기본값 16

View File

@@ -752,17 +752,27 @@ export const DetailSettingsPanel: React.FC<DetailSettingsPanelProps> = ({
// console.log("🎨 selectedComponent 전체:", selectedComponent);
const handleConfigChange = (newConfig: WebTypeConfig) => {
// console.log("🔧 WebTypeConfig 업데이트:", {
// widgetType: widget.widgetType,
// oldConfig: currentConfig,
// newConfig,
// componentId: widget.id,
// isEqual: JSON.stringify(currentConfig) === JSON.stringify(newConfig),
// });
// 강제 새 객체 생성으로 React 변경 감지 보장
const freshConfig = { ...newConfig };
onUpdateProperty(widget.id, "webTypeConfig", freshConfig);
// TextTypeConfig의 자동입력 설정을 autoGeneration으로도 매핑
const textConfig = newConfig as any;
if (textConfig.autoInput && textConfig.autoValueType === "numbering_rule" && textConfig.numberingRuleId) {
onUpdateProperty(widget.id, "autoGeneration", {
type: "numbering_rule",
enabled: true,
options: {
numberingRuleId: textConfig.numberingRuleId,
},
});
} else if (textConfig.autoInput === false) {
// 자동입력이 비활성화되면 autoGeneration도 비활성화
onUpdateProperty(widget.id, "autoGeneration", {
type: "none",
enabled: false,
});
}
};
// 1순위: DB에서 지정된 설정 패널 사용
@@ -776,7 +786,13 @@ export const DetailSettingsPanel: React.FC<DetailSettingsPanelProps> = ({
if (ConfigPanelComponent) {
// console.log(`🎨 ✅ ConfigPanelComponent 렌더링 시작`);
return <ConfigPanelComponent config={currentConfig} onConfigChange={handleConfigChange} />;
return (
<ConfigPanelComponent
config={currentConfig}
onConfigChange={handleConfigChange}
tableName={currentTableName} // 화면 테이블명 전달
/>
);
} else {
// console.log(`🎨 ❌ ConfigPanelComponent가 null - WebTypeConfigPanel 사용`);
return (

View File

@@ -7,15 +7,24 @@ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@
import { Checkbox } from "@/components/ui/checkbox";
import { Textarea } from "@/components/ui/textarea";
import { TextTypeConfig } from "@/types/screen";
import { getAvailableNumberingRules } from "@/lib/api/numberingRule";
import { getAvailableNumberingRules, getAvailableNumberingRulesForScreen } from "@/lib/api/numberingRule";
import { NumberingRuleConfig } from "@/types/numbering-rule";
interface TextTypeConfigPanelProps {
config: TextTypeConfig;
onConfigChange: (config: TextTypeConfig) => void;
tableName?: string; // 화면의 테이블명 (선택)
menuObjid?: number; // 메뉴 objid (선택)
}
export const TextTypeConfigPanel: React.FC<TextTypeConfigPanelProps> = ({ config, onConfigChange }) => {
export const TextTypeConfigPanel: React.FC<TextTypeConfigPanelProps> = ({
config,
onConfigChange,
tableName,
menuObjid,
}) => {
console.log("🔍 TextTypeConfigPanel 마운트:", { tableName, menuObjid, config });
// 기본값이 설정된 config 사용
const safeConfig = {
minLength: undefined,
@@ -54,16 +63,46 @@ export const TextTypeConfigPanel: React.FC<TextTypeConfigPanelProps> = ({ config
// 채번 규칙 목록 로드
useEffect(() => {
const loadRules = async () => {
console.log("🔄 채번 규칙 로드 시작:", {
autoValueType: localValues.autoValueType,
tableName,
hasTableName: !!tableName,
});
setLoadingRules(true);
try {
// TODO: 현재 메뉴 objid를 화면 정보에서 가져와야 함
// 지금은 menuObjid 없이 호출 (global 규칙만 조회)
const response = await getAvailableNumberingRules();
let response;
// 테이블명이 있으면 테이블 기반 필터링 사용
if (tableName) {
console.log("📋 테이블 기반 채번 규칙 조회 API 호출:", { tableName });
response = await getAvailableNumberingRulesForScreen(tableName);
console.log("📋 API 응답:", response);
} else {
// 테이블명이 없으면 빈 배열 (테이블 필수)
console.warn("⚠️ 테이블명이 없어 채번 규칙을 조회할 수 없습니다");
setNumberingRules([]);
setLoadingRules(false);
return;
}
if (response.success && response.data) {
setNumberingRules(response.data);
console.log("✅ 채번 규칙 로드 성공:", {
count: response.data.length,
rules: response.data.map((r: any) => ({
ruleId: r.ruleId,
ruleName: r.ruleName,
tableName: r.tableName,
})),
});
} else {
console.warn("⚠️ 채번 규칙 조회 실패:", response.error);
setNumberingRules([]);
}
} catch (error) {
console.error("채번 규칙 목록 로드 실패:", error);
console.error("채번 규칙 목록 로드 실패:", error);
setNumberingRules([]);
} finally {
setLoadingRules(false);
}
@@ -71,9 +110,12 @@ export const TextTypeConfigPanel: React.FC<TextTypeConfigPanelProps> = ({ config
// autoValueType이 numbering_rule일 때만 로드
if (localValues.autoValueType === "numbering_rule") {
console.log("✅ autoValueType === 'numbering_rule', 규칙 로드 시작");
loadRules();
} else {
console.log("⏭️ autoValueType !== 'numbering_rule', 규칙 로드 스킵:", localValues.autoValueType);
}
}, [localValues.autoValueType]);
}, [localValues.autoValueType, tableName]);
// config가 변경될 때 로컬 상태 동기화
useEffect(() => {

View File

@@ -22,7 +22,7 @@ export async function getNumberingRules(): Promise<ApiResponse<NumberingRuleConf
}
/**
* 메뉴별 사용 가능한 채번 규칙 조회
* 메뉴별 사용 가능한 채번 규칙 조회 (기존 방식, 하위 호환성 유지)
* @param menuObjid 현재 메뉴의 objid (선택)
* @returns 사용 가능한 채번 규칙 목록
*/
@@ -40,6 +40,27 @@ export async function getAvailableNumberingRules(
}
}
/**
* 화면용 채번 규칙 조회 (테이블 기반 필터링 - 간소화)
* @param tableName 화면의 테이블명 (필수)
* @returns 해당 테이블의 채번 규칙 목록
*/
export async function getAvailableNumberingRulesForScreen(
tableName: string
): Promise<ApiResponse<NumberingRuleConfig[]>> {
try {
const response = await apiClient.get("/numbering-rules/available-for-screen", {
params: { tableName },
});
return response.data;
} catch (error: any) {
return {
success: false,
error: error.message || "화면용 규칙 조회 실패",
};
}
}
export async function getNumberingRuleById(ruleId: string): Promise<ApiResponse<NumberingRuleConfig>> {
try {
const response = await apiClient.get(`/numbering-rules/${ruleId}`);

View File

@@ -148,19 +148,8 @@ export const DynamicComponentRenderer: React.FC<DynamicComponentRendererProps> =
const tableName = (component as any).tableName;
const columnName = (component as any).columnName;
console.log("🔍 DynamicComponentRenderer 컴포넌트 타입 확인:", {
componentId: component.id,
componentType,
inputType,
webType,
tableName,
columnName,
componentConfig: (component as any).componentConfig,
});
// 카테고리 셀렉트: webType이 "category"이고 tableName과 columnName이 있는 경우만
if ((inputType === "category" || webType === "category") && tableName && columnName) {
console.log("✅ 카테고리 타입 감지 → CategorySelectComponent 렌더링");
try {
const { CategorySelectComponent } = require("@/lib/registry/components/category-select/CategorySelectComponent");
const fieldName = columnName || component.id;
@@ -303,14 +292,6 @@ export const DynamicComponentRenderer: React.FC<DynamicComponentRendererProps> =
componentType === "split-panel-layout" ||
componentType?.includes("layout");
console.log("🔍 [DynamicComponentRenderer] 높이 처리:", {
componentId: component.id,
componentType,
isLayoutComponent,
hasHeight: !!component.style?.height,
height: component.style?.height
});
const { height: _height, ...styleWithoutHeight } = component.style || {};
// 숨김 값 추출

View File

@@ -74,20 +74,12 @@ export const CategorySelectComponent: React.FC<
setError(null);
try {
console.log("📦 카테고리 값 조회:", { tableName, columnName });
const response = await getCategoryValues(tableName, columnName);
if (response.success && response.data) {
// 활성화된 값만 필터링
const activeValues = response.data.filter((v) => v.isActive !== false);
setCategoryValues(activeValues);
console.log("✅ 카테고리 값 조회 성공:", {
total: response.data.length,
active: activeValues.length,
values: activeValues,
});
} else {
setError("카테고리 값을 불러올 수 없습니다");
console.error("❌ 카테고리 값 조회 실패:", response);

View File

@@ -8,19 +8,24 @@ interface NumberingRuleWrapperProps {
config: NumberingRuleComponentConfig;
onChange?: (config: NumberingRuleComponentConfig) => void;
isPreview?: boolean;
tableName?: string; // 현재 화면의 테이블명
}
export const NumberingRuleWrapper: React.FC<NumberingRuleWrapperProps> = ({
config,
onChange,
isPreview = false,
tableName,
}) => {
console.log("📋 NumberingRuleWrapper: 테이블명 전달", { tableName, config });
return (
<div className="h-full w-full">
<NumberingRuleDesigner
maxRules={config.maxRules || 6}
isPreview={isPreview}
className="h-full"
currentTableName={tableName} // 테이블명 전달
/>
</div>
);

View File

@@ -100,16 +100,6 @@ export const TextInputComponent: React.FC<TextInputComponentProps> = ({
const currentFormValue = formData?.[component.columnName];
const currentComponentValue = component.value;
console.log("🔧 TextInput 자동생성 체크:", {
componentId: component.id,
columnName: component.columnName,
autoGenType: testAutoGeneration.type,
ruleId: testAutoGeneration.options?.numberingRuleId,
currentFormValue,
currentComponentValue,
autoGeneratedValue,
isInteractive,
});
// 자동생성된 값이 없고, 현재 값도 없을 때만 생성
if (!autoGeneratedValue && !currentFormValue && !currentComponentValue) {

View File

@@ -8,19 +8,20 @@ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@
import { TextInputConfig } from "./types";
import { AutoGenerationType, AutoGenerationConfig } from "@/types/screen";
import { AutoGenerationUtils } from "@/lib/utils/autoGeneration";
import { getAvailableNumberingRules } from "@/lib/api/numberingRule";
import { getAvailableNumberingRules, getAvailableNumberingRulesForScreen } from "@/lib/api/numberingRule";
import { NumberingRuleConfig } from "@/types/numbering-rule";
export interface TextInputConfigPanelProps {
config: TextInputConfig;
onChange: (config: Partial<TextInputConfig>) => void;
screenTableName?: string; // 🆕 현재 화면의 테이블명
}
/**
* TextInput 설정 패널
* 컴포넌트의 설정값들을 편집할 수 있는 UI 제공
*/
export const TextInputConfigPanel: React.FC<TextInputConfigPanelProps> = ({ config, onChange }) => {
export const TextInputConfigPanel: React.FC<TextInputConfigPanelProps> = ({ config, onChange, screenTableName }) => {
// 채번 규칙 목록 상태
const [numberingRules, setNumberingRules] = useState<NumberingRuleConfig[]>([]);
const [loadingRules, setLoadingRules] = useState(false);
@@ -30,9 +31,20 @@ export const TextInputConfigPanel: React.FC<TextInputConfigPanelProps> = ({ conf
const loadRules = async () => {
setLoadingRules(true);
try {
const response = await getAvailableNumberingRules();
let response;
// 🆕 테이블명이 있으면 테이블 기반 필터링, 없으면 전체 조회
if (screenTableName) {
console.log("🔍 TextInputConfigPanel: 테이블 기반 채번 규칙 로드", { screenTableName });
response = await getAvailableNumberingRulesForScreen(screenTableName);
} else {
console.log("🔍 TextInputConfigPanel: 전체 채번 규칙 로드 (테이블명 없음)");
response = await getAvailableNumberingRules();
}
if (response.success && response.data) {
setNumberingRules(response.data);
console.log("✅ 채번 규칙 로드 완료:", response.data.length, "개");
}
} catch (error) {
console.error("채번 규칙 목록 로드 실패:", error);
@@ -45,7 +57,7 @@ export const TextInputConfigPanel: React.FC<TextInputConfigPanelProps> = ({ conf
if (config.autoGeneration?.type === "numbering_rule") {
loadRules();
}
}, [config.autoGeneration?.type]);
}, [config.autoGeneration?.type, screenTableName]);
const handleChange = (key: keyof TextInputConfig, value: any) => {
onChange({ [key]: value });
@@ -174,7 +186,12 @@ export const TextInputConfigPanel: React.FC<TextInputConfigPanelProps> = ({ conf
) : (
numberingRules.map((rule) => (
<SelectItem key={rule.ruleId} value={rule.ruleId}>
{rule.ruleName} ({rule.ruleId})
{rule.ruleName}
{rule.description && (
<span className="text-muted-foreground ml-2 text-xs">
- {rule.description}
</span>
)}
</SelectItem>
))
)}

View File

@@ -19,6 +19,8 @@ import { DashboardConfigPanel } from "@/components/screen/config-panels/Dashboar
export type ConfigPanelComponent = React.ComponentType<{
config: any;
onConfigChange: (config: any) => void;
tableName?: string; // 화면 테이블명 (선택)
menuObjid?: number; // 메뉴 objid (선택)
}>;
// ButtonConfigPanel 래퍼 (config/onConfigChange → component/onUpdateProperty 변환)

View File

@@ -0,0 +1,58 @@
/**
* WebTypeConfig와 AutoGeneration 간 변환 유틸리티
*/
import { ComponentData } from "@/types/screen";
/**
* webTypeConfig의 자동입력 설정을 autoGeneration으로 변환
*/
export function convertWebTypeConfigToAutoGeneration(component: ComponentData): ComponentData {
// webTypeConfig가 없으면 변환 불필요
if (!component.webTypeConfig) {
return component;
}
const config = component.webTypeConfig as any;
// 자동입력이 활성화되어 있는지 확인
if (!config.autoInput || !config.autoValueType) {
return component;
}
// 이미 autoGeneration이 올바르게 설정되어 있으면 변환 불필요
if (
component.autoGeneration &&
component.autoGeneration.type === config.autoValueType &&
component.autoGeneration.options?.numberingRuleId === config.numberingRuleId
) {
return component;
}
// autoGeneration 객체 생성
const autoGeneration: any = {
type: config.autoValueType,
enabled: true,
};
// 채번 규칙인 경우 options.numberingRuleId 설정
if (config.autoValueType === "numbering_rule" && config.numberingRuleId) {
autoGeneration.options = {
numberingRuleId: config.numberingRuleId,
};
}
return {
...component,
autoGeneration,
};
}
/**
* 레이아웃의 모든 컴포넌트에 대해 webTypeConfig → autoGeneration 변환 적용
*/
export function convertLayoutComponents(components: ComponentData[]): ComponentData[] {
return components.map(convertWebTypeConfigToAutoGeneration);
}

View File

@@ -132,7 +132,16 @@ export type AutoGenerationType = "table" | "form" | "mixed";
*/
export interface AutoGenerationConfig {
type: AutoGenerationType;
enabled?: boolean;
tableName?: string;
includeSearch?: boolean;
includePagination?: boolean;
options?: {
length?: number; // 랜덤 문자열/숫자 길이
prefix?: string; // 접두사
suffix?: string; // 접미사
format?: string; // 시간 형식 (current_time용)
startValue?: number; // 시퀀스 시작값
numberingRuleId?: string; // 채번 규칙 ID (numbering_rule 타입용)
};
}