@bimatrix-aud-platform/aud_mcp_server 1.1.57 → 1.1.63

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,16 @@
1
+ /**
2
+ * compact 모드 유틸리티
3
+ *
4
+ * 생성된 MTSD JSON에서 기본값과 동일한 속성을 제거하여
5
+ * 간소화된 .design.json 형태로 변환합니다.
6
+ */
7
+ /** Element JSON에서 기본값 속성을 제거 */
8
+ export declare function compactElement(element: Record<string, any>): Record<string, any>;
9
+ /** GridColumn JSON에서 기본값 속성을 제거 */
10
+ export declare function compactGridColumn(column: Record<string, any>): Record<string, any>;
11
+ /** DataSource JSON에서 기본값 속성을 제거 */
12
+ export declare function compactDataSource(ds: Record<string, any>): Record<string, any>;
13
+ /** OlapField JSON에서 기본값 속성을 제거 */
14
+ export declare function compactOlapField(field: Record<string, any>): Record<string, any>;
15
+ /** OlapField 배열 전체를 compact 처리 */
16
+ export declare function compactOlapFields(fields: Record<string, any>[]): Record<string, any>[];
@@ -0,0 +1,299 @@
1
+ /**
2
+ * compact 모드 유틸리티
3
+ *
4
+ * 생성된 MTSD JSON에서 기본값과 동일한 속성을 제거하여
5
+ * 간소화된 .design.json 형태로 변환합니다.
6
+ */
7
+ import { defaultStyle, defaultDocking, defaultValidator, ELEMENT_DEFAULTS_MAP, } from "./defaults.js";
8
+ // ============================================
9
+ // 공통 유틸
10
+ // ============================================
11
+ /** 깊은 비교 (키 순서 무관) */
12
+ function deepEqual(a, b) {
13
+ if (a === b)
14
+ return true;
15
+ if (a == null || b == null)
16
+ return false;
17
+ if (typeof a !== typeof b)
18
+ return false;
19
+ if (typeof a !== "object")
20
+ return a === b;
21
+ if (Array.isArray(a) !== Array.isArray(b))
22
+ return false;
23
+ if (Array.isArray(a)) {
24
+ if (a.length !== b.length)
25
+ return false;
26
+ for (let i = 0; i < a.length; i++) {
27
+ if (!deepEqual(a[i], b[i]))
28
+ return false;
29
+ }
30
+ return true;
31
+ }
32
+ const keysA = Object.keys(a);
33
+ const keysB = Object.keys(b);
34
+ if (keysA.length !== keysB.length)
35
+ return false;
36
+ for (const key of keysA) {
37
+ if (!b.hasOwnProperty(key))
38
+ return false;
39
+ if (!deepEqual(a[key], b[key]))
40
+ return false;
41
+ }
42
+ return true;
43
+ }
44
+ /** 객체에서 기본값과 동일한 속성을 제거. 빈 객체가 되면 undefined 반환 */
45
+ function stripMatchingDefaults(obj, defaults, keepKeys) {
46
+ const result = {};
47
+ let hasValue = false;
48
+ for (const [key, value] of Object.entries(obj)) {
49
+ if (keepKeys && keepKeys.has(key)) {
50
+ result[key] = value;
51
+ hasValue = true;
52
+ continue;
53
+ }
54
+ if (key in defaults && deepEqual(value, defaults[key])) {
55
+ continue; // 기본값과 동일하면 제거
56
+ }
57
+ result[key] = value;
58
+ hasValue = true;
59
+ }
60
+ return hasValue ? result : undefined;
61
+ }
62
+ // ============================================
63
+ // Element compact
64
+ // ============================================
65
+ /** Element JSON에서 기본값 속성을 제거 */
66
+ export function compactElement(element) {
67
+ const result = {};
68
+ const type = element.Type;
69
+ // 필수 속성 유지
70
+ result.Type = type;
71
+ result.Id = element.Id;
72
+ result.Name = element.Name;
73
+ // Position: Left/Top/Width/Height만 유지, 기본 Docking/ZIndex/TabIndex 제거
74
+ if (element.Position) {
75
+ const pos = element.Position;
76
+ const compactPos = {
77
+ Left: pos.Left,
78
+ Top: pos.Top,
79
+ Width: pos.Width,
80
+ Height: pos.Height,
81
+ };
82
+ // ZIndex: 기본값(0 또는 1)이 아닌 경우만 유지
83
+ if (pos.ZIndex !== undefined && pos.ZIndex !== 0 && pos.ZIndex !== 1) {
84
+ compactPos.ZIndex = pos.ZIndex;
85
+ }
86
+ // TabIndex: 0이 아닌 경우만 유지
87
+ if (pos.TabIndex !== undefined && pos.TabIndex !== 0) {
88
+ compactPos.TabIndex = pos.TabIndex;
89
+ }
90
+ // Docking: 기본값이 아닌 경우만 유지
91
+ if (pos.Docking && !deepEqual(pos.Docking, defaultDocking())) {
92
+ compactPos.Docking = pos.Docking;
93
+ }
94
+ result.Position = compactPos;
95
+ }
96
+ // Style: Type=0(Skin)이면 Style 전체 제거, Type!=0이면 기본값 비교
97
+ if (element.Style) {
98
+ const style = element.Style;
99
+ const styleType = style.Type;
100
+ if (styleType === undefined || styleType === 0) {
101
+ // Skin 모드: BoxStyle이 있으면 BoxStyle만 유지
102
+ if (style.BoxStyle && style.BoxStyle !== "") {
103
+ result.Style = { Type: 0, BoxStyle: style.BoxStyle };
104
+ }
105
+ // BoxStyle도 없으면 Style 전체 생략
106
+ }
107
+ else {
108
+ // Type!=0 (커스텀 스타일): 기본값과 비교하여 제거
109
+ const defStyle = defaultStyle();
110
+ const compactStyle = { Type: styleType };
111
+ if (style.BoxStyle !== undefined && style.BoxStyle !== "") {
112
+ compactStyle.BoxStyle = style.BoxStyle;
113
+ }
114
+ if (style.Background && !deepEqual(style.Background, defStyle.Background)) {
115
+ compactStyle.Background = style.Background;
116
+ }
117
+ if (style.Border && !deepEqual(style.Border, defStyle.Border)) {
118
+ compactStyle.Border = style.Border;
119
+ }
120
+ if (style.Font && !deepEqual(style.Font, defStyle.Font)) {
121
+ compactStyle.Font = style.Font;
122
+ }
123
+ result.Style = compactStyle;
124
+ }
125
+ }
126
+ // Visible: true(기본값)이면 제거
127
+ if (element.Visible === false) {
128
+ result.Visible = false;
129
+ }
130
+ // Enabled: true(기본값)이면 제거
131
+ if (element.Enabled === false) {
132
+ result.Enabled = false;
133
+ }
134
+ // TabStop: true(기본값)이면 제거
135
+ if (element.TabStop === false) {
136
+ result.TabStop = false;
137
+ }
138
+ // DataSource: 빈 문자열이면 제거
139
+ if (element.DataSource && element.DataSource !== "") {
140
+ result.DataSource = element.DataSource;
141
+ }
142
+ // 타입별 기본값 제거
143
+ const defaultsFactory = ELEMENT_DEFAULTS_MAP[type];
144
+ const typeDefaults = defaultsFactory ? defaultsFactory() : {};
145
+ // 처리 완료 키
146
+ const processedKeys = new Set([
147
+ "Type", "Id", "Name", "Position", "Style",
148
+ "Visible", "Enabled", "TabStop", "DataSource",
149
+ ]);
150
+ // 나머지 속성: 타입 기본값과 다른 것만 유지
151
+ for (const [key, value] of Object.entries(element)) {
152
+ if (processedKeys.has(key))
153
+ continue;
154
+ if (key in typeDefaults && deepEqual(value, typeDefaults[key]))
155
+ continue;
156
+ result[key] = value;
157
+ }
158
+ return result;
159
+ }
160
+ // ============================================
161
+ // GridColumn compact
162
+ // ============================================
163
+ /** GridColumn JSON에서 기본값 속성을 제거 */
164
+ export function compactGridColumn(column) {
165
+ const result = {};
166
+ // Name 필수
167
+ result.Name = column.Name;
168
+ // Caption: Name과 같으면 제거
169
+ if (column.Caption && column.Caption !== column.Name) {
170
+ result.Caption = column.Caption;
171
+ }
172
+ // Width: 100이면 제거
173
+ if (column.Width !== undefined && column.Width !== 100) {
174
+ result.Width = column.Width;
175
+ }
176
+ // Validator: 기본값이면 제거
177
+ if (column.Validator && !deepEqual(column.Validator, defaultValidator())) {
178
+ result.Validator = column.Validator;
179
+ }
180
+ // KeyType: 0이면 제거
181
+ if (column.KeyType !== undefined && column.KeyType !== 0) {
182
+ result.KeyType = column.KeyType;
183
+ }
184
+ // HeaderPosition: "center"이면 제거
185
+ if (column.HeaderPosition && column.HeaderPosition !== "center") {
186
+ result.HeaderPosition = column.HeaderPosition;
187
+ }
188
+ // ColumnType: 0이면 제거
189
+ if (column.ColumnType !== undefined && column.ColumnType !== 0) {
190
+ result.ColumnType = column.ColumnType;
191
+ }
192
+ // 나머지 속성은 그대로 유지
193
+ const processedKeys = new Set([
194
+ "Name", "Caption", "Width", "Validator", "KeyType",
195
+ "HeaderPosition", "ColumnType",
196
+ ]);
197
+ for (const [key, value] of Object.entries(column)) {
198
+ if (processedKeys.has(key))
199
+ continue;
200
+ result[key] = value;
201
+ }
202
+ return result;
203
+ }
204
+ // ============================================
205
+ // DataSource compact
206
+ // ============================================
207
+ /** DataSource JSON에서 기본값 속성을 제거 */
208
+ export function compactDataSource(ds) {
209
+ const result = {};
210
+ // 필수 속성
211
+ result.Id = ds.Id;
212
+ result.Name = ds.Name;
213
+ // ConnectionCode: 빈 문자열이 아니면 유지
214
+ if (ds.ConnectionCode) {
215
+ result.ConnectionCode = ds.ConnectionCode;
216
+ }
217
+ // DSType: 0(기본값)이면 제거
218
+ if (ds.DSType !== undefined && ds.DSType !== 0) {
219
+ result.DSType = ds.DSType;
220
+ }
221
+ // SQL: 빈 문자열이면 제거
222
+ if (ds.SQL && ds.SQL !== "") {
223
+ result.SQL = ds.SQL;
224
+ }
225
+ // UseMeta/UseCache/Encrypted: false(기본값)이면 제거
226
+ if (ds.UseMeta === true)
227
+ result.UseMeta = true;
228
+ if (ds.UseCache === true)
229
+ result.UseCache = true;
230
+ if (ds.Encrypted === true)
231
+ result.Encrypted = true;
232
+ // Params: 빈 배열이면 제거
233
+ if (ds.Params && ds.Params.length > 0) {
234
+ result.Params = ds.Params;
235
+ }
236
+ // Columns: 빈 배열이면 제거
237
+ if (ds.Columns && ds.Columns.length > 0) {
238
+ result.Columns = ds.Columns;
239
+ }
240
+ // 나머지 pass-through
241
+ const processedKeys = new Set([
242
+ "Id", "Name", "ConnectionCode", "DSType", "SQL",
243
+ "UseMeta", "UseCache", "Encrypted", "Params", "Columns",
244
+ ]);
245
+ for (const [key, value] of Object.entries(ds)) {
246
+ if (processedKeys.has(key))
247
+ continue;
248
+ result[key] = value;
249
+ }
250
+ return result;
251
+ }
252
+ // ============================================
253
+ // OlapField compact
254
+ // ============================================
255
+ /** OlapField JSON에서 기본값 속성을 제거 */
256
+ export function compactOlapField(field) {
257
+ const result = {};
258
+ // 필수: Key, Caption
259
+ result.Key = field.Key;
260
+ if (field.Caption && field.Caption !== field.Key) {
261
+ result.Caption = field.Caption;
262
+ }
263
+ // Category: 0(Dimension)이 기본, 명시적으로 유지
264
+ if (field.Category !== undefined) {
265
+ result.Category = field.Category;
266
+ }
267
+ // Area: 0(Hidden)이 아닌 경우 유지
268
+ if (field.Area !== undefined && field.Area !== 0) {
269
+ result.Area = field.Area;
270
+ }
271
+ // SummaryType: 0(None)이 아닌 경우 유지
272
+ if (field.SummaryType !== undefined && field.SummaryType !== 0) {
273
+ result.SummaryType = field.SummaryType;
274
+ }
275
+ // Format: 빈 문자열이 아닌 경우 유지
276
+ if (field.Format && field.Format !== "") {
277
+ result.Format = field.Format;
278
+ }
279
+ // Width: 100이 아닌 경우 유지
280
+ if (field.Width !== undefined && field.Width !== 100) {
281
+ result.Width = field.Width;
282
+ }
283
+ // SortType: 0(None)이 아닌 경우 유지
284
+ if (field.SortType !== undefined && field.SortType !== 0) {
285
+ result.SortType = field.SortType;
286
+ }
287
+ // DataType: 명시적으로 유지 (Dimension/Measure 구분에 중요)
288
+ if (field.DataType !== undefined) {
289
+ result.DataType = field.DataType;
290
+ }
291
+ // 나머지 boolean/기본값들은 제거
292
+ // 이하 속성들은 거의 항상 기본값이므로 compact에서 생략
293
+ // Visible, Expanded, MoveAble, SortAble, FilterAble, AllowFilter, etc.
294
+ return result;
295
+ }
296
+ /** OlapField 배열 전체를 compact 처리 */
297
+ export function compactOlapFields(fields) {
298
+ return fields.map(compactOlapField);
299
+ }
@@ -62,9 +62,9 @@ export function defaultStyle() {
62
62
  export function defaultValidator() {
63
63
  return {
64
64
  ValidateType: 8, // enGridValidateType.None
65
- UseGuideMessage: false,
65
+ UseGuideMessage: true,
66
66
  GuideLanguageCode: "",
67
- UseErrorMessage: false,
67
+ UseErrorMessage: true,
68
68
  ErrorLanguageCode: "",
69
69
  };
70
70
  }
@@ -1,6 +1,8 @@
1
1
  import { readFileSync, writeFileSync } from "fs";
2
+ import { basename } from "path";
2
3
  import { randomBytes } from "crypto";
3
4
  import { createDefaultOlapField, isNumericColumnType } from "./olap-field.js";
5
+ import { compactOlapField } from "./compact.js";
4
6
  /**
5
7
  * MTSD 파일을 읽고, 자동 보정 규칙을 적용한 뒤, 파일을 덮어씁니다.
6
8
  */
@@ -50,15 +52,17 @@ export function fixMtsd(filePath) {
50
52
  }
51
53
  }
