@sapui5/sap.suite.ui.generic.template 1.148.0 → 1.148.2

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.
Files changed (81) hide show
  1. package/package.json +1 -1
  2. package/src/sap/suite/ui/generic/template/.library +1 -1
  3. package/src/sap/suite/ui/generic/template/AnalyticalListPage/i18n/i18n_fr_CA.properties +2 -2
  4. package/src/sap/suite/ui/generic/template/AnalyticalListPage/manifest.json +1 -1
  5. package/src/sap/suite/ui/generic/template/Canvas/manifest.json +1 -1
  6. package/src/sap/suite/ui/generic/template/ListReport/controller/ControllerImplementation.js +6 -3
  7. package/src/sap/suite/ui/generic/template/ListReport/i18n/i18n_fr.properties +1 -1
  8. package/src/sap/suite/ui/generic/template/ListReport/manifest.json +1 -1
  9. package/src/sap/suite/ui/generic/template/ObjectPage/manifest.json +1 -1
  10. package/src/sap/suite/ui/generic/template/ObjectPage/view/fragments/Actions.fragment.xml +13 -13
  11. package/src/sap/suite/ui/generic/template/QuickCreate/manifest.json +1 -1
  12. package/src/sap/suite/ui/generic/template/QuickView/manifest.json +1 -1
  13. package/src/sap/suite/ui/generic/template/genericUtilities/controlStateWrapperFactory/SmartFilterBarWrapper.js +20 -1
  14. package/src/sap/suite/ui/generic/template/lib/AppComponent.js +1 -1
  15. package/src/sap/suite/ui/generic/template/lib/CRUDManager.js +0 -7
  16. package/src/sap/suite/ui/generic/template/lib/CommonEventHandlers.js +57 -6
  17. package/src/sap/suite/ui/generic/template/lib/ai/EasyFill/EasyFillAIOrchestrator.js +216 -0
  18. package/src/sap/suite/ui/generic/template/lib/ai/EasyFill/EasyFillApplyHandler.js +325 -0
  19. package/src/sap/suite/ui/generic/template/lib/ai/EasyFill/EasyFillDialogController.js +358 -0
  20. package/src/sap/suite/ui/generic/template/lib/ai/EasyFill/EasyFillFieldCollector.js +848 -0
  21. package/src/sap/suite/ui/generic/template/lib/ai/EasyFill/EasyFillFormRenderer.js +292 -0
  22. package/src/sap/suite/ui/generic/template/lib/ai/EasyFill/EasyFillHandler.js +652 -1071
  23. package/src/sap/suite/ui/generic/template/lib/ai/EasyFill/EasyFillTableRenderer.js +940 -0
  24. package/src/sap/suite/ui/generic/template/lib/ai/EasyFill/fragments/EasyFillDialog.fragment.xml +41 -26
  25. package/src/sap/suite/ui/generic/template/lib/ai/EasyFilterBarHandler.js +90 -73
  26. package/src/sap/suite/ui/generic/template/lib/i18n/i18n.properties +35 -8
  27. package/src/sap/suite/ui/generic/template/lib/i18n/i18n_ar.properties +29 -7
  28. package/src/sap/suite/ui/generic/template/lib/i18n/i18n_bg.properties +29 -7
  29. package/src/sap/suite/ui/generic/template/lib/i18n/i18n_ca.properties +29 -7
  30. package/src/sap/suite/ui/generic/template/lib/i18n/i18n_cnr.properties +29 -7
  31. package/src/sap/suite/ui/generic/template/lib/i18n/i18n_cs.properties +29 -7
  32. package/src/sap/suite/ui/generic/template/lib/i18n/i18n_cy.properties +29 -7
  33. package/src/sap/suite/ui/generic/template/lib/i18n/i18n_da.properties +29 -7
  34. package/src/sap/suite/ui/generic/template/lib/i18n/i18n_de.properties +29 -7
  35. package/src/sap/suite/ui/generic/template/lib/i18n/i18n_el.properties +29 -7
  36. package/src/sap/suite/ui/generic/template/lib/i18n/i18n_en.properties +29 -7
  37. package/src/sap/suite/ui/generic/template/lib/i18n/i18n_en_GB.properties +29 -7
  38. package/src/sap/suite/ui/generic/template/lib/i18n/i18n_en_US_saprigi.properties +29 -7
  39. package/src/sap/suite/ui/generic/template/lib/i18n/i18n_es.properties +29 -7
  40. package/src/sap/suite/ui/generic/template/lib/i18n/i18n_es_MX.properties +29 -7
  41. package/src/sap/suite/ui/generic/template/lib/i18n/i18n_et.properties +29 -7
  42. package/src/sap/suite/ui/generic/template/lib/i18n/i18n_fi.properties +29 -7
  43. package/src/sap/suite/ui/generic/template/lib/i18n/i18n_fr.properties +30 -8
  44. package/src/sap/suite/ui/generic/template/lib/i18n/i18n_fr_CA.properties +31 -9
  45. package/src/sap/suite/ui/generic/template/lib/i18n/i18n_hi.properties +29 -7
  46. package/src/sap/suite/ui/generic/template/lib/i18n/i18n_hr.properties +30 -8
  47. package/src/sap/suite/ui/generic/template/lib/i18n/i18n_hu.properties +29 -7
  48. package/src/sap/suite/ui/generic/template/lib/i18n/i18n_id.properties +29 -7
  49. package/src/sap/suite/ui/generic/template/lib/i18n/i18n_it.properties +29 -7
  50. package/src/sap/suite/ui/generic/template/lib/i18n/i18n_iw.properties +29 -7
  51. package/src/sap/suite/ui/generic/template/lib/i18n/i18n_ja.properties +29 -7
  52. package/src/sap/suite/ui/generic/template/lib/i18n/i18n_kk.properties +29 -7
  53. package/src/sap/suite/ui/generic/template/lib/i18n/i18n_ko.properties +29 -7
  54. package/src/sap/suite/ui/generic/template/lib/i18n/i18n_lt.properties +29 -7
  55. package/src/sap/suite/ui/generic/template/lib/i18n/i18n_lv.properties +29 -7
  56. package/src/sap/suite/ui/generic/template/lib/i18n/i18n_mk.properties +29 -7
  57. package/src/sap/suite/ui/generic/template/lib/i18n/i18n_ms.properties +29 -7
  58. package/src/sap/suite/ui/generic/template/lib/i18n/i18n_nl.properties +30 -8
  59. package/src/sap/suite/ui/generic/template/lib/i18n/i18n_no.properties +29 -7
  60. package/src/sap/suite/ui/generic/template/lib/i18n/i18n_pl.properties +29 -7
  61. package/src/sap/suite/ui/generic/template/lib/i18n/i18n_pt.properties +29 -7
  62. package/src/sap/suite/ui/generic/template/lib/i18n/i18n_pt_PT.properties +29 -7
  63. package/src/sap/suite/ui/generic/template/lib/i18n/i18n_ro.properties +29 -7
  64. package/src/sap/suite/ui/generic/template/lib/i18n/i18n_ru.properties +29 -7
  65. package/src/sap/suite/ui/generic/template/lib/i18n/i18n_sh.properties +29 -7
  66. package/src/sap/suite/ui/generic/template/lib/i18n/i18n_sk.properties +29 -7
  67. package/src/sap/suite/ui/generic/template/lib/i18n/i18n_sl.properties +29 -7
  68. package/src/sap/suite/ui/generic/template/lib/i18n/i18n_sr.properties +29 -7
  69. package/src/sap/suite/ui/generic/template/lib/i18n/i18n_sv.properties +29 -7
  70. package/src/sap/suite/ui/generic/template/lib/i18n/i18n_th.properties +29 -7
  71. package/src/sap/suite/ui/generic/template/lib/i18n/i18n_tr.properties +29 -7
  72. package/src/sap/suite/ui/generic/template/lib/i18n/i18n_uk.properties +29 -7
  73. package/src/sap/suite/ui/generic/template/lib/i18n/i18n_vi.properties +29 -7
  74. package/src/sap/suite/ui/generic/template/lib/i18n/i18n_zh_CN.properties +29 -7
  75. package/src/sap/suite/ui/generic/template/lib/i18n/i18n_zh_TW.properties +29 -7
  76. package/src/sap/suite/ui/generic/template/lib/navigation/NavigationController.js +1 -1
  77. package/src/sap/suite/ui/generic/template/library.js +1 -1
  78. package/src/sap/suite/ui/generic/template/themes/base/ObjectPage.less +30 -4
  79. package/src/sap/suite/ui/generic/template/lib/ai/EasyFill/ObjectPageSectionHandler.js +0 -282
  80. package/src/sap/suite/ui/generic/template/lib/ai/EasyFill/fragments/EasyFillNonUpdatableSmartForm.fragment.xml +0 -18
  81. package/src/sap/suite/ui/generic/template/lib/ai/EasyFill/fragments/EasyFillUpdatebleSmartForm.fragment.xml +0 -15
