@sapui5/sap.suite.ui.generic.template 1.148.1 → 1.148.3

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 (77) 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/manifest.json +1 -1
  4. package/src/sap/suite/ui/generic/template/Canvas/manifest.json +1 -1
  5. package/src/sap/suite/ui/generic/template/ListReport/controller/ControllerImplementation.js +6 -3
  6. package/src/sap/suite/ui/generic/template/ListReport/manifest.json +1 -1
  7. package/src/sap/suite/ui/generic/template/ObjectPage/manifest.json +1 -1
  8. package/src/sap/suite/ui/generic/template/QuickCreate/manifest.json +1 -1
  9. package/src/sap/suite/ui/generic/template/QuickView/manifest.json +1 -1
  10. package/src/sap/suite/ui/generic/template/genericUtilities/controlStateWrapperFactory/SmartFilterBarWrapper.js +48 -8
  11. package/src/sap/suite/ui/generic/template/lib/AppComponent.js +1 -1
  12. package/src/sap/suite/ui/generic/template/lib/CRUDManager.js +7 -8
  13. package/src/sap/suite/ui/generic/template/lib/CommonEventHandlers.js +8 -1
  14. package/src/sap/suite/ui/generic/template/lib/CommonUtils.js +10 -2
  15. package/src/sap/suite/ui/generic/template/lib/MessageUtils.js +4 -3
  16. package/src/sap/suite/ui/generic/template/lib/ai/EasyFill/EasyFillAIOrchestrator.js +43 -12
  17. package/src/sap/suite/ui/generic/template/lib/ai/EasyFill/EasyFillApplyHandler.js +67 -218
  18. package/src/sap/suite/ui/generic/template/lib/ai/EasyFill/EasyFillDialogController.js +55 -8
  19. package/src/sap/suite/ui/generic/template/lib/ai/EasyFill/EasyFillFieldCollector.js +214 -35
  20. package/src/sap/suite/ui/generic/template/lib/ai/EasyFill/EasyFillFormRenderer.js +93 -18
  21. package/src/sap/suite/ui/generic/template/lib/ai/EasyFill/EasyFillHandler.js +426 -48
  22. package/src/sap/suite/ui/generic/template/lib/ai/EasyFill/EasyFillTableRenderer.js +512 -63
  23. package/src/sap/suite/ui/generic/template/lib/ai/EasyFill/fragments/EasyFillDialog.fragment.xml +33 -23
  24. package/src/sap/suite/ui/generic/template/lib/ai/EasyFilterBarHandler.js +90 -73
  25. package/src/sap/suite/ui/generic/template/lib/i18n/i18n.properties +24 -0
  26. package/src/sap/suite/ui/generic/template/lib/i18n/i18n_ar.properties +26 -8
  27. package/src/sap/suite/ui/generic/template/lib/i18n/i18n_bg.properties +26 -8
  28. package/src/sap/suite/ui/generic/template/lib/i18n/i18n_ca.properties +26 -8
  29. package/src/sap/suite/ui/generic/template/lib/i18n/i18n_cnr.properties +26 -8
  30. package/src/sap/suite/ui/generic/template/lib/i18n/i18n_cs.properties +26 -8
  31. package/src/sap/suite/ui/generic/template/lib/i18n/i18n_cy.properties +26 -8
  32. package/src/sap/suite/ui/generic/template/lib/i18n/i18n_da.properties +26 -8
  33. package/src/sap/suite/ui/generic/template/lib/i18n/i18n_de.properties +30 -12
  34. package/src/sap/suite/ui/generic/template/lib/i18n/i18n_el.properties +26 -8
  35. package/src/sap/suite/ui/generic/template/lib/i18n/i18n_en.properties +26 -8
  36. package/src/sap/suite/ui/generic/template/lib/i18n/i18n_en_GB.properties +26 -8
  37. package/src/sap/suite/ui/generic/template/lib/i18n/i18n_en_US_saprigi.properties +26 -8
  38. package/src/sap/suite/ui/generic/template/lib/i18n/i18n_es.properties +26 -8
  39. package/src/sap/suite/ui/generic/template/lib/i18n/i18n_es_MX.properties +26 -8
  40. package/src/sap/suite/ui/generic/template/lib/i18n/i18n_et.properties +26 -8
  41. package/src/sap/suite/ui/generic/template/lib/i18n/i18n_fi.properties +26 -8
  42. package/src/sap/suite/ui/generic/template/lib/i18n/i18n_fr.properties +29 -11
  43. package/src/sap/suite/ui/generic/template/lib/i18n/i18n_fr_CA.properties +27 -9
  44. package/src/sap/suite/ui/generic/template/lib/i18n/i18n_hi.properties +26 -8
  45. package/src/sap/suite/ui/generic/template/lib/i18n/i18n_hr.properties +27 -9
  46. package/src/sap/suite/ui/generic/template/lib/i18n/i18n_hu.properties +26 -8
  47. package/src/sap/suite/ui/generic/template/lib/i18n/i18n_id.properties +26 -8
  48. package/src/sap/suite/ui/generic/template/lib/i18n/i18n_it.properties +26 -8
  49. package/src/sap/suite/ui/generic/template/lib/i18n/i18n_iw.properties +26 -8
  50. package/src/sap/suite/ui/generic/template/lib/i18n/i18n_ja.properties +26 -8
  51. package/src/sap/suite/ui/generic/template/lib/i18n/i18n_kk.properties +26 -8
  52. package/src/sap/suite/ui/generic/template/lib/i18n/i18n_ko.properties +26 -8
  53. package/src/sap/suite/ui/generic/template/lib/i18n/i18n_lt.properties +26 -8
  54. package/src/sap/suite/ui/generic/template/lib/i18n/i18n_lv.properties +26 -8
  55. package/src/sap/suite/ui/generic/template/lib/i18n/i18n_mk.properties +26 -8
  56. package/src/sap/suite/ui/generic/template/lib/i18n/i18n_ms.properties +26 -8
  57. package/src/sap/suite/ui/generic/template/lib/i18n/i18n_nl.properties +26 -8
  58. package/src/sap/suite/ui/generic/template/lib/i18n/i18n_no.properties +26 -8
  59. package/src/sap/suite/ui/generic/template/lib/i18n/i18n_pl.properties +26 -8
  60. package/src/sap/suite/ui/generic/template/lib/i18n/i18n_pt.properties +26 -8
  61. package/src/sap/suite/ui/generic/template/lib/i18n/i18n_pt_PT.properties +26 -8
  62. package/src/sap/suite/ui/generic/template/lib/i18n/i18n_ro.properties +26 -8
  63. package/src/sap/suite/ui/generic/template/lib/i18n/i18n_ru.properties +26 -8
  64. package/src/sap/suite/ui/generic/template/lib/i18n/i18n_sh.properties +26 -8
  65. package/src/sap/suite/ui/generic/template/lib/i18n/i18n_sk.properties +26 -8
  66. package/src/sap/suite/ui/generic/template/lib/i18n/i18n_sl.properties +26 -8
  67. package/src/sap/suite/ui/generic/template/lib/i18n/i18n_sr.properties +26 -8
  68. package/src/sap/suite/ui/generic/template/lib/i18n/i18n_sv.properties +37 -19
  69. package/src/sap/suite/ui/generic/template/lib/i18n/i18n_th.properties +26 -8
  70. package/src/sap/suite/ui/generic/template/lib/i18n/i18n_tr.properties +26 -8
  71. package/src/sap/suite/ui/generic/template/lib/i18n/i18n_uk.properties +26 -8
  72. package/src/sap/suite/ui/generic/template/lib/i18n/i18n_vi.properties +26 -8
  73. package/src/sap/suite/ui/generic/template/lib/i18n/i18n_zh_CN.properties +26 -8
  74. package/src/sap/suite/ui/generic/template/lib/i18n/i18n_zh_TW.properties +26 -8
  75. package/src/sap/suite/ui/generic/template/lib/navigation/NavigationController.js +1 -1
  76. package/src/sap/suite/ui/generic/template/library.js +1 -1
  77. package/src/sap/suite/ui/generic/template/themes/base/ObjectPage.less +22 -1