52
54
  }
55
+ // .design.json 여부 판별 (compact 형식 → 기본값 추가 생략)
56
+ const isDesignJson = basename(filePath) === ".design.json";
53
57
  // 3. 보정 규칙 적용
54
58
  // Rule 1: DataSource Name→Id 참조 보정
55
59
  fixDataSourceReferences(doc, dsNameToId, dsIdSet, fixes);
56
60
  // Rule 2: OlapGrid DataSource 기반 Fields 자동 생성
57
- fixOlapGridFields(doc, datas, fixes);
61
+ fixOlapGridFields(doc, datas, fixes, isDesignJson);
58
62
  // Rule 2-2: OlapGrid Fields 내 CreateType=1 (Measures) 필드 보정
59
63
  fixOlapMeasuresField(doc, fixes);
60
64
  // Rule 3: Enum/Range 값 범위 초과 보정
61
- fixEnumAndRangeValues(doc, datas, fixes);
65
+ fixEnumAndRangeValues(doc, datas, fixes, isDesignJson);
62
66
  // Rule 4: Tab > TabItems 내 ChildElements → Controls 노드 이름 보정
63
67
  fixTabItemChildElements(doc, fixes);
64
68
  // Rule 5: ServerScriptText Key 누락 보정
@@ -111,7 +115,7 @@ function fixDataSourceReferences(doc, dsNameToId, dsIdSet, fixes) {
111
115
  }