@@ -0,0 +1,848 @@
1
+ sap.ui.define([
2
+ "sap/suite/ui/generic/template/genericUtilities/FeLogger",
3
+ "sap/suite/ui/generic/template/genericUtilities/FeError",
4
+ "sap/suite/ui/generic/template/genericUtilities/controlHelper"
5
+ ], function(FeLogger, FeError, controlHelper) {
6
+ "use strict";
7
+
8
+ var sClassName = "lib.ai.EasyFill.EasyFillFieldCollector";
9
+ var oLogger = new FeLogger(sClassName).getLogger();
10
+
11
+ /**
12
+ * EasyFillFieldCollector
13
+ *
14
+ * Responsible for:
15
+ * 1. Building the field mapping payload sent to the LLM (entity set properties + collection/table properties).
16
+ * 2. Determining which LLM-returned fields are updatable vs. non-updatable based on OData metadata annotations.
17
+ * 3. Building the section tree structures (Condensed and ObjectPage views) that drive the dialog's SmartForm layout.
18
+ *
19
+ * This module absorbs the logic previously split between EasyFillHandler.js (field collection functions)
20
+ * and ObjectPageSectionHandler.js (section tree building), which has been deleted.
21
+ *
22
+ * All functions are stateless and receive required context via parameters.
23
+ */
24
+ return {
25
+
26
+ // ─── Field Mapping for LLM ───────────────────────────────────────────────
27
+
28
+ /**
29
+ * Builds the flat property map for the main entity type, used as the LLM input schema.
30
+ * Each entry contains the field label and OData data type.
31
+ *
32
+ * @param {object} oEntityType - OData entity type metadata object
33
+ * @param {object} oObjectPageContext - current ObjectPage binding context (used for visibility checks)
34
+ * @param {function} fnGetLabel - function(oProperty) → string label
35
+ * @returns {object} mPropertyMap keyed by property name
36
+ */
37
+ getEntitySetMap: function(oEntityType, oObjectPageContext, fnGetLabel) {
38
+ var mPropertyMap = {};
39
+ if (!oEntityType || !Array.isArray(oEntityType.property)) {
40
+ oLogger.warning("getEntitySetMap: no entity type or properties found");
41
+ return mPropertyMap;
42
+ }
43
+ oEntityType.property.forEach(function(oProperty) {
44
+ if (this.isVisible(oProperty, oObjectPageContext)) {
45
+ mPropertyMap[oProperty.name] = {
46
+ description: fnGetLabel(oProperty),
47
+ dataType: oProperty.type
48
+ };
49
+ }
50
+ }.bind(this));
51
+ return mPropertyMap;
52
+ },
53
+
54
+ /**
55
+ * Appends collection (SmartTable) field descriptors to an existing property map.
56
+ * Resets the collection context tracking maps before re-populating.
57
+ * Currently supports a single SmartTable (multi-table is a future enhancement).
58
+ *
59
+ * @param {object} mPropertyMap - the map to append to (mutated in place)
60
+ * @param {object} oObjectPage - the ObjectPage control (used to find SmartTables)
61
+ * @param {object} oObjectPageModel - the OData model of the ObjectPage
62
+ * @param {object} oTemplateUtils - FE template utilities (used for PresentationControlHandler)
63
+ * @param {object} mCollectionState - mutable collection state: { mCollectionRowContexts, mCollectionRowContextsByPath, mCollectionProperties, mCollectionEntityTypes, mCollectionEntitySets }
64
+ * @param {function} fnGetLabel - function(oProperty) → string label
65
+ */
66
+ appendCollectionMap: function(mPropertyMap, oObjectPage, oObjectPageModel, oTemplateUtils, mCollectionState, fnGetLabel) {
67
+ // Reset collection tracking before re-populating
68
+ mCollectionState.mCollectionRowContexts = Object.create(null);
69
+ mCollectionState.mCollectionRowContextsByPath = Object.create(null);
70
+ mCollectionState.mCollectionProperties = Object.create(null);
71
+ mCollectionState.mCollectionEntityTypes = Object.create(null);
72
+ mCollectionState.mCollectionEntitySets = Object.create(null);
73
+ mCollectionState.mCollectionAllowsCreation = Object.create(null);
74
+
75
+ var aSmartTables = this.getObjectPageSmartTables(oObjectPage);
76
+ if (!aSmartTables.length) {
77
+ oLogger.debug("appendCollectionMap: no SmartTables found on ObjectPage");
78
+ return;
79
+ }
80
+
81
+ var oMetaModel = oObjectPageModel && oObjectPageModel.getMetaModel();
82
+ if (!oMetaModel) {
83
+ oLogger.warning("appendCollectionMap: no MetaModel available");
84
+ return;
85
+ }
86
+
87
+ aSmartTables.forEach(function(oSmartTable) {
88
+ var oHandler = oTemplateUtils.oServices.oPresentationControlHandlerFactory.getPresentationControlHandler(oSmartTable);
89
+ if (!oHandler) {
90
+ return;
91
+ }
92
+
93
+ // Navigation property name is derived from the binding path (e.g. "/to_Items" → "to_Items")
94
+ var sNavProperty = this._extractNavigationPropertyName(oHandler.getBindingPath());
95
+ if (!sNavProperty) {
96
+ oLogger.warning("appendCollectionMap: could not extract nav property from binding path");
97
+ return;
98
+ }
99
+
100
+ var aVisibleProps = (oHandler.findVisibleColumnProperties && oHandler.findVisibleColumnProperties()) || [];
101
+ if (!aVisibleProps.length) {
102
+ return;
103
+ }
104
+
105
+ var aRowContexts = (oHandler.getCurrentContexts && oHandler.getCurrentContexts()) || [];
106
+ var sEntitySetName = oSmartTable.getEntitySet();
107
+ var oEntitySet = oMetaModel.getODataEntitySet(sEntitySetName);
108
+ var oEntityType = oEntitySet && oMetaModel.getODataEntityType(oEntitySet.entityType);
109
+ if (!oEntityType || !oEntityType.property) {
110
+ return;
111
+ }
112
+
113
+ // Build a name→property lookup for this collection entity type
114
+ var mPropertyByName = Object.create(null);
115
+ oEntityType.property.forEach(function(oProperty) {
116
+ mPropertyByName[oProperty.name] = oProperty;
117
+ });
118
+
119
+ // Build item-properties map for visible columns only
120
+ var oItemProperties = {};
121
+ aVisibleProps.forEach(function(sPropName) {
122
+ var oProperty = mPropertyByName[sPropName];
123
+ if (!oProperty) { return; }
124
+ oItemProperties[sPropName] = {
125
+ description: fnGetLabel(oProperty) || sPropName,
126
+ dataType: oProperty.type
127
+ };
128
+ });
129
+
130
+ if (!Object.keys(oItemProperties).length) {
131
+ return;
132
+ }
133
+
134
+ // Build row data array for LLM context (current table rows with their values)
135
+ var aCurrentItems = [];
136
+ var mRowsByPath = Object.create(null);
137
+ aRowContexts.forEach(function(oContext, iIndex) {
138
+ if (!oContext || !oContext.getObject) { return; }
139
+ var sContextPath = oContext.getPath && oContext.getPath();
140
+ var oRowData = { _rowIndex: iIndex };
141
+ if (typeof sContextPath === "string") {
142
+ oRowData._contextPath = sContextPath;
143
+ mRowsByPath[sContextPath] = { context: oContext, rowIndex: iIndex };
144
+ }
145
+ Object.keys(oItemProperties).forEach(function(sPropName) {
146
+ var vValue = oContext.getProperty(sPropName);
147
+ if (typeof vValue !== "object") {
148
+ oRowData[sPropName] = vValue;
149
+ }
150
+ });
151
+ aCurrentItems.push(oRowData);
152
+ });
153
+
154
+ // Populate collection state tracking maps
155
+ mCollectionState.mCollectionRowContexts[sNavProperty] = aRowContexts;
156
+ mCollectionState.mCollectionRowContextsByPath[sNavProperty] = mRowsByPath;
157
+ mCollectionState.mCollectionProperties[sNavProperty] = mPropertyByName;
158
+ mCollectionState.mCollectionEntityTypes[sNavProperty] = oEntityType;
159
+ mCollectionState.mCollectionEntitySets[sNavProperty] = oEntitySet;
160
+ mCollectionState.mCollectionAllowsCreation[sNavProperty] = this.resolveTableAllowsCreation(oSmartTable);
161
+
162
+ // Add collection entry to the LLM field mapping
163
+ mPropertyMap[sNavProperty] = {
164
+ description: typeof oSmartTable.getHeader() === "string" ? oSmartTable.getHeader() : sNavProperty,
165
+ dataType: "Collection",
166
+ isCollection: true,
167
+ itemProperties: oItemProperties,
168
+ currentItems: aCurrentItems,
169
+ entitySet: sEntitySetName,
170
+ section: typeof oSmartTable.getHeader() === "string" ? oSmartTable.getHeader() : sNavProperty
171
+ };
172
+ }.bind(this));
173
+ },
174
+
175
+ // ─── Updatable / Non-Updatable Classification ────────────────────────────
176
+
177
+ /**
178
+ * Splits LLM-returned simple fields into updatable and non-updatable buckets
179
+ * based on OData metadata annotations (sap:updatable, FieldControl, etc.).
180
+ *
181
+ * @param {object} oAiCallResult - flat map of { fieldName: aiValue } from the LLM
182
+ * @param {object} oEntityType - OData entity type metadata
183
+ * @param {object} oObjectPageContext - current ObjectPage binding context
184
+ * @param {object} oEntitySet - OData entity set metadata (for entity-set-level sap:updatable checks)
185
+ * @returns {{ updatableFields: object, nonUpdatableFields: object }}
186
+ */
187
+ getUpdatableAndNonUpdatableFields: function(oAiCallResult, oEntityType, oObjectPageContext, oEntitySet) {
188
+ var updatableFields = {};
189
+ var nonUpdatableFields = {};
190
+
191
+ if (!oEntityType || !Array.isArray(oEntityType.property)) {
192
+ oLogger.warning("getUpdatableAndNonUpdatableFields: no entity type available");
193
+ return { updatableFields: updatableFields, nonUpdatableFields: nonUpdatableFields };
194
+ }
195
+
196
+ Object.keys(oAiCallResult).forEach(function(sKey) {
197
+ var oProperty = oEntityType.property.find(function(oP) { return oP.name === sKey; });
198
+ if (!oProperty) {
199
+ // Property not found in metadata → treat as non-updatable
200
+ nonUpdatableFields[sKey] = oAiCallResult[sKey];
201
+ return;
202
+ }
203
+ if (this.isEditable(oProperty, oObjectPageContext, oEntitySet)) {
204
+ updatableFields[sKey] = oAiCallResult[sKey];
205
+ } else {
206
+ nonUpdatableFields[sKey] = oAiCallResult[sKey];
207
+ }
208
+ }.bind(this));
209
+
210
+ return { updatableFields: updatableFields, nonUpdatableFields: nonUpdatableFields };
211
+ },
212
+
213
+ // ─── Visibility & Editability Checks ─────────────────────────────────────
214
+
215
+ /**
216
+ * Checks whether a property should be included in the LLM field mapping.
217
+ * Evaluates UI.Hidden, FieldControl (Hidden), and sap:field-control annotations.
218
+ *
219
+ * @param {object} oProperty - OData property metadata
220
+ * @param {object} oObjectPageContext - current ObjectPage binding context
221
+ * @returns {boolean}
222
+ */
223
+ isVisible: function(oProperty, oObjectPageContext) {
224
+ // Check "com.sap.vocabularies.Common.v1.FieldControl" for Hidden enum
225
+ var oAnnotation = oProperty["com.sap.vocabularies.Common.v1.FieldControl"];
226
+ if (oAnnotation && oAnnotation["EnumMember"]) {
227
+ return oAnnotation["EnumMember"] !== "com.sap.vocabularies.Common.v1.FieldControlType/Hidden";
228
+ }
229
+
230
+ // Check "com.sap.vocabularies.UI.v1.Hidden" annotation
231
+ var oHiddenAnnotation = oProperty["com.sap.vocabularies.UI.v1.Hidden"];
232
+ if (oHiddenAnnotation) {
233
+ if (oHiddenAnnotation["Bool"]) {
234
+ return oHiddenAnnotation["Bool"] !== "true";
235
+ }
236
+ if (oHiddenAnnotation["Path"] && oObjectPageContext) {
237
+ return !oObjectPageContext.getObject()[oHiddenAnnotation["Path"]];
238
+ }
239
+ // <Annotation Term="UI.Hidden"/> with no value → hidden
240
+ return false;
241
+ }
242
+
243
+ // Check "sap:field-control" attribute (value 0 = hidden)
244
+ var sFieldControl = oProperty["sap:field-control"];
245
+ if (sFieldControl && oObjectPageContext) {
246
+ var iFieldControlValue = oObjectPageContext.getObject()[sFieldControl];
247
+ return iFieldControlValue !== 0;
248
+ }
249
+
250
+ // No restriction → visible by default
251
+ return true;
252
+ },
253
+
254
+ /**
255
+ * Checks whether a property is editable based on OData annotations.
256
+ * Uses the current ObjectPage context object for dynamic field-control checks.
257
+ *
258
+ * Priority: entity-set-level sap:updatable → property sap:updatable → FieldControl (EnumMember) → sap:field-control (dynamic value)
259
+ *
260
+ * Field control values:
261
+ * 0 = Hidden, 1 = ReadOnly, 3 = Editable, 7 = Mandatory (editable)
262
+ *
263
+ * @param {object} oProperty - OData property metadata
264
+ * @param {object} oObjectPageContext - current ObjectPage binding context
265
+ * @param {object} oEntitySet - optional OData entity set metadata (for entity-set-level checks)
266
+ * @returns {boolean}
267
+ */
268
+ isEditable: function(oProperty, oObjectPageContext, oEntitySet) {
269
+ var oContextObject = oObjectPageContext && oObjectPageContext.getObject && oObjectPageContext.getObject();
270
+ return this.isEditableByContextObject(oProperty, oContextObject, oEntitySet);
271
+ },
272
+
273
+ /**
274
+ * Checks editability given a plain context data object (not a binding context).
275
+ * Used for both entity-level and collection-row-level editability checks.
276
+ *
277
+ * @param {object} oProperty - OData property metadata
278
+ * @param {object} oContextObject - plain JS object of the current row/entity data
279
+ * @param {object} oEntitySet - optional OData entity set metadata (for entity-set-level checks)
280
+ * @returns {boolean}
281
+ */
282
+ isEditableByContextObject: function(oProperty, oContextObject, oEntitySet) {
283
+ if (!oProperty) {
284
+ return false;
285
+ }
286
+
287
+ // Check entity-set-level sap:updatable (entire entity set is read-only)
288
+ if (oEntitySet) {
289
+ var sEntitySetUpdatable = oEntitySet["sap:updatable"];
290
+ if (sEntitySetUpdatable === "false") {
291
+ return false;
292
+ }
293
+ // sap:updatable-path: resolved against the context object (boolean property)
294
+ var sEntitySetUpdatablePath = oEntitySet["sap:updatable-path"];
295
+ if (sEntitySetUpdatablePath && oContextObject) {
296
+ var bUpdatableFromPath = oContextObject[sEntitySetUpdatablePath];
297
+ if (bUpdatableFromPath === false || bUpdatableFromPath === "false") {
298
+ return false;
299
+ }
300
+ }
301
+ }
302
+
303
+ // Check sap:updatable — static annotation on the property
304
+ var sUpdatable = oProperty["sap:updatable"];
305
+ if (sUpdatable) {
306
+ return sUpdatable !== "false";
307
+ }
308
+
309
+ // Check "com.sap.vocabularies.Common.v1.FieldControl" enum member
310
+ var oAnnotation = oProperty["com.sap.vocabularies.Common.v1.FieldControl"];
311
+ if (oAnnotation && oAnnotation["EnumMember"]) {
312
+ return oAnnotation["EnumMember"] !== "com.sap.vocabularies.Common.v1.FieldControlType/ReadOnly";
313
+ }
314
+
315
+ // Check "sap:field-control" dynamic path (value 1 = read-only)
316
+ var sFieldControl = oProperty["sap:field-control"];
317
+ if (sFieldControl) {
318
+ var iFieldControlValue = oContextObject && oContextObject[sFieldControl];
319
+ return iFieldControlValue !== 1;
320
+ }
321
+
322
+ // No restriction → editable by default
323
+ return true;
324
+ },
325
+
326
+ // ─── Section Tree Building (from ObjectPageSectionHandler) ───────────────
327
+
328
+ /**
329
+ * Builds a flat section tree for the Condensed view mode.
330
+ * Creates two sections: "Filled Fields" (updatable) and "Unfilled Fields" (non-updatable).
331
+ *
332
+ * @param {object} oLLMResult - flat map of { fieldName: value } from the LLM
333
+ * @param {object} oUpdatableFields - flat map of updatable fields only
334
+ * @param {object} oNonUpdatableFields - flat map of non-updatable fields only
335
+ * @returns {Array} section tree array
336
+ */
337
+ getSectionsForCondensedView: function(oLLMResult, oUpdatableFields, oNonUpdatableFields) {
338
+ var aUpdatableKeys = Object.keys(oUpdatableFields || {});
339
+ var aNonUpdatableKeys = Object.keys(oNonUpdatableFields || {});
340
+ var aSections = [];
341
+ // Section 1: Filled Fields (updatable)
342
+ if (aUpdatableKeys.length > 0) {
343
+ aSections.push({
344
+ title: "{@i18n>EASY_FILL_FILLED_FIELDS}",
345
+ fields: [],
346
+ children: [{
347
+ title: "",
348
+ fields: aUpdatableKeys,
349
+ id: this._generateId("filledFields", aUpdatableKeys)
350
+ }]
351
+ });
352
+ }
353
+
354
+ // Section 2: Unfilled Fields (non-updatable/read-only)
355
+ if (aNonUpdatableKeys.length > 0) {
356
+ aSections.push({
357
+ title: "{@i18n>EASY_FILL_UNFILLED_FIELDS}",
358
+ fields: [],
359
+ children: [{
360
+ title: "",
361
+ fields: aNonUpdatableKeys,
362
+ id: this._generateId("unfilledFields", aNonUpdatableKeys)
363
+ }]
364
+ });
365
+ }
366
+
367
+ // Fallback: if no split provided, use old behavior
368
+ if (aSections.length === 0) {
369
+ return this._transformToTreeExpanded(oLLMResult);
370
+ }
371
+
372
+ return aSections;
373
+ },
374
+
375
+ /**
376
+ * Builds a structured section tree for the ObjectPage view mode.
377
+ * Mirrors the ObjectPage section/facet structure and filters to only include
378
+ * updatable fields returned by the LLM. Non-updatable (read-only) fields are
379
+ * excluded from the annotation sections and shown exclusively in the
380
+ * "Unfilled Fields" section appended at the end.
381
+ *
382
+ * @param {object} oEntityType - OData entity type metadata
383
+ * @param {object} oLLMResult - flat map of { fieldName: value } from the LLM
384
+ * @param {object} oRb - i18n resource bundle
385
+ * @param {object} oNonUpdatableFields - flat map of non-updatable fields only (optional)
386
+ * @returns {Array} filtered section tree array
387
+ */
388
+ getSectionsForObjectPageView: function(oEntityType, oLLMResult, oRb, oNonUpdatableFields) {
389
+ var aSections = [];
390
+ aSections = aSections
391
+ .concat(this._getSectionsForHeader(oEntityType, oRb))
392
+ .concat(this._getSectionsForObjectPageFacets(oEntityType));
393
+ var aTree = this._transformToTree(aSections);
394
+
395
+ // Annotation sections must only show updatable fields.
396
+ // Derive the updatable-only subset by removing non-updatable keys from oLLMResult
397
+ // so non-updatable fields do not appear in both annotation sections AND Unfilled Fields.
398
+ var aNonUpdatableSet = Object.keys(oNonUpdatableFields || {});
399
+ var oUpdatableResult = Object.keys(oLLMResult).reduce(function(oAcc, sKey) {
400
+ if (aNonUpdatableSet.indexOf(sKey) === -1) {
401
+ oAcc[sKey] = oLLMResult[sKey];
402
+ }
403
+ return oAcc;
404
+ }, {});
405
+ var aFilteredTree = this._filterTreeByLLMResult(aTree, oUpdatableResult);
406
+ aFilteredTree = this._flattenTreeForObjectPageDialog(aFilteredTree);
407
+
408
+ // "Unfilled Fields" contains only non-updatable (read-only) fields.
409
+ // Editable fields that have no annotation section are silently dropped here —
410
+ // they already appear in their respective annotation-matched sections above.
411
+ var aNonUpdatableKeys = Object.keys(oNonUpdatableFields || {});
412
+ if (aNonUpdatableKeys.length > 0) {
413
+ aFilteredTree.push({
414
+ title: "{@i18n>EASY_FILL_UNFILLED_FIELDS}",
415
+ fields: [],
416
+ children: [{
417
+ title: "",
418
+ fields: aNonUpdatableKeys,
419
+ id: this._generateId("unfilledFields", aNonUpdatableKeys)
420
+ }]
421
+ });
422
+ }
423
+
424
+ return aFilteredTree;
425
+ },
426
+
427
+ // ─── SmartTable Utilities ─────────────────────────────────────────────────
428
+
429
+ /**
430
+ * Finds all SmartTable instances on the ObjectPage that are of a supported type
431
+ * (Responsive Table or UI5 Grid Table).
432
+ *
433
+ * @param {object} oObjectPage - the ObjectPage control
434
+ * @returns {Array} array of sap.ui.comp.smarttable.SmartTable controls
435
+ */
436
+ getObjectPageSmartTables: function(oObjectPage) {
437
+ if (!oObjectPage || !oObjectPage.findAggregatedObjects) {
438
+ return [];
439
+ }
440
+ // Note: Currently restricted to Responsive and Grid tables.
441
+ // Custom SmartTables are also included — future work should filter
442
+ // to only annotation-defined tables to avoid false positives.
443
+ return oObjectPage.findAggregatedObjects(true, function(oControl) {
444
+ var bIsSmartTable = controlHelper.isSmartTable(oControl);
445
+ return bIsSmartTable && (
446
+ oControl.getTable().isA("sap.m.Table") ||
447
+ oControl.getTable().isA("sap.ui.table.Table")
448
+ );
449
+ }) || [];
450
+ },
451
+
452
+ /**
453
+ * Validates whether a given entity set name corresponds to a known SmartTable on the page.
454
+ *
455
+ * @param {string} sEntitySet - entity set name to validate
456
+ * @param {object} oObjectPage - the ObjectPage control
457
+ * @returns {{ isValid: boolean, smartTable: object|undefined }}
458
+ */
459
+ isValidSmartTable: function(sEntitySet, oObjectPage) {
460
+ var aSmartTables = this.getObjectPageSmartTables(oObjectPage);
461
+ var oSmartTable = aSmartTables.find(function(oTable) {
462
+ return oTable.getEntitySet() === sEntitySet;
463
+ });
464
+ return {
465
+ isValid: !!oSmartTable,
466
+ smartTable: oSmartTable
467
+ };
468
+ },
469
+
470
+ /**
471
+ * Determines whether a SmartTable supports creation of new rows via inline mode.
472
+ * Supports "inline" createMode (with an enabled Create toolbar button) and
473
+ * "creationRows" / "creationRowsHiddenInEditMode" createMode (InlineCreationRows).
474
+ *
475
+ * @param {object} oSmartTable - sap.ui.comp.smarttable.SmartTable control
476
+ * @returns {boolean} true if new rows can be created for this table
477
+ */
478
+ resolveTableAllowsCreation: function(oSmartTable) {
479
+ var sCreateMode = oSmartTable.data("creationMode");
480
+ var bIsInlineCreationRows = sCreateMode === "creationRows" || sCreateMode === "creationRowsHiddenInEditMode";
481
+ var bIsInline = sCreateMode === "inline";
482
+
483
+ if (bIsInlineCreationRows) {
484
+ return true;
485
+ }
486
+
487
+ if (bIsInline) {
488
+ // Inline create requires the Create toolbar button to be enabled
489
+ var oTable = oSmartTable.getTable && oSmartTable.getTable();
490
+ if (!oTable) {
491
+ return false;
492
+ }
493
+ var oToolbar = oSmartTable.getCustomToolbar && oSmartTable.getCustomToolbar();
494
+ if (!oToolbar) {
495
+ return false;
496
+ }
497
+ var aButtons = oToolbar.getContent && oToolbar.getContent();
498
+ if (!Array.isArray(aButtons)) {
499
+ return false;
500
+ }
501
+ var oCreateButton = aButtons.find(function(oControl) {
502
+ return oControl.isA && oControl.isA("sap.m.Button") &&
503
+ typeof oControl.getId === "function" &&
504
+ oControl.getId().indexOf("add") >= 0 &&
505
+ oControl.getEnabled && oControl.getEnabled();
506
+ });
507
+ return !!oCreateButton;
508
+ }
509
+
510
+ return false;
511
+ },
512
+
513
+ // ─── Private Helpers ─────────────────────────────────────────────────────
514
+
515
+ /**
516
+ * Extracts the navigation property name from an OData binding path.
517
+ * E.g. "/to_Items" → "to_Items", "/SalesOrderSet/to_Items" → "to_Items"
518
+ *
519
+ * @param {string} sBindingPath
520
+ * @returns {string|null}
521
+ */
522
+ _extractNavigationPropertyName: function(sBindingPath) {
523
+ if (!sBindingPath || typeof sBindingPath !== "string") {
524
+ return null;
525
+ }
526
+ var aParts = sBindingPath.split("/").filter(Boolean);
527
+ return aParts.length ? aParts[aParts.length - 1] : null;
528
+ },
529
+
530
+ /**
531
+ * Builds the "condensed" tree: all LLM fields in a single flat group.
532
+ * @private
533
+ */
534
+ _transformToTreeExpanded: function(oLLMRes) {
535
+ return [
536
+ {
537
+ title: "",
538
+ fields: [],
539
+ children: [{
540
+ title: "",
541
+ fields: Object.keys(oLLMRes),
542
+ id: this._generateId("", Object.keys(oLLMRes))
543
+ }]
544
+ }
545
+ ];
546
+ },
547
+
548
+ /**
549
+ * Builds the "ObjectPage" section tree from a flat array of { title[], property } entries.
550
+ * Groups entries by their title path hierarchy.
551
+ * @private
552
+ */
553
+ _transformToTree: function(data) {
554
+ var rootMap = {};
555
+ var seen = new Set();
556
+ var that = this;
557
+
558
+ data.forEach(function(entry) {
559
+ var title = entry.title;
560
+ var property = entry.property;
561
+ if (seen.has(property)) { return; }
562
+ seen.add(property);
563
+
564
+ var currentMap = rootMap;
565
+ var sTitlePath = "";
566
+
567
+ title.forEach(function(level, index) {
568
+ var isLast = index === title.length - 1;
569
+ sTitlePath = sTitlePath ? (sTitlePath + "_" + level) : level;
570
+
571
+ if (!currentMap[level]) {
572
+ currentMap[level] = {
573
+ title: level,
574
+ titlePath: sTitlePath,
575
+ fields: [],
576
+ _childrenMap: {}
577
+ };
578
+ }
579
+
580
+ if (isLast) {
581
+ currentMap[level].fields.push(property);
582
+ } else {
583
+ currentMap = currentMap[level]._childrenMap;
584
+ }
585
+ });
586
+ });
587
+
588
+ function convertToArray(oMap) {
589
+ return Object.values(oMap).map(function(oNode) {
590
+ return {
591
+ title: oNode.title,
592
+ id: that._generateId(oNode.titlePath, oNode.fields),
593
+ fields: oNode.fields,
594
+ children: oNode._childrenMap ? convertToArray(oNode._childrenMap) : []
595
+ };
596
+ });
597
+ }
598
+
599
+ return convertToArray(rootMap);
600
+ },
601
+
602
+ /**
603
+ * Generates a stable DOM-safe ID for a SmartForm section based on its title path and fields.
604
+ * @private
605
+ */
606
+ _generateId: function(sTitlePath, aFields) {
607
+ var sSanitizedTitle = sTitlePath
608
+ .replace(/\s+/g, "_")
609
+ .replace(/[^a-zA-Z0-9_]/g, "")
610
+ .toLowerCase();
611
+ var sFieldsPart = aFields
612
+ .join("_")
613
+ .replace(/[^a-zA-Z0-9_]/g, "")
614
+ .toLowerCase();
615
+ return "EasyFillSmartForm_" + sSanitizedTitle + (sFieldsPart ? "_" + sFieldsPart : "");
616
+ },
617
+
618
+ /**
619
+ * Filters a section tree to only include nodes that contain at least one LLM-returned field.
620
+ * Fields with no matching annotation section are silently dropped; the caller is responsible
621
+ * for surfacing them elsewhere (e.g. the "unfilledFields" section for non-updatable fields).
622
+ *
623
+ * @param {Array} aTree - section tree
624
+ * @param {object} oLLMResult - flat map of all LLM fields
625
+ * @returns {Array} filtered tree
626
+ * @private
627
+ */
628
+ _filterTreeByLLMResult: function(aTree, oLLMResult) {
629
+ var aRequestedKeys = Object.keys(oLLMResult);
630
+ var that = this;
631
+
632
+ function filterNode(oNode, sParentPath) {
633
+ var sTitlePath = sParentPath ? (sParentPath + "_" + oNode.title) : oNode.title;
634
+
635
+ var aFilteredFields = oNode.fields.filter(function(sField) {
636
+ return aRequestedKeys.includes(sField);
637
+ });
638
+
639
+ var aFilteredChildren = oNode.children
640
+ .map(function(oChild) { return filterNode(oChild, sTitlePath); })
641
+ .filter(function(oChild) { return oChild.fields.length > 0 || oChild.children.length > 0; });
642
+
643
+ return {
644
+ title: oNode.title,
645
+ id: that._generateId(sTitlePath, aFilteredFields),
646
+ fields: aFilteredFields,
647
+ children: aFilteredChildren
648
+ };
649
+ }
650
+
651
+ return aTree
652
+ .map(function(oNode) { return filterNode(oNode, ""); })
653
+ .filter(function(oNode) { return oNode.fields.length > 0 || oNode.children.length > 0; });
654
+ },
655
+
656
+ /**
657
+ * Flattens nested ObjectPage section trees to one group level expected by the dialog fragment.
658
+ *
659
+ * EasyFill dialog binds SmartForm groups only to section.children (one level deep).
660
+ * If annotation facets produce deeper nesting, section titles can render while fields remain
661
+ * invisible because grandchildren are never bound as groups.
662
+ *
663
+ * @param {Array} aTree - filtered section tree
664
+ * @returns {Array} dialog-compatible tree with one-level children per section
665
+ * @private
666
+ */
667
+ _flattenTreeForObjectPageDialog: function(aTree) {
668
+ var that = this;
669
+
670
+ function collectRenderableGroups(oNode, sParentPath) {
671
+ var sTitlePath = sParentPath ? (sParentPath + "_" + oNode.title) : oNode.title;
672
+ var aGroups = [];
673
+
674
+ if (Array.isArray(oNode.fields) && oNode.fields.length > 0) {
675
+ aGroups.push({
676
+ title: oNode.title,
677
+ id: that._generateId(sTitlePath, oNode.fields),
678
+ fields: oNode.fields,
679
+ children: []
680
+ });
681
+ }
682
+
683
+ if (Array.isArray(oNode.children) && oNode.children.length > 0) {
684
+ oNode.children.forEach(function(oChild) {
685
+ aGroups = aGroups.concat(collectRenderableGroups(oChild, sTitlePath));
686
+ });
687
+ }
688
+
689
+ return aGroups;
690
+ }
691
+
692
+ return (aTree || []).map(function(oSection) {
693
+ var sSectionPath = oSection.title || "";
694
+ return {
695
+ title: oSection.title,
696
+ id: oSection.id,
697
+ fields: [],
698
+ children: collectRenderableGroups(oSection, sSectionPath)
699
+ };
700
+ }).filter(function(oSection) {
701
+ return Array.isArray(oSection.children) && oSection.children.length > 0;
702
+ });
703
+ },
704
+
705
+ /**
706
+ * Extracts section entries from ObjectPage header annotations (HeaderInfo + HeaderFacets).
707
+ * @private
708
+ */
709
+ _getSectionsForHeader: function(oEntityType, oRb) {
710
+ var aRes = [];
711
+ var oHeaderInfo = oEntityType["com.sap.vocabularies.UI.v1.HeaderInfo"];
712
+ var aHeaderFacets = oEntityType["com.sap.vocabularies.UI.v1.HeaderFacets"];
713
+
714
+ if (oHeaderInfo && Object.keys(oHeaderInfo).length > 0) {
715
+ var aKeys = Object.keys(oHeaderInfo).filter(function(sKey) {
716
+ return oHeaderInfo[sKey].RecordType === "com.sap.vocabularies.UI.v1.DataField";
717
+ });
718
+ aKeys.forEach(function(sKey) {
719
+ aRes.push({
720
+ title: [oRb.getText("EASY_FILL_SECTION_HEADER"), oRb.getText("EASY_FILL_SECTION_HEADER")],
721
+ property: oHeaderInfo[sKey].Value.Path
722
+ });
723
+ });
724
+ }
725
+
726
+ if (aHeaderFacets && aHeaderFacets.length > 0) {
727
+ var aSections = this._extractSectionInfoFromReferenceFacet(oEntityType, aHeaderFacets);
728
+ aSections.forEach(function(oSection) {
729
+ oSection.title = [oRb.getText("EASY_FILL_SECTION_HEADER"), oRb.getText("EASY_FILL_SECTION_HEADER")];
730
+ });
731
+ aRes.push.apply(aRes, aSections);
732
+ }
733
+
734
+ return aRes;
735
+ },
736
+
737
+ /**
738
+ * Extracts section entries from the main ObjectPage facet annotations (UI.Facets).
739
+ * @private
740
+ */
741
+ _getSectionsForObjectPageFacets: function(oEntityType) {
742
+ var aSectionFacets = oEntityType["com.sap.vocabularies.UI.v1.Facets"];
743
+ if (aSectionFacets && aSectionFacets.length > 0) {
744
+ return this._extractFacetInfoRecursively(oEntityType, aSectionFacets, []);
745
+ }
746
+ return [];
747
+ },
748
+
749
+ /**
750
+ * Recursively walks CollectionFacets and ReferenceFacets to extract { title, property } pairs.
751
+ * @private
752
+ */
753
+ _extractFacetInfoRecursively: function(oEntityType, aFacets, aTitle) {
754
+ var aRes = [];
755
+ aFacets.forEach(function(oFacet) {
756
+ if (oFacet.RecordType === "com.sap.vocabularies.UI.v1.CollectionFacet") {
757
+ var aInnerFacets = oFacet.Facets;
758
+ if (aInnerFacets && aInnerFacets.length > 0) {
759
+ var sLabel = oFacet.Label && oFacet.Label.String ? oFacet.Label.String : null;
760
+ aRes = aRes.concat(this._extractFacetInfoRecursively(
761
+ oEntityType,
762
+ aInnerFacets,
763
+ sLabel ? aTitle.concat(sLabel) : aTitle
764
+ ));
765
+ }
766
+ } else {
767
+ aRes = aRes.concat(this._extractSectionInfoFromReferenceFacet(oEntityType, [oFacet], aTitle));
768
+ }
769
+ }.bind(this));
770
+ return aRes;
771
+ },
772
+
773
+ /**
774
+ * Extracts { title[], property } entries from a set of ReferenceFacets.
775
+ * Skips IntentBasedNavigation, ForAction, and ForAnnotation record types (not supported).
776
+ * @private
777
+ */
778
+ _extractSectionInfoFromReferenceFacet: function(oEntityType, aFacets, aText) {
779
+ aText = aText || [];
780
+ var aRes = [];
781
+ aFacets.forEach(function(oFacet) {
782
+ // Skip unsupported annotation types
783
+ if (!this._isSupportedRecordType(oFacet.RecordType)) {
784
+ return;
785
+ }
786
+
787
+ var aTempText = aText.slice();
788
+ var sAnnotationPath = oFacet.Target.AnnotationPath.substring(1);
789
+ var oAnnotation = oEntityType[sAnnotationPath];
790
+ var sTitle = null;
791
+
792
+ // Title resolution priority: Facet.Label → Annotation.Label → Facet.Title → Annotation.Title
793
+ if (oFacet && oFacet.Label) {
794
+ sTitle = oFacet.Label.String;
795
+ } else if (oAnnotation && oAnnotation.Label) {
796
+ sTitle = oAnnotation.Label.String;
797
+ } else if (oFacet && oFacet.Title) {
798
+ sTitle = oFacet.Title.String;
799
+ } else if (oAnnotation && oAnnotation.Title) {
800
+ sTitle = oAnnotation.Title.String;
801
+ }
802
+
803
+ if (sTitle) {
804
+ aTempText.push(sTitle);
805
+ }
806
+
807
+ if (oAnnotation) {
808
+ if (oAnnotation.Data) {
809
+ // FieldGroup / Form — iterate Data array
810
+ oAnnotation.Data.forEach(function(oData) {
811
+ if (!this._isSupportedRecordType(oData.RecordType)) { return; }
812
+ if (!oData.Value || !oData.Value.Path) { return; }
813
+ aRes.push({ title: aTempText, property: oData.Value.Path });
814
+ }.bind(this));
815
+ } else if (Array.isArray(oAnnotation)) {
816
+ // LineItem — iterate annotation array directly
817
+ oAnnotation.forEach(function(oAnno) {
818
+ if (!this._isSupportedRecordType(oAnno.RecordType)) { return; }
819
+ if (!oAnno.Value || !oAnno.Value.Path) { return; }
820
+ aRes.push({ title: aTempText, property: oAnno.Value.Path });
821
+ }.bind(this));
822
+ } else if (oAnnotation.Value && oAnnotation.Value.Path) {
823
+ // Single-value annotation (e.g. DataPoint)
824
+ aRes.push({ title: aTempText, property: oAnnotation.Value.Path });
825
+ }
826
+ }
827
+ }.bind(this));
828
+ return aRes;
829
+ },
830
+
831
+ /**
832
+ * Returns false for record types that EasyFill does not support rendering.
833
+ * Currently excludes: IntentBasedNavigation, ForAnnotation, ForAction.
834
+ * Handles both fully-qualified names (com.sap.vocabularies.UI.v1.*) and
835
+ * alias form (UI.*) that OData metadata can return depending on the service.
836
+ * @private
837
+ */
838
+ _isSupportedRecordType: function(sRecordType) {
839
+ if (!sRecordType) { return false; }
840
+ var sNormalized = sRecordType.replace("com.sap.vocabularies.UI.v1.", "UI.");
841
+ return sNormalized !== "UI.DataFieldForIntentBasedNavigation" &&
842
+ sNormalized !== "UI.DataFieldForAnnotation" &&
843
+ sNormalized !== "UI.DataFieldForAction" &&
844
+ sNormalized !== "UI.DataFieldWithNavigationPath" &&
845
+ sNormalized !== "UI.DataFieldWithIntentBasedNavigation";
846
+ }
847
+ };
848
+ });