@@ -60,7 +60,7 @@ sap.ui.define([
60
60
  * @param {object} oObjectPage - the ObjectPage control (used to find SmartTables)
61
61
  * @param {object} oObjectPageModel - the OData model of the ObjectPage
62
62
  * @param {object} oTemplateUtils - FE template utilities (used for PresentationControlHandler)
63
- * @param {object} mCollectionState - mutable collection state: { mCollectionRowContexts, mCollectionRowContextsByPath, mCollectionProperties, mCollectionEntityTypes }
63
+ * @param {object} mCollectionState - mutable collection state: { mCollectionRowContexts, mCollectionRowContextsByPath, mCollectionProperties, mCollectionEntityTypes, mCollectionEntitySets }
64
64
  * @param {function} fnGetLabel - function(oProperty) → string label
65
65
  */
66
66
  appendCollectionMap: function(mPropertyMap, oObjectPage, oObjectPageModel, oTemplateUtils, mCollectionState, fnGetLabel) {
@@ -69,6 +69,8 @@ sap.ui.define([
69
69
  mCollectionState.mCollectionRowContextsByPath = Object.create(null);
70
70
  mCollectionState.mCollectionProperties = Object.create(null);
71
71
  mCollectionState.mCollectionEntityTypes = Object.create(null);
72
+ mCollectionState.mCollectionEntitySets = Object.create(null);
73
+ mCollectionState.mCollectionAllowsCreation = Object.create(null);
72
74
 
73
75
  var aSmartTables = this.getObjectPageSmartTables(oObjectPage);
74
76
  if (!aSmartTables.length) {
@@ -154,6 +156,8 @@ sap.ui.define([
154
156
  mCollectionState.mCollectionRowContextsByPath[sNavProperty] = mRowsByPath;
155
157
  mCollectionState.mCollectionProperties[sNavProperty] = mPropertyByName;
156
158
  mCollectionState.mCollectionEntityTypes[sNavProperty] = oEntityType;
159
+ mCollectionState.mCollectionEntitySets[sNavProperty] = oEntitySet;
160
+ mCollectionState.mCollectionAllowsCreation[sNavProperty] = this.resolveTableAllowsCreation(oSmartTable);
157
161
 
158
162
  // Add collection entry to the LLM field mapping
159
163
  mPropertyMap[sNavProperty] = {
@@ -177,9 +181,10 @@ sap.ui.define([
177
181
  * @param {object} oAiCallResult - flat map of { fieldName: aiValue } from the LLM
178
182
  * @param {object} oEntityType - OData entity type metadata
179
183
  * @param {object} oObjectPageContext - current ObjectPage binding context
184
+ * @param {object} oEntitySet - OData entity set metadata (for entity-set-level sap:updatable checks)
180
185
  * @returns {{ updatableFields: object, nonUpdatableFields: object }}
181
186
  */
182
- getUpdatableAndNonUpdatableFields: function(oAiCallResult, oEntityType, oObjectPageContext) {
187
+ getUpdatableAndNonUpdatableFields: function(oAiCallResult, oEntityType, oObjectPageContext, oEntitySet) {
183
188
  var updatableFields = {};
184
189
  var nonUpdatableFields = {};
185
190
 
@@ -195,7 +200,7 @@ sap.ui.define([
195
200
  nonUpdatableFields[sKey] = oAiCallResult[sKey];
196
201
  return;
197
202
  }
198
- if (this.isEditable(oProperty, oObjectPageContext)) {
203
+ if (this.isEditable(oProperty, oObjectPageContext, oEntitySet)) {
199
204
  updatableFields[sKey] = oAiCallResult[sKey];
200
205
  } else {
201
206
  nonUpdatableFields[sKey] = oAiCallResult[sKey];
@@ -250,18 +255,19 @@ sap.ui.define([
250
255
  * Checks whether a property is editable based on OData annotations.
251
256
  * Uses the current ObjectPage context object for dynamic field-control checks.
252
257
  *
253
- * Priority: sap:updatable → FieldControl (EnumMember) → sap:field-control (dynamic value)
258
+ * Priority: entity-set-level sap:updatable → property sap:updatable → FieldControl (EnumMember) → sap:field-control (dynamic value)
254
259
  *
255
260
  * Field control values:
256
261
  * 0 = Hidden, 1 = ReadOnly, 3 = Editable, 7 = Mandatory (editable)
257
262
  *
258
263
  * @param {object} oProperty - OData property metadata
259
264
  * @param {object} oObjectPageContext - current ObjectPage binding context
265
+ * @param {object} oEntitySet - optional OData entity set metadata (for entity-set-level checks)
260
266
  * @returns {boolean}
261
267
  */
262
- isEditable: function(oProperty, oObjectPageContext) {
268
+ isEditable: function(oProperty, oObjectPageContext, oEntitySet) {
263
269
  var oContextObject = oObjectPageContext && oObjectPageContext.getObject && oObjectPageContext.getObject();
264
- return this.isEditableByContextObject(oProperty, oContextObject);
270
+ return this.isEditableByContextObject(oProperty, oContextObject, oEntitySet);
265
271
  },
266
272
 
267
273
  /**
@@ -270,14 +276,31 @@ sap.ui.define([
270
276
  *
271
277
  * @param {object} oProperty - OData property metadata
272
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)
273
280
  * @returns {boolean}
274
281
  */
275
- isEditableByContextObject: function(oProperty, oContextObject) {
282
+ isEditableByContextObject: function(oProperty, oContextObject, oEntitySet) {
276
283
  if (!oProperty) {
277
284
  return false;
278
285
  }
279
286
 
280
- // Check sap:updatable static annotation, highest priority
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
281
304
  var sUpdatable = oProperty["sap:updatable"];
282
305
  if (sUpdatable) {
283
306
  return sUpdatable !== "false";
@@ -304,32 +327,101 @@ sap.ui.define([
304
327
 
305
328
  /**
306
329
  * Builds a flat section tree for the Condensed view mode.
307
- * All LLM result fields are grouped into a single unnamed section.
330
+ * Creates two sections: "Filled Fields" (updatable) and "Unfilled Fields" (non-updatable).
308
331
  *
309
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
310
335
  * @returns {Array} section tree array
311
336
  */
312
- getSectionsForCondensedView: function(oLLMResult) {
313
- return this._transformToTreeExpanded(oLLMResult);
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;
314
373
  },
315
374
 
316
375
  /**
317
376
  * Builds a structured section tree for the ObjectPage view mode.
318
377
  * Mirrors the ObjectPage section/facet structure and filters to only include
319
- * fields returned by the LLM.
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.
320
381
  *
321
382
  * @param {object} oEntityType - OData entity type metadata
322
383
  * @param {object} oLLMResult - flat map of { fieldName: value } from the LLM
323
384
  * @param {object} oRb - i18n resource bundle
385
+ * @param {object} oNonUpdatableFields - flat map of non-updatable fields only (optional)
324
386
  * @returns {Array} filtered section tree array
325
387
  */
326
- getSectionsForObjectPageView: function(oEntityType, oLLMResult, oRb) {
388
+ getSectionsForObjectPageView: function(oEntityType, oLLMResult, oRb, oNonUpdatableFields) {
327
389
  var aSections = [];
328
390
  aSections = aSections
329
391
  .concat(this._getSectionsForHeader(oEntityType, oRb))
330
392
  .concat(this._getSectionsForObjectPageFacets(oEntityType));
331
393
  var aTree = this._transformToTree(aSections);
332
- return this._filterTreeByLLMResult(aTree, oLLMResult);
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;
333
425
  },
334
426
 
335
427
  // ─── SmartTable Utilities ─────────────────────────────────────────────────
@@ -375,6 +467,49 @@ sap.ui.define([
375
467
  };
376
468
  },
377
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
+
378
513
  // ─── Private Helpers ─────────────────────────────────────────────────────
379
514
 
380
515
  /**
@@ -482,23 +617,23 @@ sap.ui.define([
482
617
 
483
618
  /**
484
619
  * 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.
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
486
626
  * @private
487
627
  */
488
628
  _filterTreeByLLMResult: function(aTree, oLLMResult) {
489
629
  var aRequestedKeys = Object.keys(oLLMResult);
490
- var aMatchedKeys = new Set();
491
630
  var that = this;
492
631
 
493
632
  function filterNode(oNode, sParentPath) {
494
633
  var sTitlePath = sParentPath ? (sParentPath + "_" + oNode.title) : oNode.title;
495
634
 
496
635
  var aFilteredFields = oNode.fields.filter(function(sField) {
497
- if (aRequestedKeys.includes(sField)) {
498
- aMatchedKeys.add(sField);
499
- return true;
500
- }
501
- return false;
636
+ return aRequestedKeys.includes(sField);
502
637
  });
503
638
 
504
639
  var aFilteredChildren = oNode.children
@@ -513,22 +648,58 @@ sap.ui.define([
513
648
  };
514
649
  }
515
650
 
516
- var aFilteredTree = aTree
651
+ return aTree
517
652
  .map(function(oNode) { return filterNode(oNode, ""); })
518
653
  .filter(function(oNode) { return oNode.fields.length > 0 || oNode.children.length > 0; });
654
+ },
519
655
 
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
- });
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;
529
690
  }
530
691
 
531
- return aFilteredTree;
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
+ });
532
703
  },
533
704
 
534
705
  /**
@@ -638,12 +809,14 @@ sap.ui.define([
638
809
  // FieldGroup / Form — iterate Data array
639
810
  oAnnotation.Data.forEach(function(oData) {
640
811
  if (!this._isSupportedRecordType(oData.RecordType)) { return; }
812
+ if (!oData.Value || !oData.Value.Path) { return; }
641
813
  aRes.push({ title: aTempText, property: oData.Value.Path });
642
814
  }.bind(this));
643
815
  } else if (Array.isArray(oAnnotation)) {
644
816
  // LineItem — iterate annotation array directly
645
817
  oAnnotation.forEach(function(oAnno) {
646
818
  if (!this._isSupportedRecordType(oAnno.RecordType)) { return; }
819
+ if (!oAnno.Value || !oAnno.Value.Path) { return; }
647
820
  aRes.push({ title: aTempText, property: oAnno.Value.Path });
648
821
  }.bind(this));
649
822
  } else if (oAnnotation.Value && oAnnotation.Value.Path) {
@@ -658,12 +831,18 @@ sap.ui.define([
658
831
  /**
659
832
  * Returns false for record types that EasyFill does not support rendering.
660
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.
661
836
  * @private
662
837
  */
663
838
  _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";
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";
667
846
  }
668
847
  };
669
848
  });
@@ -47,23 +47,40 @@ sap.ui.define([
47
47
  * @param {object} oLLMData - flat map of { fieldName: aiValue } from the LLM response
48
48
  * @returns {boolean} always returns true (used as a formatter return value in XML)
49
49
  */
50
- populateSmartFields: function(sId, aFields, sEasyFillMode, oController, oTransientContextForEasyFill, oObjectPageContext, mUpdatableLookupMap, oLLMData) {
50
+ populateSmartFields: function(sId, aFields, sEasyFillMode, oController, oTransientContextForEasyFill, oObjectPageContext, mUpdatableLookupMap, oLLMData, fnOnFieldChange) {
51
51
  var oObjectPageLayout = oController.byId("EasyFillObjectPageLayout");
52
52
  if (!oObjectPageLayout || !Array.isArray(aFields) || aFields.length === 0) {
53
53
  return true;
54
54
  }
55
55
 
56
+ var sPreviouslyLabel = oController.getView().getModel("i18n").getResourceBundle().getText("EASY_FILL_PREVIOUSLY_LABEL");
57
+
56
58
  // Defer injection until after the SmartForm has rendered into the DOM
57
59
  setTimeout(function() {
58
60
  var oTargetGroup = null;
59
61
 
60
62
  // Walk the ObjectPageLayout section hierarchy to find the group
61
- // matching the given CustomData "id" value
63
+ // matching the given CustomData "id" value.
64
+ // In condensed mode the SmartForm may be nested inside a wrapper VBox
65
+ // (used to prevent BlockLayout from placing the table side-by-side with
66
+ // the SmartForm on resize). We therefore check one level deeper when the
67
+ // direct block is not a SmartForm.
62
68
  oObjectPageLayout.getSections().forEach(function(oSection) {
63
69
  oSection.getSubSections().forEach(function(oSubSection) {
64
70
  oSubSection.getBlocks().forEach(function(oBlock) {
71
+ var aSmartFormCandidates = [];
65
72
  if (oBlock.isA("sap.ui.comp.smartform.SmartForm")) {
66
- oBlock.getGroups().forEach(function(oGroup) {
73
+ aSmartFormCandidates.push(oBlock);
74
+ } else if (typeof oBlock.getItems === "function") {
75
+ // Look one level inside (e.g. condensed-mode wrapper VBox)
76
+ oBlock.getItems().forEach(function(oItem) {
77
+ if (oItem.isA("sap.ui.comp.smartform.SmartForm")) {
78
+ aSmartFormCandidates.push(oItem);
79
+ }
80
+ });
81
+ }
82
+ aSmartFormCandidates.forEach(function(oSmartForm) {
83
+ oSmartForm.getGroups().forEach(function(oGroup) {
67
84
  var oMatchedCustomData = oGroup.getCustomData().find(function(oData) {
68
85
  return oData.getKey() === "id" && oData.getValue() === sId;
69
86
  });
@@ -71,7 +88,7 @@ sap.ui.define([
71
88
  oTargetGroup = oGroup;
72
89
  }
73
90
  });
74
- }
91
+ });
75
92
  });
76
93
  });
77
94
  });
@@ -85,7 +102,7 @@ sap.ui.define([
85
102
  oTargetGroup.destroyGroupElements();
86
103
 
87
104
  aFields.forEach(function(sKey) {
88
- var bIsEditable = mUpdatableLookupMap && !!mUpdatableLookupMap[sKey];
105
+ var bIsEditable = Object.prototype.hasOwnProperty.call(mUpdatableLookupMap || {}, sKey);
89
106
 
90
107
  var oGroupElement = new GroupElement({
91
108
  useHorizontalLayout: false
@@ -114,6 +131,56 @@ sap.ui.define([
114
131
  }
115
132
  });
116
133
 
134
+ if (!bIsEditable) {
135
+ // Keep read-only behavior, but render like an input field (v4-like visual parity)
136
+ oSmartField.addStyleClass("sapUiEasyFillReadOnlyInputLike sapMInputBaseContentWrapper");
137
+ }
138
+
139
+ // Notify on user edits so SmartForm validity can re-gate the Confirm button.
140
+ if (bIsEditable && typeof fnOnFieldChange === "function") {
141
+ oSmartField.attachChange(fnOnFieldChange);
142
+
143
+ // Validate AI-seeded values on initial load — mirrors ObjectPage behaviour.
144
+ //
145
+ // In the OP, validation triggers because when a user TYPES a value the browser
146
+ // fires a change event which causes UI5 to call setExternalValue() on the inner
147
+ // control's binding. setExternalValue() has no fast-path: it always calls
148
+ // parseValue → validateValue → OData value-list lookup. If the lookup fails the
149
+ // binding calls setInvalidValue() + checkDataState() → ManagedObject fires
150
+ // validationError → inner control gets valueState="Error" (red mark).
151
+ //
152
+ // SmartForm.check() / SmartField.checkValuesValidity() do NOT work for
153
+ // AI-seeded values because SmartField._checkErrors() contains a fast-path:
154
+ // if (!isControlDirty() && formattedModelValue === displayedValue) → skip
155
+ // AI values always satisfy this (model = display = AI value, user never typed),
156
+ // so _checkErrors() short-circuits to "valid" without the OData lookup.
157
+ //
158
+ // Fix: after innerControlsCreated, call setExternalValue() on the inner
159
+ // control's own binding with the currently displayed value. This is exactly
160
+ // the same path the OP uses when the user types. For async types (TextArrangement
161
+ // with ValueList) the call starts the OData lookup:
162
+ // valid value → resolves quietly, no red mark
163
+ // invalid value → rejects → setInvalidValue + checkDataState → red mark
164
+ // Non-async types fall through to a plain fnOnFieldChange call because
165
+ // SmartForm.check() already handles them correctly (no fast-path for sync).
166
+ oSmartField.attachInnerControlsCreated(function() {
167
+ var oInnerControl = oSmartField.getFirstInnerControl();
168
+ if (oInnerControl && typeof oInnerControl.getValue === "function") {
169
+ var oBinding = oInnerControl.getBinding("value");
170
+ var oType = oBinding && typeof oBinding.getType === "function" && oBinding.getType();
171
+ if (oType && oType.async && typeof oBinding.setExternalValue === "function") {
172
+ // setExternalValue replicates the "user typed this value" path:
173
+ // parseValue → validateValue → OData lookup → validationError/Success
174
+ Promise.resolve(oBinding.setExternalValue(oInnerControl.getValue()))
175
+ .catch(Function.prototype)
176
+ .finally(fnOnFieldChange);
177
+ return;
178
+ }
179
+ }
180
+ fnOnFieldChange();
181
+ });
182
+ }
183
+
117
184
  /**
118
185
  * "Previously:" SmartField (read-only):
119
186
  * - Only shown for editable fields to display the current OP value for comparison
@@ -125,10 +192,9 @@ sap.ui.define([
125
192
  enabled: false,
126
193
  contextEditable: false,
127
194
  showLabel: false,
128
- visible: bIsEditable,
129
195
  layoutData: new ColumnElementData({
130
196
  cellsSmall: 9,
131
- cellsLarge: 10
197
+ cellsLarge: 9
132
198
  }),
133
199
  editableChanged: function(oEvent) {
134
200
  // Always keep the "previously" field read-only
@@ -138,33 +204,42 @@ sap.ui.define([
138
204
  }
139
205
  });
140
206
 
141
- if (bIsEditable) {
142
- // Editable fields: primary field uses transient context (AI value),
143
- // preview field uses original OP context (current value)
207
+ if (oTransientContextForEasyFill) {
208
+ // Primary field always uses transient context to show AI-proposed value.
209
+ // Field editability is controlled by bIsEditable.
144
210
  oSmartField.setBindingContext(oTransientContextForEasyFill);
145
- oSmartFieldPrev.setBindingContext(oObjectPageContext);
146
211
 
147
- // Set the AI-suggested value on the transient context for this field
148
- oTransientContextForEasyFill.setProperty(sKey, oLLMData[sKey]);
212
+ // Only seed the transient context with the AI-suggested value on the first
213
+ // population. On subsequent calls (e.g. mode switch), the transient context
214
+ // already holds the current value — overwriting it would discard user edits.
215
+ if (oTransientContextForEasyFill.getProperty(sKey) === undefined) {
216
+ oTransientContextForEasyFill.setProperty(sKey, oLLMData[sKey]);
217
+ }
149
218
  } else {
150
- // Non-editable fields: bound to the original OP context, display only
219
+ // Fallback: without transient context, bind to original OP context.
151
220
  oSmartField.setBindingContext(oObjectPageContext);
152
221
  }
153
222
 
223
+ // Previous value is always shown from the original ObjectPage context.
224
+ oSmartFieldPrev.setBindingContext(oObjectPageContext);
225
+
154
226
  // Build the GroupElement: [label+primary SmartField] [Previously: label] [preview SmartField]
155
227
  oGroupElement.addElement(oSmartField); // SmartField provides the label
156
228
  oGroupElement.addElement(new Text({
157
- text: "Previously:",
158
- visible: bIsEditable,
229
+ text: sPreviouslyLabel,
159
230
  layoutData: new ColumnElementData({
160
231
  cellsSmall: 3,
161
- cellsLarge: 2
232
+ cellsLarge: 3
162
233
  })
163
- }));
234
+ }).addStyleClass("sapEasyFillPreviouslyLabel"));
164
235
  oGroupElement.addElement(oSmartFieldPrev);
165
236
  oTargetGroup.addGroupElement(oGroupElement);
166
237
  });
167
238
 
239
+ // Trigger initial SmartForm validation now that all SmartFields are rendered
240
+ if (typeof fnOnFieldChange === "function") {
241
+ fnOnFieldChange();
242
+ }
168
243
  oLogger.debug("populateSmartFields: populated " + aFields.length + " fields for group '" + sId + "'");
169
244
  }, 0);
170
245