112
116
  // ---- Rule 2: OlapGrid DataSource 기반 Fields 자동 생성 ----
113
117
  // createDefaultOlapField, isNumericColumnType는 olap-field.ts에서 import
114
- function fixOlapGridFields(doc, datas, fixes) {
118
+ function fixOlapGridFields(doc, datas, fixes, isDesignJson) {
115
119
  // DataSource Id → DataSource 객체 매핑
116
120
  const dsById = new Map();
117
121
  for (const ds of datas) {
@@ -193,11 +197,15 @@ function fixOlapGridFields(doc, datas, fixes) {
193
197
  }
194
198
  }
195
199
  }
200
+ // .design.json이면 compact 처리 (기본값 속성 제거)
201
+ const finalFields = isDesignJson
202
+ ? newFields.map(compactOlapField)
203
+ : newFields;
196
204
  // iOLAPView 구조 생성/보정
197
205
  if (!el.iOLAPView) {
198
206
  el.iOLAPView = {};
199
207
  }
200
- el.iOLAPView.Fields = newFields;
208
+ el.iOLAPView.Fields = finalFields;
201
209
  fixes.push(`[Rule2] ${path}: DataSource "${ds.Name || ds.Id}" 컬럼 기준 OlapGrid Fields ${newFields.length}개 자동 생성`);
202
210
  });
