@sapui5/sap.suite.ui.generic.template 1.148.0 → 1.148.1
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.
- package/package.json +1 -1
- package/src/sap/suite/ui/generic/template/.library +1 -1
- package/src/sap/suite/ui/generic/template/AnalyticalListPage/i18n/i18n_fr_CA.properties +2 -2
- package/src/sap/suite/ui/generic/template/AnalyticalListPage/manifest.json +1 -1
- package/src/sap/suite/ui/generic/template/Canvas/manifest.json +1 -1
- package/src/sap/suite/ui/generic/template/ListReport/i18n/i18n_fr.properties +1 -1
- package/src/sap/suite/ui/generic/template/ListReport/manifest.json +1 -1
- package/src/sap/suite/ui/generic/template/ObjectPage/manifest.json +1 -1
- package/src/sap/suite/ui/generic/template/ObjectPage/view/fragments/Actions.fragment.xml +13 -13
- package/src/sap/suite/ui/generic/template/QuickCreate/manifest.json +1 -1
- package/src/sap/suite/ui/generic/template/QuickView/manifest.json +1 -1
- package/src/sap/suite/ui/generic/template/lib/AppComponent.js +1 -1
- package/src/sap/suite/ui/generic/template/lib/CommonEventHandlers.js +57 -6
- package/src/sap/suite/ui/generic/template/lib/ai/EasyFill/EasyFillAIOrchestrator.js +185 -0
- package/src/sap/suite/ui/generic/template/lib/ai/EasyFill/EasyFillApplyHandler.js +476 -0
- package/src/sap/suite/ui/generic/template/lib/ai/EasyFill/EasyFillDialogController.js +311 -0
- package/src/sap/suite/ui/generic/template/lib/ai/EasyFill/EasyFillFieldCollector.js +669 -0
- package/src/sap/suite/ui/generic/template/lib/ai/EasyFill/EasyFillFormRenderer.js +217 -0
- package/src/sap/suite/ui/generic/template/lib/ai/EasyFill/EasyFillHandler.js +323 -1120
- package/src/sap/suite/ui/generic/template/lib/ai/EasyFill/EasyFillTableRenderer.js +491 -0
- package/src/sap/suite/ui/generic/template/lib/ai/EasyFill/fragments/EasyFillDialog.fragment.xml +14 -10
- package/src/sap/suite/ui/generic/template/lib/i18n/i18n.properties +11 -8
- package/src/sap/suite/ui/generic/template/lib/i18n/i18n_ar.properties +14 -0
- package/src/sap/suite/ui/generic/template/lib/i18n/i18n_bg.properties +14 -0
- package/src/sap/suite/ui/generic/template/lib/i18n/i18n_ca.properties +14 -0
- package/src/sap/suite/ui/generic/template/lib/i18n/i18n_cnr.properties +14 -0
- package/src/sap/suite/ui/generic/template/lib/i18n/i18n_cs.properties +14 -0
- package/src/sap/suite/ui/generic/template/lib/i18n/i18n_cy.properties +14 -0
- package/src/sap/suite/ui/generic/template/lib/i18n/i18n_da.properties +14 -0
- package/src/sap/suite/ui/generic/template/lib/i18n/i18n_de.properties +14 -0
- package/src/sap/suite/ui/generic/template/lib/i18n/i18n_el.properties +14 -0
- package/src/sap/suite/ui/generic/template/lib/i18n/i18n_en.properties +14 -0
- package/src/sap/suite/ui/generic/template/lib/i18n/i18n_en_GB.properties +14 -0
- package/src/sap/suite/ui/generic/template/lib/i18n/i18n_en_US_saprigi.properties +14 -0
- package/src/sap/suite/ui/generic/template/lib/i18n/i18n_es.properties +14 -0
- package/src/sap/suite/ui/generic/template/lib/i18n/i18n_es_MX.properties +14 -0
- package/src/sap/suite/ui/generic/template/lib/i18n/i18n_et.properties +14 -0
- package/src/sap/suite/ui/generic/template/lib/i18n/i18n_fi.properties +14 -0
- package/src/sap/suite/ui/generic/template/lib/i18n/i18n_fr.properties +15 -1
- package/src/sap/suite/ui/generic/template/lib/i18n/i18n_fr_CA.properties +15 -1
- package/src/sap/suite/ui/generic/template/lib/i18n/i18n_hi.properties +14 -0
- package/src/sap/suite/ui/generic/template/lib/i18n/i18n_hr.properties +15 -1
- package/src/sap/suite/ui/generic/template/lib/i18n/i18n_hu.properties +14 -0
- package/src/sap/suite/ui/generic/template/lib/i18n/i18n_id.properties +14 -0
- package/src/sap/suite/ui/generic/template/lib/i18n/i18n_it.properties +14 -0
- package/src/sap/suite/ui/generic/template/lib/i18n/i18n_iw.properties +14 -0
- package/src/sap/suite/ui/generic/template/lib/i18n/i18n_ja.properties +14 -0
- package/src/sap/suite/ui/generic/template/lib/i18n/i18n_kk.properties +14 -0
- package/src/sap/suite/ui/generic/template/lib/i18n/i18n_ko.properties +14 -0
- package/src/sap/suite/ui/generic/template/lib/i18n/i18n_lt.properties +14 -0
- package/src/sap/suite/ui/generic/template/lib/i18n/i18n_lv.properties +14 -0
- package/src/sap/suite/ui/generic/template/lib/i18n/i18n_mk.properties +14 -0
- package/src/sap/suite/ui/generic/template/lib/i18n/i18n_ms.properties +14 -0
- package/src/sap/suite/ui/generic/template/lib/i18n/i18n_nl.properties +15 -1
- package/src/sap/suite/ui/generic/template/lib/i18n/i18n_no.properties +14 -0
- package/src/sap/suite/ui/generic/template/lib/i18n/i18n_pl.properties +14 -0
- package/src/sap/suite/ui/generic/template/lib/i18n/i18n_pt.properties +14 -0
- package/src/sap/suite/ui/generic/template/lib/i18n/i18n_pt_PT.properties +14 -0
- package/src/sap/suite/ui/generic/template/lib/i18n/i18n_ro.properties +14 -0
- package/src/sap/suite/ui/generic/template/lib/i18n/i18n_ru.properties +14 -0
- package/src/sap/suite/ui/generic/template/lib/i18n/i18n_sh.properties +14 -0
- package/src/sap/suite/ui/generic/template/lib/i18n/i18n_sk.properties +14 -0
- package/src/sap/suite/ui/generic/template/lib/i18n/i18n_sl.properties +14 -0
- package/src/sap/suite/ui/generic/template/lib/i18n/i18n_sr.properties +14 -0
- package/src/sap/suite/ui/generic/template/lib/i18n/i18n_sv.properties +14 -0
- package/src/sap/suite/ui/generic/template/lib/i18n/i18n_th.properties +14 -0
- package/src/sap/suite/ui/generic/template/lib/i18n/i18n_tr.properties +14 -0
- package/src/sap/suite/ui/generic/template/lib/i18n/i18n_uk.properties +14 -0
- package/src/sap/suite/ui/generic/template/lib/i18n/i18n_vi.properties +14 -0
- package/src/sap/suite/ui/generic/template/lib/i18n/i18n_zh_CN.properties +14 -0
- package/src/sap/suite/ui/generic/template/lib/i18n/i18n_zh_TW.properties +14 -0
- package/src/sap/suite/ui/generic/template/lib/navigation/NavigationController.js +1 -1
- package/src/sap/suite/ui/generic/template/library.js +1 -1
- package/src/sap/suite/ui/generic/template/themes/base/ObjectPage.less +8 -3
- package/src/sap/suite/ui/generic/template/lib/ai/EasyFill/ObjectPageSectionHandler.js +0 -282
- package/src/sap/suite/ui/generic/template/lib/ai/EasyFill/fragments/EasyFillNonUpdatableSmartForm.fragment.xml +0 -18
- package/src/sap/suite/ui/generic/template/lib/ai/EasyFill/fragments/EasyFillUpdatebleSmartForm.fragment.xml +0 -15
|
@@ -0,0 +1,669 @@
|
|
|
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 }
|
|
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
|
+
|
|
73
|
+
var aSmartTables = this.getObjectPageSmartTables(oObjectPage);
|
|
74
|
+
if (!aSmartTables.length) {
|
|
75
|
+
oLogger.debug("appendCollectionMap: no SmartTables found on ObjectPage");
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
var oMetaModel = oObjectPageModel && oObjectPageModel.getMetaModel();
|
|
80
|
+
if (!oMetaModel) {
|
|
81
|
+
oLogger.warning("appendCollectionMap: no MetaModel available");
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
aSmartTables.forEach(function(oSmartTable) {
|
|
86
|
+
var oHandler = oTemplateUtils.oServices.oPresentationControlHandlerFactory.getPresentationControlHandler(oSmartTable);
|
|
87
|
+
if (!oHandler) {
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Navigation property name is derived from the binding path (e.g. "/to_Items" → "to_Items")
|
|
92
|
+
var sNavProperty = this._extractNavigationPropertyName(oHandler.getBindingPath());
|
|
93
|
+
if (!sNavProperty) {
|
|
94
|
+
oLogger.warning("appendCollectionMap: could not extract nav property from binding path");
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
var aVisibleProps = (oHandler.findVisibleColumnProperties && oHandler.findVisibleColumnProperties()) || [];
|
|
99
|
+
if (!aVisibleProps.length) {
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
var aRowContexts = (oHandler.getCurrentContexts && oHandler.getCurrentContexts()) || [];
|
|
104
|
+
var sEntitySetName = oSmartTable.getEntitySet();
|
|
105
|
+
var oEntitySet = oMetaModel.getODataEntitySet(sEntitySetName);
|
|
106
|
+
var oEntityType = oEntitySet && oMetaModel.getODataEntityType(oEntitySet.entityType);
|
|
107
|
+
if (!oEntityType || !oEntityType.property) {
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Build a name→property lookup for this collection entity type
|
|
112
|
+
var mPropertyByName = Object.create(null);
|
|
113
|
+
oEntityType.property.forEach(function(oProperty) {
|
|
114
|
+
mPropertyByName[oProperty.name] = oProperty;
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
// Build item-properties map for visible columns only
|
|
118
|
+
var oItemProperties = {};
|
|
119
|
+
aVisibleProps.forEach(function(sPropName) {
|
|
120
|
+
var oProperty = mPropertyByName[sPropName];
|
|
121
|
+
if (!oProperty) { return; }
|
|
122
|
+
oItemProperties[sPropName] = {
|
|
123
|
+
description: fnGetLabel(oProperty) || sPropName,
|
|
124
|
+
dataType: oProperty.type
|
|
125
|
+
};
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
if (!Object.keys(oItemProperties).length) {
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Build row data array for LLM context (current table rows with their values)
|
|
133
|
+
var aCurrentItems = [];
|
|
134
|
+
var mRowsByPath = Object.create(null);
|
|
135
|
+
aRowContexts.forEach(function(oContext, iIndex) {
|
|
136
|
+
if (!oContext || !oContext.getObject) { return; }
|
|
137
|
+
var sContextPath = oContext.getPath && oContext.getPath();
|
|
138
|
+
var oRowData = { _rowIndex: iIndex };
|
|
139
|
+
if (typeof sContextPath === "string") {
|
|
140
|
+
oRowData._contextPath = sContextPath;
|
|
141
|
+
mRowsByPath[sContextPath] = { context: oContext, rowIndex: iIndex };
|
|
142
|
+
}
|
|
143
|
+
Object.keys(oItemProperties).forEach(function(sPropName) {
|
|
144
|
+
var vValue = oContext.getProperty(sPropName);
|
|
145
|
+
if (typeof vValue !== "object") {
|
|
146
|
+
oRowData[sPropName] = vValue;
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
aCurrentItems.push(oRowData);
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
// Populate collection state tracking maps
|
|
153
|
+
mCollectionState.mCollectionRowContexts[sNavProperty] = aRowContexts;
|
|
154
|
+
mCollectionState.mCollectionRowContextsByPath[sNavProperty] = mRowsByPath;
|
|
155
|
+
mCollectionState.mCollectionProperties[sNavProperty] = mPropertyByName;
|
|
156
|
+
mCollectionState.mCollectionEntityTypes[sNavProperty] = oEntityType;
|
|
157
|
+
|
|
158
|
+
// Add collection entry to the LLM field mapping
|
|
159
|
+
mPropertyMap[sNavProperty] = {
|
|
160
|
+
description: typeof oSmartTable.getHeader() === "string" ? oSmartTable.getHeader() : sNavProperty,
|
|
161
|
+
dataType: "Collection",
|
|
162
|
+
isCollection: true,
|
|
163
|
+
itemProperties: oItemProperties,
|
|
164
|
+
currentItems: aCurrentItems,
|
|
165
|
+
entitySet: sEntitySetName,
|
|
166
|
+
section: typeof oSmartTable.getHeader() === "string" ? oSmartTable.getHeader() : sNavProperty
|
|
167
|
+
};
|
|
168
|
+
}.bind(this));
|
|
169
|
+
},
|
|
170
|
+
|
|
171
|
+
// ─── Updatable / Non-Updatable Classification ────────────────────────────
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Splits LLM-returned simple fields into updatable and non-updatable buckets
|
|
175
|
+
* based on OData metadata annotations (sap:updatable, FieldControl, etc.).
|
|
176
|
+
*
|
|
177
|
+
* @param {object} oAiCallResult - flat map of { fieldName: aiValue } from the LLM
|
|
178
|
+
* @param {object} oEntityType - OData entity type metadata
|
|
179
|
+
* @param {object} oObjectPageContext - current ObjectPage binding context
|
|
180
|
+
* @returns {{ updatableFields: object, nonUpdatableFields: object }}
|
|
181
|
+
*/
|
|
182
|
+
getUpdatableAndNonUpdatableFields: function(oAiCallResult, oEntityType, oObjectPageContext) {
|
|
183
|
+
var updatableFields = {};
|
|
184
|
+
var nonUpdatableFields = {};
|
|
185
|
+
|
|
186
|
+
if (!oEntityType || !Array.isArray(oEntityType.property)) {
|
|
187
|
+
oLogger.warning("getUpdatableAndNonUpdatableFields: no entity type available");
|
|
188
|
+
return { updatableFields: updatableFields, nonUpdatableFields: nonUpdatableFields };
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
Object.keys(oAiCallResult).forEach(function(sKey) {
|
|
192
|
+
var oProperty = oEntityType.property.find(function(oP) { return oP.name === sKey; });
|
|
193
|
+
if (!oProperty) {
|
|
194
|
+
// Property not found in metadata → treat as non-updatable
|
|
195
|
+
nonUpdatableFields[sKey] = oAiCallResult[sKey];
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
if (this.isEditable(oProperty, oObjectPageContext)) {
|
|
199
|
+
updatableFields[sKey] = oAiCallResult[sKey];
|
|
200
|
+
} else {
|
|
201
|
+
nonUpdatableFields[sKey] = oAiCallResult[sKey];
|
|
202
|
+
}
|
|
203
|
+
}.bind(this));
|
|
204
|
+
|
|
205
|
+
return { updatableFields: updatableFields, nonUpdatableFields: nonUpdatableFields };
|
|
206
|
+
},
|
|
207
|
+
|
|
208
|
+
// ─── Visibility & Editability Checks ─────────────────────────────────────
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Checks whether a property should be included in the LLM field mapping.
|
|
212
|
+
* Evaluates UI.Hidden, FieldControl (Hidden), and sap:field-control annotations.
|
|
213
|
+
*
|
|
214
|
+
* @param {object} oProperty - OData property metadata
|
|
215
|
+
* @param {object} oObjectPageContext - current ObjectPage binding context
|
|
216
|
+
* @returns {boolean}
|
|
217
|
+
*/
|
|
218
|
+
isVisible: function(oProperty, oObjectPageContext) {
|
|
219
|
+
// Check "com.sap.vocabularies.Common.v1.FieldControl" for Hidden enum
|
|
220
|
+
var oAnnotation = oProperty["com.sap.vocabularies.Common.v1.FieldControl"];
|
|
221
|
+
if (oAnnotation && oAnnotation["EnumMember"]) {
|
|
222
|
+
return oAnnotation["EnumMember"] !== "com.sap.vocabularies.Common.v1.FieldControlType/Hidden";
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// Check "com.sap.vocabularies.UI.v1.Hidden" annotation
|
|
226
|
+
var oHiddenAnnotation = oProperty["com.sap.vocabularies.UI.v1.Hidden"];
|
|
227
|
+
if (oHiddenAnnotation) {
|
|
228
|
+
if (oHiddenAnnotation["Bool"]) {
|
|
229
|
+
return oHiddenAnnotation["Bool"] !== "true";
|
|
230
|
+
}
|
|
231
|
+
if (oHiddenAnnotation["Path"] && oObjectPageContext) {
|
|
232
|
+
return !oObjectPageContext.getObject()[oHiddenAnnotation["Path"]];
|
|
233
|
+
}
|
|
234
|
+
// <Annotation Term="UI.Hidden"/> with no value → hidden
|
|
235
|
+
return false;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// Check "sap:field-control" attribute (value 0 = hidden)
|
|
239
|
+
var sFieldControl = oProperty["sap:field-control"];
|
|
240
|
+
if (sFieldControl && oObjectPageContext) {
|
|
241
|
+
var iFieldControlValue = oObjectPageContext.getObject()[sFieldControl];
|
|
242
|
+
return iFieldControlValue !== 0;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// No restriction → visible by default
|
|
246
|
+
return true;
|
|
247
|
+
},
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Checks whether a property is editable based on OData annotations.
|
|
251
|
+
* Uses the current ObjectPage context object for dynamic field-control checks.
|
|
252
|
+
*
|
|
253
|
+
* Priority: sap:updatable → FieldControl (EnumMember) → sap:field-control (dynamic value)
|
|
254
|
+
*
|
|
255
|
+
* Field control values:
|
|
256
|
+
* 0 = Hidden, 1 = ReadOnly, 3 = Editable, 7 = Mandatory (editable)
|
|
257
|
+
*
|
|
258
|
+
* @param {object} oProperty - OData property metadata
|
|
259
|
+
* @param {object} oObjectPageContext - current ObjectPage binding context
|
|
260
|
+
* @returns {boolean}
|
|
261
|
+
*/
|
|
262
|
+
isEditable: function(oProperty, oObjectPageContext) {
|
|
263
|
+
var oContextObject = oObjectPageContext && oObjectPageContext.getObject && oObjectPageContext.getObject();
|
|
264
|
+
return this.isEditableByContextObject(oProperty, oContextObject);
|
|
265
|
+
},
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Checks editability given a plain context data object (not a binding context).
|
|
269
|
+
* Used for both entity-level and collection-row-level editability checks.
|
|
270
|
+
*
|
|
271
|
+
* @param {object} oProperty - OData property metadata
|
|
272
|
+
* @param {object} oContextObject - plain JS object of the current row/entity data
|
|
273
|
+
* @returns {boolean}
|
|
274
|
+
*/
|
|
275
|
+
isEditableByContextObject: function(oProperty, oContextObject) {
|
|
276
|
+
if (!oProperty) {
|
|
277
|
+
return false;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// Check sap:updatable — static annotation, highest priority
|
|
281
|
+
var sUpdatable = oProperty["sap:updatable"];
|
|
282
|
+
if (sUpdatable) {
|
|
283
|
+
return sUpdatable !== "false";
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// Check "com.sap.vocabularies.Common.v1.FieldControl" enum member
|
|
287
|
+
var oAnnotation = oProperty["com.sap.vocabularies.Common.v1.FieldControl"];
|
|
288
|
+
if (oAnnotation && oAnnotation["EnumMember"]) {
|
|
289
|
+
return oAnnotation["EnumMember"] !== "com.sap.vocabularies.Common.v1.FieldControlType/ReadOnly";
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// Check "sap:field-control" dynamic path (value 1 = read-only)
|
|
293
|
+
var sFieldControl = oProperty["sap:field-control"];
|
|
294
|
+
if (sFieldControl) {
|
|
295
|
+
var iFieldControlValue = oContextObject && oContextObject[sFieldControl];
|
|
296
|
+
return iFieldControlValue !== 1;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// No restriction → editable by default
|
|
300
|
+
return true;
|
|
301
|
+
},
|
|
302
|
+
|
|
303
|
+
// ─── Section Tree Building (from ObjectPageSectionHandler) ───────────────
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* Builds a flat section tree for the Condensed view mode.
|
|
307
|
+
* All LLM result fields are grouped into a single unnamed section.
|
|
308
|
+
*
|
|
309
|
+
* @param {object} oLLMResult - flat map of { fieldName: value } from the LLM
|
|
310
|
+
* @returns {Array} section tree array
|
|
311
|
+
*/
|
|
312
|
+
getSectionsForCondensedView: function(oLLMResult) {
|
|
313
|
+
return this._transformToTreeExpanded(oLLMResult);
|
|
314
|
+
},
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* Builds a structured section tree for the ObjectPage view mode.
|
|
318
|
+
* Mirrors the ObjectPage section/facet structure and filters to only include
|
|
319
|
+
* fields returned by the LLM.
|
|
320
|
+
*
|
|
321
|
+
* @param {object} oEntityType - OData entity type metadata
|
|
322
|
+
* @param {object} oLLMResult - flat map of { fieldName: value } from the LLM
|
|
323
|
+
* @param {object} oRb - i18n resource bundle
|
|
324
|
+
* @returns {Array} filtered section tree array
|
|
325
|
+
*/
|
|
326
|
+
getSectionsForObjectPageView: function(oEntityType, oLLMResult, oRb) {
|
|
327
|
+
var aSections = [];
|
|
328
|
+
aSections = aSections
|
|
329
|
+
.concat(this._getSectionsForHeader(oEntityType, oRb))
|
|
330
|
+
.concat(this._getSectionsForObjectPageFacets(oEntityType));
|
|
331
|
+
var aTree = this._transformToTree(aSections);
|
|
332
|
+
return this._filterTreeByLLMResult(aTree, oLLMResult);
|
|
333
|
+
},
|
|
334
|
+
|
|
335
|
+
// ─── SmartTable Utilities ─────────────────────────────────────────────────
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* Finds all SmartTable instances on the ObjectPage that are of a supported type
|
|
339
|
+
* (Responsive Table or UI5 Grid Table).
|
|
340
|
+
*
|
|
341
|
+
* @param {object} oObjectPage - the ObjectPage control
|
|
342
|
+
* @returns {Array} array of sap.ui.comp.smarttable.SmartTable controls
|
|
343
|
+
*/
|
|
344
|
+
getObjectPageSmartTables: function(oObjectPage) {
|
|
345
|
+
if (!oObjectPage || !oObjectPage.findAggregatedObjects) {
|
|
346
|
+
return [];
|
|
347
|
+
}
|
|
348
|
+
// Note: Currently restricted to Responsive and Grid tables.
|
|
349
|
+
// Custom SmartTables are also included — future work should filter
|
|
350
|
+
// to only annotation-defined tables to avoid false positives.
|
|
351
|
+
return oObjectPage.findAggregatedObjects(true, function(oControl) {
|
|
352
|
+
var bIsSmartTable = controlHelper.isSmartTable(oControl);
|
|
353
|
+
return bIsSmartTable && (
|
|
354
|
+
oControl.getTable().isA("sap.m.Table") ||
|
|
355
|
+
oControl.getTable().isA("sap.ui.table.Table")
|
|
356
|
+
);
|
|
357
|
+
}) || [];
|
|
358
|
+
},
|
|
359
|
+
|
|
360
|
+
/**
|
|
361
|
+
* Validates whether a given entity set name corresponds to a known SmartTable on the page.
|
|
362
|
+
*
|
|
363
|
+
* @param {string} sEntitySet - entity set name to validate
|
|
364
|
+
* @param {object} oObjectPage - the ObjectPage control
|
|
365
|
+
* @returns {{ isValid: boolean, smartTable: object|undefined }}
|
|
366
|
+
*/
|
|
367
|
+
isValidSmartTable: function(sEntitySet, oObjectPage) {
|
|
368
|
+
var aSmartTables = this.getObjectPageSmartTables(oObjectPage);
|
|
369
|
+
var oSmartTable = aSmartTables.find(function(oTable) {
|
|
370
|
+
return oTable.getEntitySet() === sEntitySet;
|
|
371
|
+
});
|
|
372
|
+
return {
|
|
373
|
+
isValid: !!oSmartTable,
|
|
374
|
+
smartTable: oSmartTable
|
|
375
|
+
};
|
|
376
|
+
},
|
|
377
|
+
|
|
378
|
+
// ─── Private Helpers ─────────────────────────────────────────────────────
|
|
379
|
+
|
|
380
|
+
/**
|
|
381
|
+
* Extracts the navigation property name from an OData binding path.
|
|
382
|
+
* E.g. "/to_Items" → "to_Items", "/SalesOrderSet/to_Items" → "to_Items"
|
|
383
|
+
*
|
|
384
|
+
* @param {string} sBindingPath
|
|
385
|
+
* @returns {string|null}
|
|
386
|
+
*/
|
|
387
|
+
_extractNavigationPropertyName: function(sBindingPath) {
|
|
388
|
+
if (!sBindingPath || typeof sBindingPath !== "string") {
|
|
389
|
+
return null;
|
|
390
|
+
}
|
|
391
|
+
var aParts = sBindingPath.split("/").filter(Boolean);
|
|
392
|
+
return aParts.length ? aParts[aParts.length - 1] : null;
|
|
393
|
+
},
|
|
394
|
+
|
|
395
|
+
/**
|
|
396
|
+
* Builds the "condensed" tree: all LLM fields in a single flat group.
|
|
397
|
+
* @private
|
|
398
|
+
*/
|
|
399
|
+
_transformToTreeExpanded: function(oLLMRes) {
|
|
400
|
+
return [
|
|
401
|
+
{
|
|
402
|
+
title: "",
|
|
403
|
+
fields: [],
|
|
404
|
+
children: [{
|
|
405
|
+
title: "",
|
|
406
|
+
fields: Object.keys(oLLMRes),
|
|
407
|
+
id: this._generateId("", Object.keys(oLLMRes))
|
|
408
|
+
}]
|
|
409
|
+
}
|
|
410
|
+
];
|
|
411
|
+
},
|
|
412
|
+
|
|
413
|
+
/**
|
|
414
|
+
* Builds the "ObjectPage" section tree from a flat array of { title[], property } entries.
|
|
415
|
+
* Groups entries by their title path hierarchy.
|
|
416
|
+
* @private
|
|
417
|
+
*/
|
|
418
|
+
_transformToTree: function(data) {
|
|
419
|
+
var rootMap = {};
|
|
420
|
+
var seen = new Set();
|
|
421
|
+
var that = this;
|
|
422
|
+
|
|
423
|
+
data.forEach(function(entry) {
|
|
424
|
+
var title = entry.title;
|
|
425
|
+
var property = entry.property;
|
|
426
|
+
if (seen.has(property)) { return; }
|
|
427
|
+
seen.add(property);
|
|
428
|
+
|
|
429
|
+
var currentMap = rootMap;
|
|
430
|
+
var sTitlePath = "";
|
|
431
|
+
|
|
432
|
+
title.forEach(function(level, index) {
|
|
433
|
+
var isLast = index === title.length - 1;
|
|
434
|
+
sTitlePath = sTitlePath ? (sTitlePath + "_" + level) : level;
|
|
435
|
+
|
|
436
|
+
if (!currentMap[level]) {
|
|
437
|
+
currentMap[level] = {
|
|
438
|
+
title: level,
|
|
439
|
+
titlePath: sTitlePath,
|
|
440
|
+
fields: [],
|
|
441
|
+
_childrenMap: {}
|
|
442
|
+
};
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
if (isLast) {
|
|
446
|
+
currentMap[level].fields.push(property);
|
|
447
|
+
} else {
|
|
448
|
+
currentMap = currentMap[level]._childrenMap;
|
|
449
|
+
}
|
|
450
|
+
});
|
|
451
|
+
});
|
|
452
|
+
|
|
453
|
+
function convertToArray(oMap) {
|
|
454
|
+
return Object.values(oMap).map(function(oNode) {
|
|
455
|
+
return {
|
|
456
|
+
title: oNode.title,
|
|
457
|
+
id: that._generateId(oNode.titlePath, oNode.fields),
|
|
458
|
+
fields: oNode.fields,
|
|
459
|
+
children: oNode._childrenMap ? convertToArray(oNode._childrenMap) : []
|
|
460
|
+
};
|
|
461
|
+
});
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
return convertToArray(rootMap);
|
|
465
|
+
},
|
|
466
|
+
|
|
467
|
+
/**
|
|
468
|
+
* Generates a stable DOM-safe ID for a SmartForm section based on its title path and fields.
|
|
469
|
+
* @private
|
|
470
|
+
*/
|
|
471
|
+
_generateId: function(sTitlePath, aFields) {
|
|
472
|
+
var sSanitizedTitle = sTitlePath
|
|
473
|
+
.replace(/\s+/g, "_")
|
|
474
|
+
.replace(/[^a-zA-Z0-9_]/g, "")
|
|
475
|
+
.toLowerCase();
|
|
476
|
+
var sFieldsPart = aFields
|
|
477
|
+
.join("_")
|
|
478
|
+
.replace(/[^a-zA-Z0-9_]/g, "")
|
|
479
|
+
.toLowerCase();
|
|
480
|
+
return "EasyFillSmartForm_" + sSanitizedTitle + (sFieldsPart ? "_" + sFieldsPart : "");
|
|
481
|
+
},
|
|
482
|
+
|
|
483
|
+
/**
|
|
484
|
+
* Filters a section tree to only include nodes that contain at least one LLM-returned field.
|
|
485
|
+
* Fields not matched to any section are grouped into an "unableToFill" catch-all node.
|
|
486
|
+
* @private
|
|
487
|
+
*/
|
|
488
|
+
_filterTreeByLLMResult: function(aTree, oLLMResult) {
|
|
489
|
+
var aRequestedKeys = Object.keys(oLLMResult);
|
|
490
|
+
var aMatchedKeys = new Set();
|
|
491
|
+
var that = this;
|
|
492
|
+
|
|
493
|
+
function filterNode(oNode, sParentPath) {
|
|
494
|
+
var sTitlePath = sParentPath ? (sParentPath + "_" + oNode.title) : oNode.title;
|
|
495
|
+
|
|
496
|
+
var aFilteredFields = oNode.fields.filter(function(sField) {
|
|
497
|
+
if (aRequestedKeys.includes(sField)) {
|
|
498
|
+
aMatchedKeys.add(sField);
|
|
499
|
+
return true;
|
|
500
|
+
}
|
|
501
|
+
return false;
|
|
502
|
+
});
|
|
503
|
+
|
|
504
|
+
var aFilteredChildren = oNode.children
|
|
505
|
+
.map(function(oChild) { return filterNode(oChild, sTitlePath); })
|
|
506
|
+
.filter(function(oChild) { return oChild.fields.length > 0 || oChild.children.length > 0; });
|
|
507
|
+
|
|
508
|
+
return {
|
|
509
|
+
title: oNode.title,
|
|
510
|
+
id: that._generateId(sTitlePath, aFilteredFields),
|
|
511
|
+
fields: aFilteredFields,
|
|
512
|
+
children: aFilteredChildren
|
|
513
|
+
};
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
var aFilteredTree = aTree
|
|
517
|
+
.map(function(oNode) { return filterNode(oNode, ""); })
|
|
518
|
+
.filter(function(oNode) { return oNode.fields.length > 0 || oNode.children.length > 0; });
|
|
519
|
+
|
|
520
|
+
// Any LLM fields that didn't map to a known section go into a special "unableToFill" node
|
|
521
|
+
var aUnmatchedKeys = aRequestedKeys.filter(function(sKey) { return !aMatchedKeys.has(sKey); });
|
|
522
|
+
if (aUnmatchedKeys.length > 0) {
|
|
523
|
+
aFilteredTree.push({
|
|
524
|
+
title: "unableToFill",
|
|
525
|
+
id: this._generateId("unableToFill", aUnmatchedKeys),
|
|
526
|
+
fields: aUnmatchedKeys,
|
|
527
|
+
children: []
|
|
528
|
+
});
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
return aFilteredTree;
|
|
532
|
+
},
|
|
533
|
+
|
|
534
|
+
/**
|
|
535
|
+
* Extracts section entries from ObjectPage header annotations (HeaderInfo + HeaderFacets).
|
|
536
|
+
* @private
|
|
537
|
+
*/
|
|
538
|
+
_getSectionsForHeader: function(oEntityType, oRb) {
|
|
539
|
+
var aRes = [];
|
|
540
|
+
var oHeaderInfo = oEntityType["com.sap.vocabularies.UI.v1.HeaderInfo"];
|
|
541
|
+
var aHeaderFacets = oEntityType["com.sap.vocabularies.UI.v1.HeaderFacets"];
|
|
542
|
+
|
|
543
|
+
if (oHeaderInfo && Object.keys(oHeaderInfo).length > 0) {
|
|
544
|
+
var aKeys = Object.keys(oHeaderInfo).filter(function(sKey) {
|
|
545
|
+
return oHeaderInfo[sKey].RecordType === "com.sap.vocabularies.UI.v1.DataField";
|
|
546
|
+
});
|
|
547
|
+
aKeys.forEach(function(sKey) {
|
|
548
|
+
aRes.push({
|
|
549
|
+
title: [oRb.getText("EASY_FILL_SECTION_HEADER"), oRb.getText("EASY_FILL_SECTION_HEADER")],
|
|
550
|
+
property: oHeaderInfo[sKey].Value.Path
|
|
551
|
+
});
|
|
552
|
+
});
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
if (aHeaderFacets && aHeaderFacets.length > 0) {
|
|
556
|
+
var aSections = this._extractSectionInfoFromReferenceFacet(oEntityType, aHeaderFacets);
|
|
557
|
+
aSections.forEach(function(oSection) {
|
|
558
|
+
oSection.title = [oRb.getText("EASY_FILL_SECTION_HEADER"), oRb.getText("EASY_FILL_SECTION_HEADER")];
|
|
559
|
+
});
|
|
560
|
+
aRes.push.apply(aRes, aSections);
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
return aRes;
|
|
564
|
+
},
|
|
565
|
+
|
|
566
|
+
/**
|
|
567
|
+
* Extracts section entries from the main ObjectPage facet annotations (UI.Facets).
|
|
568
|
+
* @private
|
|
569
|
+
*/
|
|
570
|
+
_getSectionsForObjectPageFacets: function(oEntityType) {
|
|
571
|
+
var aSectionFacets = oEntityType["com.sap.vocabularies.UI.v1.Facets"];
|
|
572
|
+
if (aSectionFacets && aSectionFacets.length > 0) {
|
|
573
|
+
return this._extractFacetInfoRecursively(oEntityType, aSectionFacets, []);
|
|
574
|
+
}
|
|
575
|
+
return [];
|
|
576
|
+
},
|
|
577
|
+
|
|
578
|
+
/**
|
|
579
|
+
* Recursively walks CollectionFacets and ReferenceFacets to extract { title, property } pairs.
|
|
580
|
+
* @private
|
|
581
|
+
*/
|
|
582
|
+
_extractFacetInfoRecursively: function(oEntityType, aFacets, aTitle) {
|
|
583
|
+
var aRes = [];
|
|
584
|
+
aFacets.forEach(function(oFacet) {
|
|
585
|
+
if (oFacet.RecordType === "com.sap.vocabularies.UI.v1.CollectionFacet") {
|
|
586
|
+
var aInnerFacets = oFacet.Facets;
|
|
587
|
+
if (aInnerFacets && aInnerFacets.length > 0) {
|
|
588
|
+
var sLabel = oFacet.Label && oFacet.Label.String ? oFacet.Label.String : null;
|
|
589
|
+
aRes = aRes.concat(this._extractFacetInfoRecursively(
|
|
590
|
+
oEntityType,
|
|
591
|
+
aInnerFacets,
|
|
592
|
+
sLabel ? aTitle.concat(sLabel) : aTitle
|
|
593
|
+
));
|
|
594
|
+
}
|
|
595
|
+
} else {
|
|
596
|
+
aRes = aRes.concat(this._extractSectionInfoFromReferenceFacet(oEntityType, [oFacet], aTitle));
|
|
597
|
+
}
|
|
598
|
+
}.bind(this));
|
|
599
|
+
return aRes;
|
|
600
|
+
},
|
|
601
|
+
|
|
602
|
+
/**
|
|
603
|
+
* Extracts { title[], property } entries from a set of ReferenceFacets.
|
|
604
|
+
* Skips IntentBasedNavigation, ForAction, and ForAnnotation record types (not supported).
|
|
605
|
+
* @private
|
|
606
|
+
*/
|
|
607
|
+
_extractSectionInfoFromReferenceFacet: function(oEntityType, aFacets, aText) {
|
|
608
|
+
aText = aText || [];
|
|
609
|
+
var aRes = [];
|
|
610
|
+
aFacets.forEach(function(oFacet) {
|
|
611
|
+
// Skip unsupported annotation types
|
|
612
|
+
if (!this._isSupportedRecordType(oFacet.RecordType)) {
|
|
613
|
+
return;
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
var aTempText = aText.slice();
|
|
617
|
+
var sAnnotationPath = oFacet.Target.AnnotationPath.substring(1);
|
|
618
|
+
var oAnnotation = oEntityType[sAnnotationPath];
|
|
619
|
+
var sTitle = null;
|
|
620
|
+
|
|
621
|
+
// Title resolution priority: Facet.Label → Annotation.Label → Facet.Title → Annotation.Title
|
|
622
|
+
if (oFacet && oFacet.Label) {
|
|
623
|
+
sTitle = oFacet.Label.String;
|
|
624
|
+
} else if (oAnnotation && oAnnotation.Label) {
|
|
625
|
+
sTitle = oAnnotation.Label.String;
|
|
626
|
+
} else if (oFacet && oFacet.Title) {
|
|
627
|
+
sTitle = oFacet.Title.String;
|
|
628
|
+
} else if (oAnnotation && oAnnotation.Title) {
|
|
629
|
+
sTitle = oAnnotation.Title.String;
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
if (sTitle) {
|
|
633
|
+
aTempText.push(sTitle);
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
if (oAnnotation) {
|
|
637
|
+
if (oAnnotation.Data) {
|
|
638
|
+
// FieldGroup / Form — iterate Data array
|
|
639
|
+
oAnnotation.Data.forEach(function(oData) {
|
|
640
|
+
if (!this._isSupportedRecordType(oData.RecordType)) { return; }
|
|
641
|
+
aRes.push({ title: aTempText, property: oData.Value.Path });
|
|
642
|
+
}.bind(this));
|
|
643
|
+
} else if (Array.isArray(oAnnotation)) {
|
|
644
|
+
// LineItem — iterate annotation array directly
|
|
645
|
+
oAnnotation.forEach(function(oAnno) {
|
|
646
|
+
if (!this._isSupportedRecordType(oAnno.RecordType)) { return; }
|
|
647
|
+
aRes.push({ title: aTempText, property: oAnno.Value.Path });
|
|
648
|
+
}.bind(this));
|
|
649
|
+
} else if (oAnnotation.Value && oAnnotation.Value.Path) {
|
|
650
|
+
// Single-value annotation (e.g. DataPoint)
|
|
651
|
+
aRes.push({ title: aTempText, property: oAnnotation.Value.Path });
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
}.bind(this));
|
|
655
|
+
return aRes;
|
|
656
|
+
},
|
|
657
|
+
|
|
658
|
+
/**
|
|
659
|
+
* Returns false for record types that EasyFill does not support rendering.
|
|
660
|
+
* Currently excludes: IntentBasedNavigation, ForAnnotation, ForAction.
|
|
661
|
+
* @private
|
|
662
|
+
*/
|
|
663
|
+
_isSupportedRecordType: function(sRecordType) {
|
|
664
|
+
return sRecordType !== "com.sap.vocabularies.UI.v1.DataFieldForIntentBasedNavigation" &&
|
|
665
|
+
sRecordType !== "com.sap.vocabularies.UI.v1.DataFieldForAnnotation" &&
|
|
666
|
+
sRecordType !== "com.sap.vocabularies.UI.v1.DataFieldForAction";
|
|
667
|
+
}
|
|
668
|
+
};
|
|
669
|
+
});
|