203
211
  }
@@ -366,7 +374,7 @@ function clampColorRGBA(obj, path, fixes) {
366
374
  }
367
375
  }
368
376
  }
369
- function fixEnumAndRangeValues(doc, datas, fixes) {
377
+ function fixEnumAndRangeValues(doc, datas, fixes, isDesignJson) {
370
378
  // ── 1. ReportInfo ──
371
379
  const ri = doc.ReportInfo;
372
380
  if (ri) {
@@ -384,14 +392,16 @@ function fixEnumAndRangeValues(doc, datas, fixes) {
384
392
  if (form.Style) {
385
393
  const fPath = `Form("${form.Name}").Style`;
386
394
  fixIntRange(form.Style, "Type", 0, 2, 0, fPath, fixes); // 기본값: 0(Skin)
387
- // Background 빈 객체 보정
388
- if (!form.Style.Background || typeof form.Style.Background !== "object") {
389
- form.Style.Background = { Color: { R: 255, G: 255, B: 255, A: 1 } };
390
- fixes.push(`[Rule3] ${fPath}.Background: 누락 흰색 기본값 보정`);
391
- }
392
- else if (!form.Style.Background.Color) {
393
- form.Style.Background.Color = { R: 255, G: 255, B: 255, A: 1 };
394
- fixes.push(`[Rule3] ${fPath}.Background.Color: 누락 흰색 기본값 보정`);
395
+ // Background 빈 객체 보정 (.design.json에서는 생략 — compact 원칙)
396
+ if (!isDesignJson) {
397
+ if (!form.Style.Background || typeof form.Style.Background !== "object") {
398
+ form.Style.Background = { Color: { R: 255, G: 255, B: 255, A: 1 } };
399
+ fixes.push(`[Rule3] ${fPath}.Background: 누락 → 흰색 기본값 보정`);
400
+ }
401
+ else if (!form.Style.Background.Color) {
402
+ form.Style.Background.Color = { R: 255, G: 255, B: 255, A: 1 };
403
+ fixes.push(`[Rule3] ${fPath}.Background.Color: 누락 → 흰색 기본값 보정`);
404
+ }
395
405
  }
396
406
  }
397
407
  }
@@ -400,26 +410,29 @@ function fixEnumAndRangeValues(doc, datas, fixes) {
400
410
  const elements = form.Elements || [];
401
411
  walkElements(elements, (el, path) => {
402
412
  // ---- AutoRefresh / DoRefresh / DoExport 필수 속성 보정 ----
413
+ // .design.json에서는 기본값 추가를 생략 (compact 원칙)
403
414
  const type = el.Type;
404
- // AutoRefresh 대상: DataGrid, ComboBox, MultiComboBox, OlapGrid, Chart, PieChart, ScatterChart, PolygonChart, TreeGrid, iGrid, WebContainer, Tree
405
- const hasAutoRefresh = ["DataGrid", "ComboBox", "MultiComboBox", "OlapGrid", "Chart", "PieChart", "ScatterChart", "PolygonChart", "TreeGrid", "iGrid", "WebContainer", "Tree"];
406
- if (hasAutoRefresh.includes(type) && !("AutoRefresh" in el)) {
407
- const autoDefault = type === "ComboBox" ? true : false;
408
- el.AutoRefresh = autoDefault;
409
- fixes.push(`[Rule3] ${path}.AutoRefresh: 누락 → ${autoDefault}`);
410
- }
411
- // DoRefresh 대상: DataGrid, ComboBox, OlapGrid, Chart, PieChart, ScatterChart, PolygonChart, TreeGrid, iGrid, UserComponent, WebContainer, Tree
412
- const hasDoRefresh = ["DataGrid", "ComboBox", "OlapGrid", "Chart", "PieChart", "ScatterChart", "PolygonChart", "TreeGrid", "iGrid", "UserComponent", "WebContainer", "Tree"];
413
- if (hasDoRefresh.includes(type) && !("DoRefresh" in el)) {
414
- const isCombo = type === "ComboBox";
415
- el.DoRefresh = isCombo ? false : true;
416
- fixes.push(`[Rule3] ${path}.DoRefresh: 누락 ${el.DoRefresh}`);
417
- }
418
- // DoExport 대상: DataGrid, OlapGrid, Chart, PieChart, ScatterChart, PolygonChart, TreeGrid, iGrid, UserComponent, Tab
419
- const hasDoExport = ["DataGrid", "OlapGrid", "Chart", "PieChart", "ScatterChart", "PolygonChart", "TreeGrid", "iGrid", "UserComponent", "Tab"];
420
- if (hasDoExport.includes(type) && !("DoExport" in el)) {
421
- el.DoExport = true;
422
- fixes.push(`[Rule3] ${path}.DoExport: 누락 true`);
415
+ if (!isDesignJson) {
416
+ // AutoRefresh 대상: DataGrid, ComboBox, MultiComboBox, OlapGrid, Chart, PieChart, ScatterChart, PolygonChart, TreeGrid, iGrid, WebContainer, Tree
417
+ const hasAutoRefresh = ["DataGrid", "ComboBox", "MultiComboBox", "OlapGrid", "Chart", "PieChart", "ScatterChart", "PolygonChart", "TreeGrid", "iGrid", "WebContainer", "Tree"];
418
+ if (hasAutoRefresh.includes(type) && !("AutoRefresh" in el)) {
419
+ const autoDefault = type === "ComboBox" ? true : false;
420
+ el.AutoRefresh = autoDefault;
421
+ fixes.push(`[Rule3] ${path}.AutoRefresh: 누락 → ${autoDefault}`);
422
+ }
423
+ // DoRefresh 대상: DataGrid, ComboBox, OlapGrid, Chart, PieChart, ScatterChart, PolygonChart, TreeGrid, iGrid, UserComponent, WebContainer, Tree
424
+ const hasDoRefresh = ["DataGrid", "ComboBox", "OlapGrid", "Chart", "PieChart", "ScatterChart", "PolygonChart", "TreeGrid", "iGrid", "UserComponent", "WebContainer", "Tree"];
425
+ if (hasDoRefresh.includes(type) && !("DoRefresh" in el)) {
426
+ const isCombo = type === "ComboBox";
427
+ el.DoRefresh = isCombo ? false : true;
428
+ fixes.push(`[Rule3] ${path}.DoRefresh: 누락 → ${el.DoRefresh}`);
429
+ }
430
+ // DoExport 대상: DataGrid, OlapGrid, Chart, PieChart, ScatterChart, PolygonChart, TreeGrid, iGrid, UserComponent, Tab
431
+ const hasDoExport = ["DataGrid", "OlapGrid", "Chart", "PieChart", "ScatterChart", "PolygonChart", "TreeGrid", "iGrid", "UserComponent", "Tab"];
432
+ if (hasDoExport.includes(type) && !("DoExport" in el)) {
433
+ el.DoExport = true;
434
+ fixes.push(`[Rule3] ${path}.DoExport: 누락 → true`);
435
+ }
423
436
  }
424
437
  // ---- Style.Type: 커스텀 색상 설정 시 Type=2(Custom) 자동 보정 ----
425
438
  // Control.ts UpdateStyle: Type=0(Skin)이면 커스텀 Background/Border/Font CSS 무시됨
@@ -487,45 +500,56 @@ function fixEnumAndRangeValues(doc, datas, fixes) {
487
500
  }
488
501
  // ---- Style.Background Color (빈 객체 {} 허용 안 됨) ----
489
502
  if (el.Style) {
490
- if (!el.Style.Background || typeof el.Style.Background !== "object") {
491
- el.Style.Background = { Color: { R: 255, G: 255, B: 255, A: 1 } };
492
- fixes.push(`[Rule3] ${path}.Style.Background: 누락 흰색 기본값 보정`);
493
- }
494
- else if (!el.Style.Background.Color) {
495
- el.Style.Background.Color = { R: 255, G: 255, B: 255, A: 1 };
496
- fixes.push(`[Rule3] ${path}.Style.Background.Color: 누락 → 흰색 기본값 보정`);
503
+ // .design.json에서는 Background 기본값 추가 생략 (compact 원칙)
504
+ if (!isDesignJson) {
505
+ if (!el.Style.Background || typeof el.Style.Background !== "object") {
506
+ el.Style.Background = { Color: { R: 255, G: 255, B: 255, A: 1 } };
507
+ fixes.push(`[Rule3] ${path}.Style.Background: 누락 → 흰색 기본값 보정`);
508
+ }
509
+ else if (!el.Style.Background.Color) {
510
+ el.Style.Background.Color = { R: 255, G: 255, B: 255, A: 1 };
511
+ fixes.push(`[Rule3] ${path}.Style.Background.Color: 누락 → 흰색 기본값 보정`);
512
+ }
497
513
  }
514
+ // 색상 범위 검증은 항상 수행 (존재하는 값의 유효성 체크)
498
515
  const bg = el.Style.Background;
499
- clampColorRGBA(bg, `${path}.Style.Background`, fixes);
500
- if (bg.Color)
501
- clampColorRGBA(bg.Color, `${path}.Style.Background.Color`, fixes);
516
+ if (bg) {
517
+ clampColorRGBA(bg, `${path}.Style.Background`, fixes);
518
+ if (bg.Color)
519
+ clampColorRGBA(bg.Color, `${path}.Style.Background.Color`, fixes);
520
+ }
502
521
  }
503
522
  // ---- DataGrid: ColumnHeaderHeight / RowHeight / ShowHeader 필수 ----
504
523
  if (el.Type === "DataGrid") {
505
- if (!("ColumnHeaderHeight" in el)) {
506
- el.ColumnHeaderHeight = 28;
507
- fixes.push(`[Rule3] ${path}.ColumnHeaderHeight: 누락 28`);
508
- }
509
- if (!("RowHeight" in el)) {
510
- el.RowHeight = 24;
511
- fixes.push(`[Rule3] ${path}.RowHeight: 누락 24`);
512
- }
513
- if (!("ShowHeader" in el)) {
514
- el.ShowHeader = 3;
515
- fixes.push(`[Rule3] ${path}.ShowHeader: 누락 3 (Row&Column)`);
516
- }
517
- fixIntRange(el, "ShowHeader", 0, 3, 3, path, fixes); // 0:None, 1:Row, 2:Column, 3:Row&Column
518
- if (!("SelectRule" in el)) {
519
- el.SelectRule = 2;
520
- fixes.push(`[Rule3] ${path}.SelectRule: 누락 → 2 (SingleRange)`);
524
+ if (!isDesignJson) {
525
+ if (!("ColumnHeaderHeight" in el)) {
526
+ el.ColumnHeaderHeight = 28;
527
+ fixes.push(`[Rule3] ${path}.ColumnHeaderHeight: 누락 → 28`);
528
+ }
529
+ if (!("RowHeight" in el)) {
530
+ el.RowHeight = 24;
531
+ fixes.push(`[Rule3] ${path}.RowHeight: 누락 → 24`);
532
+ }
533
+ if (!("ShowHeader" in el)) {
534
+ el.ShowHeader = 3;
535
+ fixes.push(`[Rule3] ${path}.ShowHeader: 누락 → 3 (Row&Column)`);
536
+ }
537
+ if (!("SelectRule" in el)) {
538
+ el.SelectRule = 2;
539
+ fixes.push(`[Rule3] ${path}.SelectRule: 누락 → 2 (SingleRange)`);
540
+ }
521
541
  }
522
- fixIntRange(el, "SelectRule", 0, 3, 2, path, fixes); // 0:SingleRow, 1:MultiRow, 2:SingleRange, 3:SingleCell
542
+ // 범위 검증은 항상 수행 (존재하는 값의 유효성 체크)
543
+ fixIntRange(el, "ShowHeader", 0, 3, 3, path, fixes);
544
+ fixIntRange(el, "SelectRule", 0, 3, 2, path, fixes);
523
545
  }
524
546
  // ---- TreeGrid: ToggleBtnSize 필수 ----
525
547
  if (el.Type === "TreeGrid") {
526
- if (!("ToggleBtnSize" in el)) {
527
- el.ToggleBtnSize = 10;
528
- fixes.push(`[Rule3] ${path}.ToggleBtnSize: 누락 10`);
548
+ if (!isDesignJson) {
549
+ if (!("ToggleBtnSize" in el)) {
550
+ el.ToggleBtnSize = 10;
551
+ fixes.push(`[Rule3] ${path}.ToggleBtnSize: 누락 → 10`);
552
+ }
529
553
  }
530
554
  }
531
555
  // ---- DataGrid / TreeGrid Columns ----
@@ -544,7 +568,7 @@ function fixEnumAndRangeValues(doc, datas, fixes) {
544
568
  }
545
569
  }
546
570
  // ---- FileUploadButton: Value / Cursor 필수 ----
547
- if (el.Type === "FileUploadButton") {
571
+ if (el.Type === "FileUploadButton" && !isDesignJson) {
548
572
  if (!("Value" in el)) {
549
573
  el.Value = "upload";
550
574
  fixes.push(`[Rule3] ${path}.Value: 누락 → "upload"`);
@@ -556,7 +580,7 @@ function fixEnumAndRangeValues(doc, datas, fixes) {
556
580
  }
557
581
  // ---- Chart: PlotOptions 필수 ----
558
582
  const isChart = ["Chart", "PieChart", "ScatterChart", "PolygonChart"].includes(type);
559
- if (isChart) {
583
+ if (isChart && !isDesignJson) {
560
584
  if (!el.PlotOptions || typeof el.PlotOptions !== "object") {
561
585
  el.PlotOptions = { Animation: 1000, EnableMouseTracking: true, DataLabels: {}, ConnectNulls: false, AllowOverlap: false };
562
586
  fixes.push(`[Rule3] ${path}.PlotOptions: 누락 → 기본값 생성`);
@@ -587,16 +611,19 @@ function fixEnumAndRangeValues(doc, datas, fixes) {
587
611
  }
588
612
  // ---- MultiComboBox: InitType / RefreshType 필수 + enum 범위 ----
589
613
  if (el.Type === "MultiComboBox") {
590
- if (!("InitType" in el)) {
591
- el.InitType = 0;
592
- fixes.push(`[Rule3] ${path}.InitType: 누락 0 (CurrentValue)`);
593
- }
594
- fixIntRange(el, "InitType", 0, 2, 0, path, fixes); // 0:CurrentValue, 1:InitValue, 2:없음
595
- if (!("RefreshType" in el)) {
596
- el.RefreshType = 1;
597
- fixes.push(`[Rule3] ${path}.RefreshType: 누락 → 1 (FirstTime)`);
614
+ if (!isDesignJson) {
615
+ if (!("InitType" in el)) {
616
+ el.InitType = 0;
617
+ fixes.push(`[Rule3] ${path}.InitType: 누락 → 0 (CurrentValue)`);
618
+ }
619
+ if (!("RefreshType" in el)) {
620
+ el.RefreshType = 1;
621
+ fixes.push(`[Rule3] ${path}.RefreshType: 누락 → 1 (FirstTime)`);
622
+ }
598
623
  }
599
- fixIntRange(el, "RefreshType", 0, 2, 1, path, fixes); // 0:None, 1:FirstTime, 2:EveryTime
624
+ // 범위 검증은 항상 수행
625
+ fixIntRange(el, "InitType", 0, 2, 0, path, fixes);
626
+ fixIntRange(el, "RefreshType", 0, 2, 1, path, fixes);
600
627
  }
601
628
  // ---- OlapGrid Fields ----
602
629
  if (el.Type === "OlapGrid") {
@@ -78,6 +78,7 @@ export function createDefaultOlapField(key, caption) {
78
78
  MeasureFilterTypeB: 0,
79
79
  MeasureAndOrOperator: 2,
80
80
  },
81
+ RelationFields: [],
81
82
  };
82
83
  }
83
84
  // ─── summaryType 문자열 → enum 변환 ───