@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
@@ -1,1205 +1,786 @@
1
1
  sap.ui.define([
2
2
  "sap/ui/base/Object",
3
3
  "sap/base/util/extend",
4
- "sap/ui/comp/smartform/GroupElement",
5
- "sap/ui/comp/smartfield/SmartField",
6
- "sap/m/MessageToast",
7
- "sap/ui/model/json/JSONModel",
8
- "sap/ui/model/odata/AnnotationHelper",
9
- "sap/suite/ui/generic/template/lib/ai/EasyFill/ObjectPageSectionHandler",
10
- "sap/m/VBox",
11
- "sap/m/Text",
12
4
  "sap/suite/ui/generic/template/library",
13
- "sap/m/HBox",
14
- "sap/ui/layout/form/ColumnElementData",
15
- "sap/suite/ui/generic/template/js/AnnotationHelper",
16
- "sap/suite/ui/generic/template/genericUtilities/controlHelper",
17
- "sap/ui/model/Filter",
18
- "sap/ui/model/FilterOperator",
19
- "sap/m/Title",
20
- "sap/m/SegmentedButton",
21
- "sap/m/SegmentedButtonItem",
22
- "sap/m/Table",
23
- "sap/m/Column",
24
- "sap/m/ColumnListItem"
25
- ], function(BaseObject, extend, GroupElement, SmartField,MessageToast,JSONModel,AnnotationHelperModel,ObjectPageSectionHandler,VBox,Text,Library,HBox,ColumnElementData,GenericAnnotationHelper,controlHelper,Filter,FilterOperator,Title,SegmentedButton,SegmentedButtonItem,Table,Column,ColumnListItem) {
26
- 'use strict';
27
- const EasyFillLayoutMode = Library.EasyFillLayoutMode;
28
- /*
29
- * This is a handler class for EasyFill.
30
- * It gets initialized when the Fiori AI EasyFill is enabled
31
- * Below things are handled in this class:
32
- * 1. Prepare Field metadata
33
- * 2. Prepare EasyFill Dialog
5
+ "sap/suite/ui/generic/template/genericUtilities/FeError",
6
+ "sap/suite/ui/generic/template/lib/ai/EasyFill/EasyFillFieldCollector",
7
+ "sap/suite/ui/generic/template/lib/ai/EasyFill/EasyFillAIOrchestrator",
8
+ "sap/suite/ui/generic/template/lib/ai/EasyFill/EasyFillFormRenderer",
9
+ "sap/suite/ui/generic/template/lib/ai/EasyFill/EasyFillTableRenderer",
10
+ "sap/suite/ui/generic/template/lib/ai/EasyFill/EasyFillApplyHandler",
11
+ "sap/suite/ui/generic/template/lib/ai/EasyFill/EasyFillDialogController"
12
+ ], function(
13
+ BaseObject,
14
+ extend,
15
+ Library,
16
+ FeError,
17
+ EasyFillFieldCollector,
18
+ EasyFillAIOrchestrator,
19
+ EasyFillFormRenderer,
20
+ EasyFillTableRenderer,
21
+ EasyFillApplyHandler,
22
+ EasyFillDialogController
23
+ ) {
24
+ "use strict";
25
+
26
+ /**
27
+ * EasyFillHandler
34
28
  *
29
+ * Thin orchestrator for the EasyFill AI feature on the ObjectPage.
30
+ *
31
+ * This class wires all EasyFill sub-modules together and exposes a single public
32
+ * entry point (onEasyFillButtonClick) to the ObjectPage ControllerImplementation.
33
+ *
34
+ * Sub-module responsibilities:
35
+ * EasyFillFieldCollector — OData metadata field/section extraction
36
+ * EasyFillAIOrchestrator — AI service call and response post-processing
37
+ * EasyFillFormRenderer — SmartForm/GroupElement/SmartField population
38
+ * EasyFillTableRenderer — SmartTable collection preview rendering
39
+ * EasyFillApplyHandler — Confirm/save, validation, reset logic
40
+ * EasyFillDialogController — Dialog lifecycle, formatters, UI event handlers
41
+ *
42
+ * Shared mutable state lives here and is passed by reference into sub-modules.
43
+ * Sub-modules are stateless — they never store state themselves.
35
44
  */
36
45
  function getMethods(oState, oController, oTemplateUtils, oObjectPage) {
37
- let oTransientContextForEasyFill;
38
- let oObjectPageContext;
39
- let oObjectPageModel;
40
- let mUpdatableLookupMap;
41
- let oRb;
42
- let oAIPopover;
43
- let aEditableFields = [];
44
- let mCollectionUpdates = Object.create(null);
45
- let mCollectionRowContexts = Object.create(null);
46
- let mCollectionRowContextsByPath = Object.create(null);
47
- let mCollectionProperties = Object.create(null);
48
- let mCollectionEntityTypes = Object.create(null);
49
- let mStagedRowValues = Object.create(null);
50
- const MAX_TABLE_PREVIEW_ROWS = 20;
51
-
52
- async function openEasyFillDialog(oEvent,oContext) {
53
- let oEasyFillDialog;
46
+ var EasyFillLayoutMode = Library.EasyFillLayoutMode;
47
+
48
+ // ─── Shared Mutable State ────────────────────────────────────────────────
49
+ // All state is declared here and passed into sub-modules as needed.
50
+ // Sub-modules must not cache these values — always use the latest reference.
51
+
52
+ var oTransientContextForEasyFill = null; // Transient OData context for staged AI scalar values
53
+ var oObjectPageContext = null; // Live ObjectPage binding context
54
+ var oObjectPageModel = null; // OData model of the ObjectPage
55
+ var mUpdatableLookupMap = {}; // { fieldName: value } for updatable fields only
56
+ var oRb = null; // i18n resource bundle
57
+ var oAIPopoverRef = { popover: null }; // Mutable ref for lazy-loaded AINotice popover
58
+
59
+ // Collection state — tracks OData row contexts and property metadata for SmartTable collections
60
+ var mCollectionState = {
61
+ mCollectionRowContexts: Object.create(null),
62
+ mCollectionRowContextsByPath: Object.create(null),
63
+ mCollectionProperties: Object.create(null),
64
+ mCollectionEntityTypes: Object.create(null),
65
+ mCollectionEntitySets: Object.create(null),
66
+ mCollectionAllowsCreation: Object.create(null)
67
+ };
68
+
69
+ var mCollectionUpdates = Object.create(null); // { navProperty: [ rowUpdate ] } from AI response
70
+ var mLastFieldMapping = null; // retained so table sections can be re-rendered on mode switch
71
+ var oTableStagingState = {
72
+ mStagedRows: Object.create(null),
73
+ mPendingNewRowContexts: Object.create(null)
74
+ };
75
+
76
+ // ─── Internal Helpers ────────────────────────────────────────────────────
77
+
78
+ /**
79
+ * Opens the EasyFill dialog for the given ObjectPage context.
80
+ * Initializes the dialog model on first open, then loads and opens the dialog fragment
81
+ * with all event handlers and formatters bound.
82
+ *
83
+ * @param {sap.ui.base.Event} oEvent - the button click event
84
+ * @param {sap.ui.model.Context} [oContext] - optional context override (used when OP just entered edit mode)
85
+ */
86
+ async function openEasyFillDialog(oEvent, oContext) {
54
87
  oRb = oController.getView().getModel("i18n").getResourceBundle();
55
- const bIsModelCreated = !!oController.getView().getModel("easyFillDialogModel");
56
88
  oObjectPageModel = oObjectPage.getModel();
57
- if (!bIsModelCreated) {
58
- const aDeferredGroups = oObjectPageModel.getDeferredGroups();
59
- //Adding a defferred group will make sure that no batch call would get triggered to backend until submitChanges() is invoked
60
- aDeferredGroups.push("EasyFillFETransientChanges");
61
- oObjectPageModel.setDeferredGroups(aDeferredGroups);
62
-
63
- //Creating a JSON model for the dialog
64
-
65
- const oData = {
66
- isBusy: false,
67
- isFormVisible: false,
68
- isIllustrationVisible: true,
69
- isSaveEnabled: false,
70
- stateType: "Initial",
71
- feedbackEnabled: true,
72
- feedbackPressedUp: false,
73
- feedbackPressedDown: false,
74
- isEasyFillButtonEnabled: false,
75
- llmData: {},
76
- easyFillMode: EasyFillLayoutMode.CondensedMode,
77
- sections:[],
78
- isFullScreen: true,
79
- hasTableUpdates: false,
80
- hasTableValidationErrors: false,
81
- tableValidationMessage: ""
82
- };
83
-
84
- let oModel = new JSONModel(oData);
85
- oController.getView().setModel(oModel, "easyFillDialogModel");
86
- }
87
89
 
88
- oObjectPageContext = (oContext) ? oContext : oObjectPage.getBindingContext();
90
+ // Initialize dialog model and deferred group once per view lifetime
91
+ EasyFillDialogController.initDialogModel(oController, oObjectPageModel, EasyFillLayoutMode);
92
+
93
+ // Use the provided context override or fall back to the current OP binding context
94
+ oObjectPageContext = oContext || oObjectPage.getBindingContext();
95
+
96
+ // Build the handler/formatter map that the dialog fragment binds to
97
+ var oHandlers = _buildDialogHandlers();
98
+
99
+ await EasyFillDialogController.openDialog(oController, oTemplateUtils, oHandlers);
100
+ }
89
101
 
90
- // cannot use OP path to create a list binding as it is a individual entry
91
- var sFragmentname = "sap.suite.ui.generic.template.lib.ai.EasyFill.fragments.EasyFillDialog";
92
- oTemplateUtils.oCommonUtils.getDialogFragmentAsync(sFragmentname, {
102
+ /**
103
+ * Builds the event handler and formatter map passed to the EasyFillDialog fragment.
104
+ * Each entry corresponds to a function called from the fragment via event binding or formatter.
105
+ *
106
+ * @returns {object} handler/formatter map
107
+ */
108
+ function _buildDialogHandlers() {
109
+ return {
110
+ // ── Layout mode toggle (Condensed ↔ ObjectPage view) ──────────────
111
+ // After the sections binding is refreshed, SmartForms for the new mode
112
+ // are repopulated asynchronously (setTimeout in populateSmartFields).
113
+ // _validateFieldsAndUpdateConfirmState is called here so that validation runs
114
+ // immediately on whatever SmartForms are currently in the DOM, and then
115
+ // again via fnOnFieldChange once the new SmartFields are rendered.
93
116
  onSelectionChange: function(oEvent) {
94
- const sSelectedKey = oEvent.getSource().getSelectedKey();
95
- const easyFillDialogModel = oController.getView().getModel("easyFillDialogModel");
96
- const oEntityType = oTemplateUtils.oCommonUtils.getMetaModelEntityType(oController.getOwnerComponent().getEntitySet());
97
- easyFillDialogModel.setProperty("/easyFillMode",sSelectedKey);
98
- const oLLMResult = easyFillDialogModel.getProperty("/llmData");
99
- const aData = (sSelectedKey === EasyFillLayoutMode.CondensedMode) ? ObjectPageSectionHandler.getSectionsForCondensedView(oLLMResult) : ObjectPageSectionHandler.getSectionsForObjectPageView(oEntityType,oLLMResult,oRb);
100
- easyFillDialogModel.setProperty("/sections",aData);
117
+ // Step 1: clean up table content injected for the CURRENT mode BEFORE the
118
+ // model update. This ensures the OPL aggregation is free of manually-added
119
+ // sections when the sections binding recreates controls for the new mode,
120
+ // so getSections() in renderCollectionPreviews returns only bound sections.
121
+ var oOpl = oController.byId("EasyFillObjectPageLayout");
122
+ if (oOpl && mLastFieldMapping && Object.keys(mCollectionUpdates).length > 0) {
123
+ EasyFillTableRenderer.removeTableSections(oOpl);
124
+ }
125
+
126
+ // Step 2: update model — this triggers the OPL sections binding to recreate
127
+ // bound section controls for the new mode.
128
+ EasyFillDialogController.onSelectionChange(
129
+ oEvent, oController, oTemplateUtils, oRb,
130
+ EasyFillFieldCollector, EasyFillLayoutMode
131
+ );
132
+
133
+ // Step 3: re-render table previews for the new mode.
134
+ // _removeTableSections is skipped inside renderCollectionPreviews because
135
+ // bReuseStaging=true, since cleanup was already done in step 1 above.
136
+ if (mLastFieldMapping && Object.keys(mCollectionUpdates).length > 0) {
137
+ EasyFillTableRenderer.renderCollectionPreviews(
138
+ mCollectionUpdates,
139
+ mLastFieldMapping,
140
+ [],
141
+ oController,
142
+ oRb,
143
+ mCollectionState,
144
+ oTableStagingState,
145
+ oObjectPageModel,
146
+ _validateFieldsAndUpdateConfirmState,
147
+ true // bReuseStaging — also skips re-cleanup (already done above)
148
+ );
149
+ }
150
+ _validateFieldsAndUpdateConfirmState();
101
151
  },
152
+
153
+ // ── Feedback thumbs-up/down ───────────────────────────────────────
102
154
  onFeedbackPress: function() {
103
- const easyFillDialogModel = oController.getView().getModel("easyFillDialogModel");
104
- easyFillDialogModel.setProperty("/feedbackEnabled",false);
105
- MessageToast.show(oRb.getText("EASYFILL_RESULT_FEEDBACK"));
155
+ EasyFillDialogController.onFeedbackPress(oController, oRb);
106
156
  },
157
+
158
+ // ── Section title formatter (resolves i18n patterns) ──────────────
107
159
  formatSectionTitle: function(sTitle) {
108
- if (!sTitle) {return "";}
109
- // Matches {@i18n>SomeKey} or {i18n>SomeKey}
110
- const oMatch = sTitle.match(/\{@?i18n>([^}]+)\}/);
111
- if (oMatch) {
112
- const sKey = oMatch[1];
113
- const oResourceBundle = oController.getView().getModel("i18n").getResourceBundle();
114
- return oResourceBundle.getText(sKey);
115
- }
116
- // No i18n pattern → return as-is
117
- return sTitle;
160
+ return EasyFillDialogController.formatSectionTitle(sTitle, oController);
118
161
  },
119
- formatIllustrationType: function (sState) {
120
- switch (sState) {
121
- case "Initial":
122
- return "sapIllus-NoSearchResults";
123
- case "NoEntries":
124
- return "sapIllus-NoEntries";
125
- case "Error":
126
- return "sapIllus-UnableToLoad";
127
- default:
128
- return "sapIllus-NoSearchResults";
129
- }
162
+
163
+ // ── IllustratedMessage state formatters ───────────────────────────
164
+ formatIllustrationType: function(sState) {
165
+ return EasyFillDialogController.formatIllustrationType(sState);
130
166
  },
131
- formatIllustrationTitle: function (sState) {
132
- switch (sState) {
133
- case "Initial":
134
- return oRb.getText("EASY_FILL_ILLUSTRATION_TITLE_INITIAL");
135
- case "NoEntries":
136
- return oRb.getText("EASY_FILL_ILLUSTRATION_TITLE_NO_ENTRIES");
137
- case "Error":
138
- return oRb.getText("EASY_FILL_ILLUSTRATION_TITLE_ERROR");
139
- default:
140
- return oRb.getText("EASY_FILL_ILLUSTRATION_TITLE_INITIAL");
141
- }
167
+ formatIllustrationTitle: function(sState) {
168
+ return EasyFillDialogController.formatIllustrationTitle(sState, oRb);
142
169
  },
143
- formatIllustrationDescription: function (sState) {
144
- switch (sState) {
145
- case "Initial":
146
- return oRb.getText("EASY_FILL_ILLUSTRATION_DESCRIPTION_INITIAL");
147
- case "NoEntries":
148
- return oRb.getText("EASY_FILL_ILLUSTRATION_DESCRIPTION_NO_ENTRIES");
149
- case "Error":
150
- return oRb.getText("EASY_FILL_ILLUSTRATION_DESCRIPTION_ERROR");
151
- default:
152
- return oRb.getText("EASY_FILL_ILLUSTRATION_DESCRIPTION_INITIAL");
153
- }
170
+ formatIllustrationDescription: function(sState) {
171
+ return EasyFillDialogController.formatIllustrationDescription(sState, oRb);
154
172
  },
155
- onSaveFromEasyFillDialog : async function (oEvent) {
156
- var oTransientObject = oTransientContextForEasyFill.getObject();
157
- for (const oProp in oTransientObject) {
158
- if (oProp !== "__metadata" && mUpdatableLookupMap[oProp]) {
159
- oObjectPageContext.setProperty(oProp, oTransientObject[oProp]);
160
- }
161
- }
162
- // Collection row values are already staged onto OData contexts via the editable New table;
163
- // clear staged tracking without reverting, then activate side effects.
164
- mStagedRowValues = Object.create(null);
165
- //Temporarily deactivating, Yet to be added again
166
- // activateSideEffects();
167
- resetEasyFill(true);
168
- oEasyFillDialog?.close();
173
+
174
+ // ── Confirm: apply AI values to the live OP context ───────────────
175
+ // The Confirm button is disabled reactively by _validateFieldsAndUpdateConfirmState
176
+ // whenever a SmartField value is invalid — no click-time check needed.
177
+ onSaveFromEasyFillDialog: async function() {
178
+ // Capture staged collection updates BEFORE applyAndSave writes them to the live
179
+ // contexts — after applyAndSave the staged values equal the live values and the map would be empty.
180
+ var mStagedUpdates = EasyFillApplyHandler.buildStagedCollectionUpdates(oTableStagingState, mCollectionState);
181
+
182
+ // Extract reviewed new-row values from staging contexts BEFORE reset discards them.
183
+ var mNewRowValues = EasyFillApplyHandler.extractPendingNewRowValues(oTableStagingState, mCollectionState);
184
+
185
+ EasyFillApplyHandler.applyAndSave(
186
+ oTransientContextForEasyFill,
187
+ oObjectPageContext,
188
+ mUpdatableLookupMap,
189
+ oTableStagingState,
190
+ mCollectionState
191
+ );
192
+ // Trigger side effects on the OP context for all changed SmartForm fields
193
+ // before resetting the dialog (SmartFields must still be present)
194
+ _activateSideEffects();
195
+ // Trigger side effects for all modified table rows
196
+ _activateSideEffectsForTable(mStagedUpdates);
197
+ _resetEasyFill(true);
198
+ EasyFillDialogController.closeDialog(oController);
199
+
200
+ // Create the new rows via the standard CRUDManager (draft-aware, handles side effects)
201
+ // This is done after dialog close so the user sees the OP immediately.
202
+ await _createNewRows(mNewRowValues);
169
203
  },
204
+
205
+ // ── Text area live change: enable/disable submit button ───────────
170
206
  onLiveChange: function(oEvent) {
171
- const easyFillDialogModel = oController.getView().getModel("easyFillDialogModel");
172
- easyFillDialogModel.setProperty("/isEasyFillButtonEnabled",!!oEvent.getSource().getValue());
207
+ EasyFillDialogController.onLiveChange(oEvent, oController);
173
208
  },
174
- onCancelEasyFillDialog : function () {
175
- resetEasyFill(true);
176
- oEasyFillDialog.close();
209
+
210
+ // ── Cancel: revert staged values and close dialog ─────────────────
211
+ onCancelEasyFillDialog: function() {
212
+ _resetEasyFill(true);
213
+ EasyFillDialogController.closeDialog(oController);
177
214
  },
178
- formatFieldValue: function(sFieldName,oData) {
179
- return `{${sFieldName}}`;
215
+
216
+ // ── Field value formatter (binding expression builder) ────────────
217
+ formatFieldValue: function(sFieldName) {
218
+ return "{" + sFieldName + "}";
180
219
  },
181
- onEasyFillSubmitInputToAI : async function (oEvent) {
182
- const easyFillDialogModel = oController.getView().getModel("easyFillDialogModel");
183
- easyFillDialogModel.setProperty("/isBusy",true);
184
- let mFieldMapping = getEntitySetMapForLLM();
185
- let aiCallResult = await oTemplateUtils.oServices.oFioriAIHandler.fioriaiLib.EasyFill.extractFieldValuesFromText(oController.byId("EasyFillInputTextArea").getValue(), mFieldMapping);
186
- aEditableFields = [];
187
- easyFillDialogModel.setProperty("/isBusy",false);
188
- formatDateValues(aiCallResult,mFieldMapping);
189
- const oEntityType = oTemplateUtils.oCommonUtils.getMetaModelEntityType(oController.getOwnerComponent().getEntitySet());
190
- const sSelectedKey = easyFillDialogModel.getProperty("/easyFillMode") || EasyFillLayoutMode.CondensedMode;
191
- easyFillDialogModel.setProperty("/easyFillMode",sSelectedKey);
192
- if (aiCallResult.success) {
193
- const {simpleFields,collectionFields} = splitAIResultByCollection(aiCallResult.data, mFieldMapping);
194
- const {updatableFields,nonUpdatableFields} = getUpdatableAndNonUpdatableFields(simpleFields);
195
- mUpdatableLookupMap = updatableFields;
196
- //Setting up Sections for both condensed view and Object page view
197
- const aData = (sSelectedKey === EasyFillLayoutMode.CondensedMode) ? ObjectPageSectionHandler.getSectionsForCondensedView(simpleFields) : ObjectPageSectionHandler.getSectionsForObjectPageView(oEntityType,simpleFields,oRb);
198
- resetEasyFill();
199
- makeTransientContext();
200
- easyFillDialogModel.setProperty("/sections",aData);
201
- easyFillDialogModel.setProperty("/llmData",simpleFields);
202
- //Setting and rendering of SmartTables
203
- mCollectionUpdates = collectionFields;
204
- const bHasTableUpdates = Object.keys(mCollectionUpdates).length > 0;
205
- easyFillDialogModel.setProperty("/hasTableUpdates", bHasTableUpdates);
206
- const aTableValidationErrors = await validateCollectionUpdatesAgainstValueHelp(mFieldMapping);
207
- applyTableValidationState(aTableValidationErrors);
208
- renderCollectionPreviews(mFieldMapping, aTableValidationErrors);
209
-
210
- if (Object.keys(updatableFields).length + Object.keys(nonUpdatableFields).length === 0 && !bHasTableUpdates) {
211
- easyFillDialogModel.setProperty("/stateType","NoEntries");
212
- return;
213
- }
214
- easyFillDialogModel.setProperty("/isIllustrationVisible",false);
215
- //Fillable Fields
216
- easyFillDialogModel.setProperty("/isFormVisible",Object.keys(updatableFields).length + Object.keys(nonUpdatableFields).length > 0);
217
220
 
218
- if (bHasTableUpdates) {
219
- easyFillDialogModel.setProperty("/isSaveEnabled", aTableValidationErrors.length === 0);
220
- }
221
- } else {
222
- applyTableValidationState([]);
223
- easyFillDialogModel.setProperty("/stateType","Error");
224
- }
221
+ // ── Submit user text to AI ────────────────────────────────────────
222
+ onEasyFillSubmitInputToAI: async function() {
223
+ await _handleAISubmit();
225
224
  },
226
225
 
226
+ // ── Clear all (reset without closing) ────────────────────────────
227
227
  onEasyFillClearAll: function() {
228
- resetEasyFill(true);
228
+ _resetEasyFill(true);
229
229
  },
230
230
 
231
+ // ── SmartForm group population formatter ──────────────────────────
231
232
  populateSmartFields: function(sId, aFields, sEasyFillMode) {
232
- const oObjectPageLayout = oController.byId("EasyFillObjectPageLayout");
233
- if (!oObjectPageLayout || !Array.isArray(aFields) || aFields.length === 0) {
233
+ return EasyFillFormRenderer.populateSmartFields(
234
+ sId, aFields, sEasyFillMode,
235
+ oController,
236
+ oTransientContextForEasyFill,
237
+ oObjectPageContext,
238
+ mUpdatableLookupMap,
239
+ oDialogModel().getProperty("/llmData"),
240
+ _validateFieldsAndUpdateConfirmState
241
+ );
242
+ },
243
+
244
+ // ── Section visibility: hide SmartForm sections with no fields ──────────────
245
+ hasSectionFields: function(aFields, aChildren) {
246
+ if (Array.isArray(aFields) && aFields.length > 0) {
234
247
  return true;
235
248
  }
236
249
 
237
- const easyFillDialogModel = oController.getView().getModel("easyFillDialogModel");
238
-
239
- setTimeout(() => {
240
- const oLLMData = easyFillDialogModel.getProperty("/llmData");
241
- let oTargetGroup = null;
242
-
243
- // sections -> ObjectPageSection
244
- oObjectPageLayout.getSections().forEach((oSection) => {
245
- // subSections -> ObjectPageSubSection
246
- oSection.getSubSections().forEach((oSubSection) => {
247
- // blocks -> SmartForm
248
- oSubSection.getBlocks().forEach((oBlock) => {
249
- if (oBlock.isA("sap.ui.comp.smartform.SmartForm")) {
250
- // ✅ Search inside Groups, not SmartForm
251
- oBlock.getGroups().forEach((oGroup) => {
252
- const oMatchedCustomData = oGroup.getCustomData().find(
253
- (oData) => oData.getKey() === "id" && oData.getValue() === sId
254
- );
255
- if (oMatchedCustomData) {
256
- oTargetGroup = oGroup;
257
- }
258
- });
259
- }
260
- });
261
- });
262
- });
263
-
264
- if (!oTargetGroup) {
265
- return;
250
+ function hasFieldsInNode(oNode) {
251
+ if (!oNode) {
252
+ return false;
266
253
  }
254
+ if (Array.isArray(oNode.fields) && oNode.fields.length > 0) {
255
+ return true;
256
+ }
257
+ if (Array.isArray(oNode.children)) {
258
+ return oNode.children.some(hasFieldsInNode);
259
+ }
260
+ return false;
261
+ }
267
262
 
268
- // Destroy old GroupElements before adding new
269
- oTargetGroup.destroyGroupElements();
270
-
271
- aFields.forEach((sKey) => {
272
- // const bFieldEditable = true;
273
- const oGroupElement = new GroupElement({
274
- useHorizontalLayout: false
275
- });
276
-
277
- const isEditable = mUpdatableLookupMap && !!mUpdatableLookupMap[sKey];
278
-
279
- const oSmartField = new SmartField({
280
- value: "{" + sKey + "}",
281
- editable: isEditable,
282
- enabled: isEditable,
283
- contextEditable: isEditable,
284
- //Yet to be added, Temporarily disabled
285
- // change: onSmartFieldChange.bind(this),
286
- layoutData: new ColumnElementData({
287
- cellsSmall: 12,
288
- cellsLarge: 12
289
- }),
290
- editableChanged: (oEvent) => {
291
- if (oEvent.getParameter("editable") !== isEditable) {
292
- oEvent.getSource().setEditable(isEditable);
293
- }
294
- }
295
- });
296
-
297
- const oSmartFieldPrev = new SmartField({
298
- value: "{" + sKey + "}",
299
- editable: false,
300
- enabled: false,
301
- contextEditable: false,
302
- showLabel: false,
303
- visible: isEditable,
304
- layoutData: new ColumnElementData({
305
- cellsSmall: 9,
306
- cellsLarge: 10
307
- }),
308
- editableChanged: (oEvent) => {
309
- if (oEvent.getParameter("editable") !== false) {
310
- oEvent.getSource().setEditable(false);
311
- }
312
- }
313
- });
314
-
315
-
316
- if (isEditable) {
317
- oSmartField.setBindingContext(oTransientContextForEasyFill);
318
- oSmartFieldPrev.setBindingContext(oObjectPageContext);
319
- // aEditableFields.push(oSmartField);
320
- oTransientContextForEasyFill.setProperty(sKey, oLLMData[sKey]);
321
- } else {
322
- oSmartField.setBindingContext(oObjectPageContext);
323
- }
324
- oGroupElement.addElement(oSmartField); // label source
325
- oGroupElement.addElement( new Text({
326
- text: "Previously:",
327
- visible: isEditable,
328
- layoutData: new ColumnElementData({
329
- cellsSmall: 3,
330
- cellsLarge: 2
331
- })
332
- }));
333
- oGroupElement.addElement(oSmartFieldPrev);
334
- oTargetGroup.addGroupElement(oGroupElement);
335
- });
336
-
337
- }, 0);
338
-
339
- return true;
263
+ return Array.isArray(aChildren) && aChildren.some(hasFieldsInNode);
340
264
  },
341
265
 
266
+ // ── Fullscreen toggle ─────────────────────────────────────────────
342
267
  onToggleFullScreen: function() {
343
- const oEasyFillDialogModel = oController.getView().getModel("easyFillDialogModel");
344
- const bIsFullScreen = oEasyFillDialogModel.getProperty("/isFullScreen");
345
- // ✅ Toggle fullscreen state
346
- oEasyFillDialogModel.setProperty("/isFullScreen", !bIsFullScreen);
347
- if (bIsFullScreen) {
348
- oController.byId("inputAreaSplitPane").getLayoutData().setSize("0%");
349
- oController.byId("reviewAreaSplitPane").getLayoutData().setSize("100%");
350
- } else {
351
- oController.byId("inputAreaSplitPane").getLayoutData().setSize("40%");
352
- oController.byId("reviewAreaSplitPane").getLayoutData().setSize("60%");
353
- }
268
+ EasyFillDialogController.onToggleFullScreen(oController);
354
269
  },
355
270
 
356
-
271
+ // ── AI Notice popover ─────────────────────────────────────────────
357
272
  onLinkPress: async function(oEvent) {
358
- if (!oAIPopover) {
359
- oAIPopover = await getAIPopover();
360
- }
361
- oAIPopover.openBy(oEvent.getSource());
273
+ await EasyFillDialogController.onLinkPress(oEvent, oTemplateUtils, oAIPopoverRef);
362
274
  }
363
- }, "easyFillPopup",undefined,true,true).then(function (oPopup) {
364
- oEasyFillDialog = oPopup;
365
- oPopup?.open();
366
- });
367
- }
368
-
369
- //Yet to implement, temporarily disabled
370
- // function activateSideEffects() {
371
- // //Making a set so that , duplicate field group ids are not passed
372
- // const aFieldGroupIdSet = new Set();
373
- // aEditableFields.forEach((oSmartField)=>{
374
- // //SideEffect would be invoked only for the editable controls inside the SmartField
375
- // const aEditableInnerFields = oSmartField.getInnerControls().filter((oControl)=>oControl.getEditable());
376
- // aEditableInnerFields.forEach((oControl)=>{
377
- // const aControlFieldGroupId = oControl.getFieldGroupIds();
378
- // if (aControlFieldGroupId.length > 0) {
379
- // aControlFieldGroupId.forEach((sFieldGroupId)=>{
380
- // aFieldGroupIdSet.add(sFieldGroupId);
381
- // });
382
- // }
383
- // });
384
-
385
- // });
386
-
387
- // const oView = oController.getView();
388
-
389
- // aFieldGroupIdSet.forEach((sFieldGroupId)=>{
390
- // const oID = oView.data(sFieldGroupId);
391
-
392
- // /**
393
- // * Currently, the SmartFields are created using the TransientContext, which means both
394
- // * "context" and "contextObject" refer to the TransientContext instance.
395
- // *
396
- // * However, we need to use the original OP context so that side effects are triggered
397
- // * on the original OP context when the user saves data from the EasyFill dialog back to OP.
398
- // *
399
- // * Therefore, we update the "context" and "contextObject" to reference the original OP context.
400
- // */
401
-
402
- // if (oID) {
403
- // oID["context"] = oObjectPageContext.getPath();
404
- // oID["contextObject"] = oObjectPageContext;
405
- // oView.data(sFieldGroupId,oID);
406
- // }
407
- // //To make sure that all the fields sideeffect would be triggered
408
- // const sSideEffectQualifier = oTemplateUtils.oServices.oApplicationController._getSideEffectsQualifier(oID.name);
409
- // oTemplateUtils.oServices.oApplicationController.registerGroupChange(sSideEffectQualifier);
410
- // });
411
-
412
-
413
- // oView.triggerValidateFieldGroup([...aFieldGroupIdSet]);
414
- // }
415
-
416
- function getAIPopover() {
417
- return new Promise(async(resolve,reject) => {
418
- const sFragmentname = "sap.suite.ui.generic.template.fragments.AINotice";
419
- const oPopover = await oTemplateUtils.oCommonUtils.getDialogFragmentAsync(sFragmentname,{
420
- onCloseAiPopover: function() {
421
- oAIPopover.close();
422
- }
423
- },"aiPopover",undefined,true);
424
- resolve(oPopover);
425
- });
426
- }
427
-
428
- function getEntitySetMapForLLM(oLLMResult) {
429
- const mPropertyMap = {};
430
- const oEntityType = oTemplateUtils.oCommonUtils.getMetaModelEntityType(oController.getOwnerComponent().getEntitySet());
431
- oEntityType.property.forEach((oProperty)=>{
432
- if (isVisible(oProperty)) {
433
- mPropertyMap[oProperty.name] = {
434
- description: getLabel(oProperty),
435
- dataType: oProperty.type
436
- };
437
- }
438
- });
439
- appendCollectionMapForLLM(mPropertyMap);
440
- return mPropertyMap;
441
- }
442
-
443
- function getLabel(oProperty) {
444
- return oProperty["com.sap.vocabularies.Common.v1.Label"] ? AnnotationHelperModel.format(oObjectPageContext,oProperty["com.sap.vocabularies.Common.v1.Label"]) : oProperty["sap:label"];
275
+ };
445
276
  }
446
277
 
447
- function formatDateValues(aiCallResult,mFieldMapping) {
448
- if (aiCallResult.success) {
449
- for (const key of Object.keys(aiCallResult.data)) {
450
- const sDataType = mFieldMapping[key].dataType;
451
- if (sDataType === "Edm.DateTimeOffset" || sDataType === "Edm.DateTime") {
452
- aiCallResult.data[key] = new Date(aiCallResult.data[key]);
453
- }
454
- }
455
- }
456
- }
278
+ /**
279
+ * Handles the full AI submission flow:
280
+ * 1. Build field mapping
281
+ * 2. Call AI service
282
+ * 3. Post-process response (dates, split)
283
+ * 4. Update dialog model
284
+ * 5. Populate SmartForms and table previews
285
+ */
286
+ async function _handleAISubmit() {
287
+ var oModel = oDialogModel();
288
+ oModel.setProperty("/isBusy", true);
289
+
290
+ // Build complete LLM field mapping (entity fields + collection fields)
291
+ var mFieldMapping = EasyFillAIOrchestrator.buildFieldMapping(
292
+ oTemplateUtils, oController, oObjectPage, oObjectPageModel,
293
+ oObjectPageContext, EasyFillFieldCollector, mCollectionState
294
+ );
457
295
 
458
- //Yet to be implemented, temporarily disabled
459
- // function validateSmartForm() {
460
- // setTimeout(async() => {
461
- // const easyFillDialogModel = oController.getView().getModel("easyFillDialogModel");
462
- // try {
463
- // const oAIResponseReviewRegionSmartFormForUpdatableFields = oController.byId("EasyFillUpdatebleForm");
464
- // const aIds = await oAIResponseReviewRegionSmartFormForUpdatableFields.check();
465
- // const bHasTableValidationErrors = !!easyFillDialogModel.getProperty("/hasTableValidationErrors");
466
- // easyFillDialogModel.setProperty("/isSaveEnabled",aIds.length === 0 && !bHasTableValidationErrors);
467
- // } catch (error) {
468
- // easyFillDialogModel.setProperty("/isSaveEnabled",false);
469
- // }
470
- // }, 0);
471
- // }
472
-
473
- function applyTableValidationState(aValidationErrors) {
474
- const easyFillDialogModel = oController.getView().getModel("easyFillDialogModel");
475
- if (!aValidationErrors || aValidationErrors.length === 0) {
476
- easyFillDialogModel.setProperty("/hasTableValidationErrors", false);
477
- easyFillDialogModel.setProperty("/tableValidationMessage", "");
478
- return;
479
- }
480
- const sSummary = aValidationErrors.slice(0, 3).map(function(oError) {
481
- return oError.collectionLabel + " / " + oError.rowLabel + " / " + oError.propertyLabel;
482
- }).join(", ");
483
- easyFillDialogModel.setProperty("/hasTableValidationErrors", true);
484
- easyFillDialogModel.setProperty(
485
- "/tableValidationMessage",
486
- oRb.getText("EASY_FILL_TABLE_VALIDATION_MESSAGE", [sSummary])
296
+ // Submit to AI
297
+ var aiCallResult = await EasyFillAIOrchestrator.submitToAI(
298
+ oController.byId("EasyFillInputTextArea").getValue(),
299
+ mFieldMapping,
300
+ oTemplateUtils
487
301
  );
488
- }
489
302
 
490
- function resetEasyFill(bRemoveTextAreaValue) {
491
- const easyFillDialogModel = oController.getView().getModel("easyFillDialogModel");
492
- const oTablePreviewContainer = oController.byId("EasyFillTablePreviewContainer");
493
- if (oTablePreviewContainer) {
494
- oTablePreviewContainer.destroyItems();
495
- }
496
- revertStagedCollectionUpdates();
497
- resetTransientContext();
498
- easyFillDialogModel.setProperty("/isFormVisible",false);
499
- easyFillDialogModel.setProperty("/isIllustrationVisible",true);
500
- easyFillDialogModel.setProperty("/isSaveEnabled",false);
501
- easyFillDialogModel.setProperty("/stateType","Initial");
502
- easyFillDialogModel.setProperty("/feedbackEnabled",true);
503
- easyFillDialogModel.setProperty("/feedbackPressedUp",false);
504
- easyFillDialogModel.setProperty("/feedbackPressedDown",false);
505
- easyFillDialogModel.setProperty("/isFullScreen",true);
506
- easyFillDialogModel.setProperty("/hasTableUpdates",false);
507
- easyFillDialogModel.setProperty("/hasTableValidationErrors",false);
508
- easyFillDialogModel.setProperty("/tableValidationMessage","");
509
- easyFillDialogModel.setProperty("/easyFillMode",EasyFillLayoutMode.CondensedMode);
510
- mCollectionUpdates = Object.create(null);
511
- if (bRemoveTextAreaValue) {
512
- oController.byId("EasyFillInputTextArea").setValue("");
513
- easyFillDialogModel.setProperty("/isEasyFillButtonEnabled",false);
514
- }
515
- easyFillDialogModel.setProperty("/llmData",{});
516
- easyFillDialogModel.setProperty("/sections",[]);
303
+ // Reset editable fields tracker for fresh population
304
+ oModel.setProperty("/isBusy", false);
517
305
 
518
- }
306
+ // Convert date strings in AI response to JS Date objects
307
+ EasyFillAIOrchestrator.formatDateValues(aiCallResult, mFieldMapping);
519
308
 
520
- function getUpdatableAndNonUpdatableFields(oAiCallResult) {
521
- const oEntityType = oTemplateUtils.oCommonUtils.getMetaModelEntityType(oController.getOwnerComponent().getEntitySet());
522
- const updatableFields = {};
523
- const nonUpdatableFields = {};
309
+ var sSelectedKey = oModel.getProperty("/easyFillMode") || EasyFillLayoutMode.CondensedMode;
310
+ oModel.setProperty("/easyFillMode", sSelectedKey);
524
311
 
525
- Object.keys(oAiCallResult).forEach((sKey)=>{
526
- const oProperty = oEntityType.property.find((oProperty)=>oProperty.name === sKey);
527
- if (!oProperty) {
528
- nonUpdatableFields[sKey] = oAiCallResult[sKey];
312
+ if (aiCallResult.success) {
313
+ // Split AI response into simple fields (SmartForm) and collection rows (SmartTable)
314
+ // Pass mCollectionState so the splitter can check which tables allow creation
315
+ var oSplitResult = EasyFillAIOrchestrator.splitAIResultByCollection(
316
+ aiCallResult.data, mFieldMapping, EasyFillFieldCollector, oObjectPage, mCollectionState
317
+ );
318
+ var simpleFields = oSplitResult.simpleFields;
319
+ var collectionFields = oSplitResult.collectionFields;
320
+
321
+ // Separate updatable from non-updatable fields
322
+ var sEntitySetName = oController.getOwnerComponent().getEntitySet();
323
+ var oEntityType = oTemplateUtils.oCommonUtils.getMetaModelEntityType(sEntitySetName);
324
+ var oMetaModel = oObjectPageModel.getMetaModel();
325
+ var oEntitySet = oMetaModel.getODataEntitySet(sEntitySetName);
326
+ var oSplit = EasyFillFieldCollector.getUpdatableAndNonUpdatableFields(
327
+ simpleFields, oEntityType, oObjectPageContext, oEntitySet
328
+ );
329
+ mUpdatableLookupMap = oSplit.updatableFields;
330
+ var mNonUpdatableFields = oSplit.nonUpdatableFields;
331
+
332
+ // Build section tree for selected view mode
333
+ var aData = (sSelectedKey === EasyFillLayoutMode.CondensedMode)
334
+ ? EasyFillFieldCollector.getSectionsForCondensedView(simpleFields, mUpdatableLookupMap, mNonUpdatableFields)
335
+ : EasyFillFieldCollector.getSectionsForObjectPageView(oEntityType, simpleFields, oRb, mNonUpdatableFields);
336
+
337
+ // Reset form (destroy previous SmartForm content + revert table staging)
338
+ // then create a fresh transient context for the new AI result.
339
+ // Restore the user's chosen layout mode afterwards because _resetEasyFill
340
+ // unconditionally resets it to CondensedMode — without this, renderCollectionPreviews
341
+ // would see the wrong mode and embed table sections inside the SmartForm SubSection
342
+ // instead of adding them as standalone OPL sections (OP mode) or vice-versa.
343
+ _resetEasyFill();
344
+ oModel.setProperty("/easyFillMode", sSelectedKey);
345
+ oTransientContextForEasyFill = EasyFillFormRenderer.makeTransientContext(
346
+ null, oObjectPageModel, oController
347
+ );
348
+
349
+ // Update dialog model with section data and AI result.
350
+ // llmData must be set BEFORE sections so that when the sections binding fires
351
+ // and triggers the populateSmartFields formatter synchronously, the AI-suggested
352
+ // values are already available and can be written to the transient context.
353
+ oModel.setProperty("/llmData", simpleFields);
354
+ oModel.setProperty("/updatableFields", mUpdatableLookupMap);
355
+ oModel.setProperty("/nonUpdatableFields", mNonUpdatableFields);
356
+ oModel.setProperty("/sections", aData);
357
+ oModel.setProperty("/hasNonUpdatableFields", Object.keys(mNonUpdatableFields).length > 0);
358
+
359
+ // Show warning if AI attempted to add multiple new rows but only one was accepted
360
+ oModel.setProperty("/attemptedMultipleNewRows", oSplitResult.attemptedMultipleNewRows === true);
361
+
362
+ // Process collection/table updates
363
+ mCollectionUpdates = collectionFields;
364
+ mLastFieldMapping = mFieldMapping;
365
+ var mRejectedNewRows = oSplitResult.collectionsWithRejectedNewRows || {};
366
+ // hasTableUpdates is true when there are accepted updates OR rejected-only tables to show
367
+ var bHasTableUpdates = Object.keys(mCollectionUpdates).length > 0 ||
368
+ Object.keys(mRejectedNewRows).length > 0;
369
+ oModel.setProperty("/hasTableUpdates", bHasTableUpdates);
370
+
371
+ // Render collection previews into the table preview container
372
+ EasyFillTableRenderer.renderCollectionPreviews(
373
+ mCollectionUpdates,
374
+ mFieldMapping,
375
+ [],
376
+ oController,
377
+ oRb,
378
+ mCollectionState,
379
+ oTableStagingState,
380
+ oObjectPageModel,
381
+ _validateFieldsAndUpdateConfirmState,
382
+ false,
383
+ mRejectedNewRows
384
+ );
385
+
386
+ _validateFieldsAndUpdateConfirmState();
387
+
388
+ var iTotalFields = Object.keys(oSplit.updatableFields).length + Object.keys(oSplit.nonUpdatableFields).length;
389
+ if (iTotalFields === 0 && !bHasTableUpdates) {
390
+ oModel.setProperty("/stateType", "NoEntries");
529
391
  return;
530
392
  }
531
- if (isEditable(oProperty)) {
532
- updatableFields[sKey] = oAiCallResult[sKey];
533
- } else {
534
- nonUpdatableFields[sKey] = oAiCallResult[sKey];
535
- }
536
393
 
537
- });
538
- return {updatableFields,nonUpdatableFields};
394
+ oModel.setProperty("/isIllustrationVisible", false);
395
+ oModel.setProperty("/isFormVisible", iTotalFields > 0 || bHasTableUpdates);
396
+ } else {
397
+ // AI call failed — show error illustration
398
+ oModel.setProperty("/stateType", "Error");
399
+ }
539
400
  }
540
401
 
541
- //Checks if the field is editable or not
542
-
543
- // It can be set in 2 ways using field-control, it would be present when sap:updatable attribute is not present
544
- // 1. By attaching to an existing metadata property, sap:field-control-> path for the property
545
- //Below are the values by which the editable state would be defined
546
-
547
402
  /**
548
- * Value Meaning
549
- -----------------------------------------------
550
- 0 Hidden (field not shown at all)
551
- 1 Read-only (display only)
552
- 3 Editable
553
- 7 Mandatory (Still Editable)
554
- */
555
-
556
- // 2. By Adding to an annotation "com.sap.vocabularies.Common.v1.FieldControl"
557
-
558
- //Since "com.sap.vocabularies.Common.v1.FieldControl" is an V4 annotation, it would be given the first priority
559
-
560
- function isEditable(oProperty) {
561
- return isEditableByContextObject(oProperty, oObjectPageContext && oObjectPageContext.getObject && oObjectPageContext.getObject());
562
- }
563
-
564
- function isEditableByContextObject(oProperty, oContextObject) {
565
- if (!oProperty) {
566
- return false;
567
- }
568
-
569
- //Check for sap:updatable
570
- const sUpdatable = oProperty["sap:updatable"];
571
- if (sUpdatable) {
572
- return sUpdatable === "false" ? false : true;
573
- }
574
-
575
- //Check for "com.sap.vocabularies.Common.v1.FieldControl"
576
- const oAnnotation = oProperty["com.sap.vocabularies.Common.v1.FieldControl"];
577
-
578
- if (oAnnotation && oAnnotation["EnumMember"]) {
579
- const enumNumberType = oAnnotation["EnumMember"];
580
- return enumNumberType === 'com.sap.vocabularies.Common.v1.FieldControlType/ReadOnly' ? false : true;
581
- }
582
-
583
-
584
- //Check for "sap:field-control"
585
- const sFieldControl = oProperty["sap:field-control"];
586
-
587
- if (sFieldControl) {
588
- const iFieldControlValue = oContextObject && oContextObject[sFieldControl];
589
- return iFieldControlValue === 1 ? false : true;
403
+ * Runs SmartForm.check() on every SmartForm in the EasyFill ObjectPageLayout.
404
+ * Returns true if all SmartForms pass validation (or none exist yet).
405
+ * As a side effect, check() visually marks invalid fields in the dialog.
406
+ *
407
+ * SmartForm.check() returns either:
408
+ * - string[] (synchronous validationMode) — field IDs with errors
409
+ * - Promise<Object[]> (asynchronous validationMode) — resolves with field objects with errors
410
+ * Both are normalised via Promise.resolve() so the caller can always await the result.
411
+ *
412
+ * @returns {Promise<boolean>} resolves to true when all SmartForms have zero errors
413
+ */
414
+ async function _checkSmartFormsValidity() {
415
+ var oObjectPageLayout = oController.byId("EasyFillObjectPageLayout");
416
+ if (!oObjectPageLayout) {
417
+ return true;
590
418
  }
591
419
 
592
- //If no restriction is present then by default all fields are editable
593
- return true;
594
- }
595
-
596
- function splitAIResultByCollection(oAiData, mFieldMapping) {
597
- var oSplitResult = {
598
- simpleFields: {},
599
- collectionFields: {}
600
- };
601
- Object.keys(oAiData || {}).forEach(function(sFieldName) {
602
- var oFieldMeta = mFieldMapping[sFieldName];
603
- var vFieldValue = oAiData[sFieldName];
604
- var aCollectionRows = null;
605
- if (Array.isArray(vFieldValue)) {
606
- aCollectionRows = vFieldValue;
607
- } else if (vFieldValue && Array.isArray(vFieldValue.rows)) {
608
- aCollectionRows = vFieldValue.rows;
609
- }
610
- if (oFieldMeta && oFieldMeta.isCollection === true && aCollectionRows) {
611
- var aRowUpdates = aCollectionRows.filter(function(oRow) {
612
- return oRow && typeof oRow === "object" && (typeof oRow._contextPath === "string" || typeof oRow._rowIndex === "number");
420
+ // Collect one Promise<errorArray> per SmartForm found in the layout
421
+ var aCheckPromises = [];
422
+ oObjectPageLayout.getSections().forEach(function(oSection) {
423
+ oSection.getSubSections().forEach(function(oSubSection) {
424
+ oSubSection.getBlocks().forEach(function(oBlock) {
425
+ var aSmartFormCandidates = [];
426
+ if (oBlock.isA("sap.ui.comp.smartform.SmartForm")) {
427
+ aSmartFormCandidates.push(oBlock);
428
+ } else if (typeof oBlock.getItems === "function") {
429
+ // In condensed mode the SmartForm is nested inside a wrapper VBox;
430
+ // look one level deeper to find it (mirrors EasyFillFormRenderer).
431
+ oBlock.getItems().forEach(function(oItem) {
432
+ if (oItem.isA("sap.ui.comp.smartform.SmartForm")) {
433
+ aSmartFormCandidates.push(oItem);
434
+ }
435
+ });
436
+ }
437
+ aSmartFormCandidates.forEach(function(oSmartForm) {
438
+ // .catch(() => []) absorbs internal SmartField metadata-promise rejections
439
+ // that fire when SmartFields are destroyed during a mode switch.
440
+ aCheckPromises.push(
441
+ Promise.resolve(oSmartForm.check()).catch(function() { return []; })
442
+ );
443
+ });
613
444
  });
614
-
615
- // For current development, we are only supporting one SmartTable
616
- // in future if we support multiple SmartTable in the same OP, then we can change the structure of collectionFields to support multiple tables
617
- const {isValid} = isValidSmartTable(oFieldMeta.entitySet);
618
- if (aRowUpdates.length > 0 && Object.keys(oSplitResult.collectionFields).length === 0 && isValid) {
619
- oSplitResult.collectionFields[sFieldName] = aRowUpdates;
620
- }
621
- } else {
622
- oSplitResult.simpleFields[sFieldName] = vFieldValue;
623
- }
445
+ });
624
446
  });
625
- return oSplitResult;
626
- }
627
447
 
628
- function isValidSmartTable(sEntitySet) {
629
- var aSmartTables = getObjectPageSmartTables();
630
- const oSmartTable = aSmartTables.find(function(oSmartTable) {
631
- return oSmartTable.getEntitySet() === sEntitySet;
448
+ // Resolve all checks in parallel; valid only when every SmartForm has no errors
449
+ var aResults = await Promise.all(aCheckPromises);
450
+ return aResults.every(function(aErrors) {
451
+ return Array.isArray(aErrors) && aErrors.length === 0;
632
452
  });
633
- return {
634
- isValid: !!oSmartTable,
635
- smartTable: oSmartTable
636
- };
637
453
  }
638
454
 
639
- function getObjectPageSmartTables() {
640
- if (!oObjectPage || !oObjectPage.findAggregatedObjects) {
641
- return [];
642
- }
643
- //Currently we are looping all the tables in the ObjectPage, but it might contain custom smarttables
644
- //In future we have to read the table data from annotations/manifest so that we dont confuse with custom smart tables
645
- return oObjectPage.findAggregatedObjects(true, function(oControl) {
646
- const isSmartTable = controlHelper.isSmartTable(oControl);
647
- //Currently restricting it to Responsive Table and UITable, in future we have to extend it for other table types as well
648
- return isSmartTable && (oControl.getTable().isA("sap.m.Table") || oControl.getTable().isA("sap.ui.table.Table"));
649
- }) || [];
455
+ /**
456
+ * Re-computes isSaveEnabled from the combined SmartForm and table SmartField validity
457
+ * flag. Called as the single source of truth for the Confirm button state.
458
+ *
459
+ * @param {boolean} bAllFieldsValid - true when both SmartForm and table SmartField checks pass
460
+ */
461
+ function _computeSaveEnabled(bAllFieldsValid) {
462
+ var bHasScalarChanges = Object.keys(mUpdatableLookupMap || {}).length > 0;
463
+ var bHasTableChanges = EasyFillApplyHandler.hasStagedCollectionChanges(oTableStagingState, mCollectionState);
464
+ var bHasChanges = bHasScalarChanges || bHasTableChanges;
465
+ oDialogModel().setProperty("/isSaveEnabled", bAllFieldsValid && bHasChanges);
650
466
  }
651
467
 
652
- function appendCollectionMapForLLM(mPropertyMap) {
653
- mCollectionRowContexts = Object.create(null);
654
- mCollectionRowContextsByPath = Object.create(null);
655
- mCollectionProperties = Object.create(null);
656
- mCollectionEntityTypes = Object.create(null);
657
- var aSmartTables = getObjectPageSmartTables();
658
- if (!aSmartTables.length) {
659
- return;
468
+ /**
469
+ * Runs SmartField.checkValuesValidity() on every editable table SmartField.
470
+ * Returns true if all fields pass (or no table exists yet).
471
+ *
472
+ * checkValuesValidity() returns a Promise<boolean> (true = valid).
473
+ *
474
+ * @returns {Promise<boolean>} resolves to true when all table SmartFields are valid
475
+ */
476
+ async function _checkTableSmartFieldsValidity() {
477
+ var sMarker = "easyfillTableSection";
478
+ var oObjectPageLayout = oController.byId("EasyFillObjectPageLayout");
479
+ if (!oObjectPageLayout) {
480
+ return true;
660
481
  }
661
- var oMetaModel = oObjectPageModel && oObjectPageModel.getMetaModel();
662
- aSmartTables.forEach(function(oSmartTable) {
663
- var oHandler = oTemplateUtils.oServices.oPresentationControlHandlerFactory.getPresentationControlHandler(oSmartTable);
664
- if (!oHandler) {
665
- return;
666
- }
667
- var sNavProperty = extractNavigationPropertyName(oHandler.getBindingPath());
668
- if (!sNavProperty) {
669
- return;
670
- }
671
- var aVisibleProps = (oHandler.findVisibleColumnProperties && oHandler.findVisibleColumnProperties()) || [];
672
- if (!aVisibleProps.length) {
673
- return;
674
- }
675
- var aRowContexts = (oHandler.getCurrentContexts && oHandler.getCurrentContexts()) || [];
676
- var sEntitySetName = oSmartTable.getEntitySet();
677
- var oEntitySet = oMetaModel.getODataEntitySet(oSmartTable.getEntitySet());
678
- var oEntityType = oEntitySet && oMetaModel.getODataEntityType(oEntitySet.entityType);
679
- if (!oEntityType || !oEntityType.property) {
680
- return;
681
- }
682
- var mPropertyByName = Object.create(null);
683
- oEntityType.property.forEach(function(oProperty) {
684
- mPropertyByName[oProperty.name] = oProperty;
685
- });
686
482
 
687
- var oItemProperties = {};
688
- aVisibleProps.forEach(function(sPropName) {
689
- var oProperty = mPropertyByName[sPropName];
690
- if (!oProperty) {
691
- return;
692
- }
693
- oItemProperties[sPropName] = {
694
- description: getLabel(oProperty) || sPropName,
695
- dataType: oProperty.type
696
- };
483
+ var aCheckPromises = [];
484
+
485
+ function scanTableForSmartFields(oTable) {
486
+ if (!oTable.isA("sap.m.Table") || !oTable.getVisible()) { return; }
487
+ oTable.getItems().forEach(function(oListItem) {
488
+ if (!oListItem.isA("sap.m.ColumnListItem")) { return; }
489
+ oListItem.getCells().forEach(function(oCell) {
490
+ if (!oCell.isA("sap.ui.comp.smartfield.SmartField") || !oCell.getEditable()) { return; }
491
+ var bValueStateOk = oCell.getValueState() !== "Error";
492
+ aCheckPromises.push(
493
+ Promise.resolve(oCell.checkValuesValidity()).then(function(bValid) {
494
+ return bValueStateOk && bValid !== false;
495
+ })
496
+ );
497
+ });
697
498
  });
499
+ }
698
500
 
699
- if (!Object.keys(oItemProperties).length) {
700
- return;
701
- }
501
+ function scanVBox(oVBox) {
502
+ oVBox.getItems().forEach(function(oItem) { scanTableForSmartFields(oItem); });
503
+ }
702
504
 
703
- var aCurrentItems = [];
704
- var mRowsByPath = Object.create(null);
705
- aRowContexts.forEach(function(oContext, iIndex) {
706
- if (!oContext || !oContext.getObject) {
707
- return;
708
- }
709
- var sContextPath = oContext.getPath && oContext.getPath();
710
- var oRowData = {_rowIndex: iIndex};
711
- if (typeof sContextPath === "string") {
712
- oRowData._contextPath = sContextPath;
713
- mRowsByPath[sContextPath] = {
714
- context: oContext,
715
- rowIndex: iIndex
716
- };
717
- }
718
- Object.keys(oItemProperties).forEach(function(sPropName) {
719
- var vValue = oContext.getProperty(sPropName);
720
- if (typeof vValue !== "object") {
721
- oRowData[sPropName] = vValue;
505
+ oObjectPageLayout.getSections().forEach(function(oSection) {
506
+ var bIsTableSection = oSection.getCustomData().some(function(oData) {
507
+ return oData.getKey() === sMarker;
508
+ });
509
+
510
+ oSection.getSubSections().forEach(function(oSubSection) {
511
+ oSubSection.getBlocks().forEach(function(oBlock) {
512
+ if (!oBlock.isA("sap.m.VBox")) { return; }
513
+ var bIsMarkedBlock = oBlock.getCustomData().some(function(oData) {
514
+ return oData.getKey() === sMarker;
515
+ });
516
+ // Scan VBoxes that are either:
517
+ // (a) directly marked (condensed mode: injected into Filled Fields SubSection)
518
+ // (b) inside a marked standalone section (OP mode)
519
+ if (bIsMarkedBlock || bIsTableSection) {
520
+ scanVBox(oBlock);
722
521
  }
723
522
  });
724
- aCurrentItems.push(oRowData);
725
523
  });
726
-
727
- mCollectionRowContexts[sNavProperty] = aRowContexts;
728
- mCollectionRowContextsByPath[sNavProperty] = mRowsByPath;
729
- mCollectionProperties[sNavProperty] = mPropertyByName;
730
- mCollectionEntityTypes[sNavProperty] = oEntityType;
731
- mPropertyMap[sNavProperty] = {
732
- description: typeof oSmartTable.getHeader() === "string" ? oSmartTable.getHeader() : sNavProperty,
733
- dataType: "Collection",
734
- isCollection: true,
735
- itemProperties: oItemProperties,
736
- currentItems: aCurrentItems,
737
- entitySet: sEntitySetName,
738
- section: typeof oSmartTable.getHeader() === "string" ? oSmartTable.getHeader() : sNavProperty
739
- };
740
524
  });
741
- }
742
525
 
743
- function extractNavigationPropertyName(sBindingPath) {
744
- if (!sBindingPath || typeof sBindingPath !== "string") {
745
- return null;
746
- }
747
- var aParts = sBindingPath.split("/").filter(Boolean);
748
- return aParts.length ? aParts[aParts.length - 1] : null;
526
+ var aResults = await Promise.all(aCheckPromises);
527
+ return aResults.every(function(bValid) { return bValid === true; });
749
528
  }
750
529
 
751
- function resolveCollectionRowContext(sCollectionName, oRowUpdate) {
752
- var mByPath = mCollectionRowContextsByPath[sCollectionName] || Object.create(null);
753
- var sContextPath = oRowUpdate && oRowUpdate._contextPath;
754
- if (typeof sContextPath === "string" && mByPath[sContextPath]) {
755
- return mByPath[sContextPath];
756
- }
757
- var iRowIndex = oRowUpdate && oRowUpdate._rowIndex;
758
- var aRowContexts = mCollectionRowContexts[sCollectionName] || [];
759
- if (typeof iRowIndex === "number" && aRowContexts[iRowIndex]) {
760
- return {
761
- context: aRowContexts[iRowIndex],
762
- rowIndex: iRowIndex
763
- };
764
- }
765
- return null;
766
- }
767
-
768
- function readValueListByEquality(sCollectionPath, sValueListProperty, vValue) {
769
- return new Promise(function(resolve) {
770
- oObjectPageModel.read("/" + sCollectionPath, {
771
- filters: [
772
- new Filter({
773
- path: sValueListProperty,
774
- operator: FilterOperator.EQ,
775
- value1: vValue
776
- })
777
- ],
778
- success: function(oResponse) {
779
- resolve(!!(oResponse && Array.isArray(oResponse.results) && oResponse.results.length > 0));
780
- },
781
- error: function() {
782
- resolve(true);
783
- }
784
- });
785
- });
530
+ /**
531
+ * Called after every SmartField change event (both form and table) and once after each
532
+ * group/table is populated. Validates all SmartForms and all table SmartFields and
533
+ * updates the Confirm button enabled state.
534
+ */
535
+ function _validateFieldsAndUpdateConfirmState() {
536
+ setTimeout(function() {
537
+ Promise.all([
538
+ _checkSmartFormsValidity(),
539
+ _checkTableSmartFieldsValidity()
540
+ ]).then(function(aResults) {
541
+ _computeSaveEnabled(aResults[0] && aResults[1]);
542
+ }).catch(Function.prototype);
543
+ }, 0);
786
544
  }
787
545
 
788
- async function validateCollectionUpdatesAgainstValueHelp(mFieldMapping) {
789
- const oMetaModel = oObjectPageModel && oObjectPageModel.getMetaModel();
790
- if (!oMetaModel) {
791
- return [];
792
- }
793
- const aValidationErrors = [];
794
- const aCollectionNames = Object.keys(mCollectionUpdates || {});
795
- for (const sCollectionName of aCollectionNames) {
796
- const aRowUpdates = mCollectionUpdates[sCollectionName] || [];
797
- const mCollectionPropertyMap = mCollectionProperties[sCollectionName] || Object.create(null);
798
- const oCollectionEntityType = mCollectionEntityTypes[sCollectionName];
799
- const sCollectionLabel = (mFieldMapping[sCollectionName] && mFieldMapping[sCollectionName].description) || sCollectionName;
800
- if (!oCollectionEntityType) {
801
- continue;
802
- }
803
- for (const oRowUpdate of aRowUpdates) {
804
- const oResolvedRow = resolveCollectionRowContext(sCollectionName, oRowUpdate);
805
- if (!oResolvedRow || !oResolvedRow.context) {
806
- continue;
807
- }
808
- const oRowContext = oResolvedRow.context;
809
- const oRowObject = oRowContext.getObject && oRowContext.getObject();
810
- const sRowLabel = oRb.getText("EASY_FILL_TABLE_ROW_LABEL", [String((oResolvedRow.rowIndex || 0) + 1)]);
811
- for (const sPropertyName of Object.keys(oRowUpdate)) {
812
- if (sPropertyName === "_rowIndex" || sPropertyName === "_contextPath") {
813
- continue;
814
- }
815
- const oPropertyMeta = mCollectionPropertyMap[sPropertyName];
816
- if (!oPropertyMeta || !isEditableByContextObject(oPropertyMeta, oRowObject)) {
817
- continue;
818
- }
819
- if (!GenericAnnotationHelper.isValueHelpTableAvailable(oPropertyMeta)) {
820
- continue;
821
- }
822
- const vUpdatedValue = oRowUpdate[sPropertyName];
823
- if (vUpdatedValue === null || vUpdatedValue === undefined || typeof vUpdatedValue === "object") {
824
- continue;
825
- }
826
- try {
827
- const sPropertyPath = oMetaModel.getODataProperty(oCollectionEntityType, sPropertyName, true);
828
- if (!sPropertyPath) {
829
- continue;
830
- }
831
- const oPropertyContext = oMetaModel.createBindingContext(sPropertyPath);
832
- const oValueLists = await oMetaModel.getODataValueLists(oPropertyContext);
833
- const oDefaultValueList = oValueLists && oValueLists[""];
834
- if (!oDefaultValueList || !oDefaultValueList.CollectionPath || !oDefaultValueList.CollectionPath.String) {
835
- continue;
836
- }
837
- let sValueListProperty = sPropertyName;
838
- if (Array.isArray(oDefaultValueList.Parameters)) {
839
- const oParameter = oDefaultValueList.Parameters.find(function(oParam) {
840
- return (
841
- oParam.RecordType === "com.sap.vocabularies.Common.v1.ValueListParameterInOut" &&
842
- oParam.LocalDataProperty &&
843
- oParam.LocalDataProperty.PropertyPath === sPropertyName
844
- );
845
- });
846
- if (oParameter && oParameter.ValueListProperty && oParameter.ValueListProperty.String) {
847
- sValueListProperty = oParameter.ValueListProperty.String;
848
- }
849
- }
850
- const bValueFound = await readValueListByEquality(
851
- oDefaultValueList.CollectionPath.String,
852
- sValueListProperty,
853
- vUpdatedValue
854
- );
855
- if (!bValueFound) {
856
- aValidationErrors.push({
857
- collectionName: sCollectionName,
858
- collectionLabel: sCollectionLabel,
859
- rowLabel: sRowLabel,
860
- rowIndex: oResolvedRow.rowIndex,
861
- rowPath: oRowContext.getPath && oRowContext.getPath(),
862
- propertyName: sPropertyName,
863
- propertyLabel: getLabel(oPropertyMeta) || sPropertyName
864
- });
865
- }
866
- } catch (e) {
867
- // If metadata/value list cannot be resolved, we keep current behavior and do not block save.
868
- }
869
- }
546
+ /**
547
+ * Resets the EasyFill dialog to its initial state.
548
+ * Delegates to EasyFillApplyHandler.resetEasyFill with the current state references.
549
+ *
550
+ * @param {boolean} [bRemoveTextAreaValue=false] - if true, also clears the text area input
551
+ */
552
+ function _resetEasyFill(bRemoveTextAreaValue) {
553
+ mLastFieldMapping = null;
554
+ EasyFillApplyHandler.resetEasyFill(
555
+ !!bRemoveTextAreaValue,
556
+ oDialogModel(),
557
+ oController,
558
+ mCollectionUpdates,
559
+ EasyFillLayoutMode,
560
+ function() {
561
+ EasyFillTableRenderer.resetStagedCollectionContexts(oTableStagingState, oObjectPageModel);
562
+ },
563
+ function() {
564
+ EasyFillFormRenderer.resetTransientContext(oTransientContextForEasyFill, oObjectPageModel);
565
+ oTransientContextForEasyFill = null;
870
566
  }
871
- }
872
- return aValidationErrors;
567
+ );
873
568
  }
874
569
 
875
- function stringifyCellValue(vValue) {
876
- if (vValue === null || vValue === undefined) {
877
- return "";
878
- }
879
- if (vValue instanceof Date) {
880
- return vValue.toISOString();
570
+ /**
571
+ * Activates side effects for all editable SmartFields in the EasyFill SmartForm.
572
+ * Called after applyAndSave so that side effects are triggered on the original
573
+ * ObjectPage context (not the transient context used while the dialog was open).
574
+ *
575
+ * Collects field group IDs from the inner controls of every editable SmartField,
576
+ * updates the view's CustomData for each field group to reference the OP context,
577
+ * then registers the group change and triggers field-group validation on the view.
578
+ */
579
+ function _activateSideEffects() {
580
+ var oObjectPageLayout = oController.byId("EasyFillObjectPageLayout");
581
+ if (!oObjectPageLayout) {
582
+ return;
881
583
  }
882
- return String(vValue);
883
- }
884
-
885
- function createPreviewTable(aPropertyNames, oItemProperties, aRows, fnCellFormatter) {
886
- var aColumns = aPropertyNames.map(function(sPropertyName) {
887
- return new Column({
888
- header: new Text({ text: (oItemProperties[sPropertyName] && oItemProperties[sPropertyName].description) || sPropertyName })
889
- });
890
- });
891
584
 
892
- var aItems = aRows.map(function(oRow) {
893
- return new ColumnListItem({
894
- cells: aPropertyNames.map(function(sPropertyName) {
895
- var sText = fnCellFormatter
896
- ? fnCellFormatter(oRow, sPropertyName, stringifyCellValue(oRow[sPropertyName]))
897
- : stringifyCellValue(oRow[sPropertyName]);
898
- return new Text({ text: sText });
899
- })
900
- });
901
- });
902
-
903
- return new Table({
904
- columns: aColumns,
905
- items: aItems,
906
- fixedLayout: false,
907
- autoPopinMode: true
908
- }).addStyleClass("sapSmartTemplatesObjectPageEasyFillTablePreview");
909
- }
910
-
911
- function stageCollectionUpdatesForPreview() {
912
- mStagedRowValues = Object.create(null);
913
- Object.keys(mCollectionUpdates).forEach(function(sCollectionName) {
914
- var mCollectionPropertyMap = mCollectionProperties[sCollectionName] || Object.create(null);
915
- var aRowUpdates = mCollectionUpdates[sCollectionName] || [];
916
- aRowUpdates.forEach(function(oRowUpdate) {
917
- var oResolved = resolveCollectionRowContext(sCollectionName, oRowUpdate);
918
- if (!oResolved || !oResolved.context) { return; }
919
- var oContext = oResolved.context;
920
- var sPath = oContext.getPath && oContext.getPath();
921
- if (!sPath) { return; }
922
- if (!mStagedRowValues[sPath]) {
923
- mStagedRowValues[sPath] = { context: oContext, originalValues: {} };
924
- }
925
- var oRowObject = oContext.getObject && oContext.getObject();
926
- Object.keys(oRowUpdate).forEach(function(sPropName) {
927
- if (sPropName === "_rowIndex" || sPropName === "_contextPath") { return; }
928
- var oPropertyMeta = mCollectionPropertyMap[sPropName];
929
- if (!isEditableByContextObject(oPropertyMeta, oRowObject)) { return; }
930
- if (!Object.prototype.hasOwnProperty.call(mStagedRowValues[sPath].originalValues, sPropName)) {
931
- mStagedRowValues[sPath].originalValues[sPropName] = oContext.getProperty(sPropName);
585
+ // Collect all editable SmartFields from the EasyFill dialog SmartForms
586
+ var aEditableFields = [];
587
+ oObjectPageLayout.getSections().forEach(function(oSection) {
588
+ oSection.getSubSections().forEach(function(oSubSection) {
589
+ oSubSection.getBlocks().forEach(function(oBlock) {
590
+ if (oBlock.isA("sap.ui.comp.smartform.SmartForm")) {
591
+ oBlock.getGroups().forEach(function(oGroup) {
592
+ oGroup.getGroupElements().forEach(function(oGroupElement) {
593
+ oGroupElement.getElements().forEach(function(oElement) {
594
+ if (oElement.isA && oElement.isA("sap.ui.comp.smartfield.SmartField") && oElement.getEditable()) {
595
+ aEditableFields.push(oElement);
596
+ }
597
+ });
598
+ });
599
+ });
932
600
  }
933
- oContext.setProperty(sPropName, oRowUpdate[sPropName]);
934
601
  });
935
602
  });
936
603
  });
937
- }
938
604
 
939
- function revertStagedCollectionUpdates() {
940
- Object.keys(mStagedRowValues).forEach(function(sPath) {
941
- var oEntry = mStagedRowValues[sPath];
942
- var oContext = oEntry.context;
943
- Object.keys(oEntry.originalValues).forEach(function(sPropName) {
944
- oContext.setProperty(sPropName, oEntry.originalValues[sPropName]);
605
+ // Making a Set so that duplicate field group IDs are not passed
606
+ var aFieldGroupIdSet = new Set();
607
+ aEditableFields.forEach(function(oSmartField) {
608
+ // SideEffect would be invoked only for the editable controls inside the SmartField
609
+ var aEditableInnerFields = oSmartField.getInnerControls().filter(function(oControl) {
610
+ return oControl.getEditable();
611
+ });
612
+ aEditableInnerFields.forEach(function(oControl) {
613
+ var aControlFieldGroupId = oControl.getFieldGroupIds();
614
+ if (aControlFieldGroupId.length > 0) {
615
+ aControlFieldGroupId.forEach(function(sFieldGroupId) {
616
+ aFieldGroupIdSet.add(sFieldGroupId);
617
+ });
618
+ }
945
619
  });
946
620
  });
947
- mStagedRowValues = Object.create(null);
948
- }
949
621
 
950
- function buildCollectionPreviewRow(aPropertyNames, oResolvedRow, oRowUpdate, bUseUpdatedValues) {
951
- var oPreviewRow = {};
952
- var oContext = oResolvedRow && oResolvedRow.context;
953
- var sPath = oContext && oContext.getPath && oContext.getPath();
954
- var oStagedEntry = sPath && mStagedRowValues[sPath];
955
- aPropertyNames.forEach(function(sPropertyName) {
956
- if (bUseUpdatedValues && oRowUpdate && Object.prototype.hasOwnProperty.call(oRowUpdate, sPropertyName)) {
957
- oPreviewRow[sPropertyName] = oRowUpdate[sPropertyName];
958
- return;
959
- }
960
- if (!bUseUpdatedValues && oStagedEntry && Object.prototype.hasOwnProperty.call(oStagedEntry.originalValues, sPropertyName)) {
961
- oPreviewRow[sPropertyName] = oStagedEntry.originalValues[sPropertyName];
962
- return;
622
+ var oView = oController.getView();
623
+
624
+ aFieldGroupIdSet.forEach(function(sFieldGroupId) {
625
+ var oID = oView.data(sFieldGroupId);
626
+
627
+ /**
628
+ * Currently, the SmartFields are created using the TransientContext, which means both
629
+ * "context" and "contextObject" refer to the TransientContext instance.
630
+ *
631
+ * However, we need to use the original OP context so that side effects are triggered
632
+ * on the original OP context when the user saves data from the EasyFill dialog back to OP.
633
+ *
634
+ * Therefore, we update the "context" and "contextObject" to reference the original OP context.
635
+ */
636
+ if (oID) {
637
+ oID["context"] = oObjectPageContext.getPath();
638
+ oID["contextObject"] = oObjectPageContext;
639
+ oView.data(sFieldGroupId, oID);
640
+
641
+ // To make sure that all the fields side effect would be triggered
642
+ var sSideEffectQualifier = oTemplateUtils.oServices.oApplicationController._getSideEffectsQualifier(oID.name);
643
+ oTemplateUtils.oServices.oApplicationController.registerGroupChange(sSideEffectQualifier);
963
644
  }
964
- oPreviewRow[sPropertyName] = oContext ? oContext.getProperty(sPropertyName) : "";
965
645
  });
966
- return oPreviewRow;
646
+
647
+ oView.triggerValidateFieldGroup([...aFieldGroupIdSet]);
967
648
  }
968
649
 
969
- function getCollectionRowIdentity(oResolvedRow, oRowUpdate, iFallbackIndex) {
970
- if (oResolvedRow && oResolvedRow.context && oResolvedRow.context.getPath) {
971
- return oResolvedRow.context.getPath();
972
- }
973
- if (oRowUpdate && typeof oRowUpdate._contextPath === "string") {
974
- return oRowUpdate._contextPath;
975
- }
976
- if (oRowUpdate && typeof oRowUpdate._rowIndex === "number") {
977
- return "index:" + String(oRowUpdate._rowIndex);
650
+ /**
651
+ * Activates side effects for all modified table (collection) rows after applyAndSave.
652
+ * For each collection row that had AI-suggested value changes applied, triggers
653
+ * executeSideEffects on the real row context with the list of changed property names.
654
+ *
655
+ * Must be called with mStagedUpdates captured BEFORE applyAndSave, because after
656
+ * applyAndSave the staged values equal the live values so buildStagedCollectionUpdates
657
+ * would return an empty map.
658
+ *
659
+ * @param {object} mStagedUpdates - pre-computed collection updates: { navProperty: [ rowUpdate ] }
660
+ */
661
+ function _activateSideEffectsForTable(mStagedUpdates) {
662
+ if (!mStagedUpdates || !Object.keys(mStagedUpdates).length) {
663
+ return;
978
664
  }
979
- return "row:" + String(iFallbackIndex);
980
- }
981
665
 
982
- function createEditableNewTable(aPropertyNames, oItemProperties, aPreviewRows, sCollectionName) {
983
- var aColumns = aPropertyNames.map(function(sPropName) {
984
- return new Column({
985
- header: new Text({ text: (oItemProperties[sPropName] && oItemProperties[sPropName].description) || sPropName })
986
- });
987
- });
988
- var mCollectionPropertyMap = mCollectionProperties[sCollectionName] || Object.create(null);
989
- var aItems = aPreviewRows.map(function(oPreviewRow) {
990
- var oContext = oPreviewRow.context;
991
- var oRowObject = oContext && oContext.getObject && oContext.getObject();
992
- var aCells = aPropertyNames.map(function(sPropName) {
993
- if (!oContext) {
994
- return new Text({ text: stringifyCellValue(oPreviewRow.newValues[sPropName]) });
666
+ Object.keys(mStagedUpdates).forEach(function(sCollectionName) {
667
+ var aRowUpdates = mStagedUpdates[sCollectionName] || [];
668
+ aRowUpdates.forEach(function(oRowUpdate) {
669
+ var oResolvedRow = EasyFillApplyHandler._resolveCollectionRowContext(sCollectionName, oRowUpdate, mCollectionState);
670
+ if (!oResolvedRow || !oResolvedRow.context) {
671
+ return;
995
672
  }
996
- var oPropertyMeta = mCollectionPropertyMap[sPropName];
997
- var bEditable = isEditableByContextObject(oPropertyMeta, oRowObject);
998
- var oSmartField = new SmartField({
999
- value: "{" + sPropName + "}",
1000
- editable: bEditable,
1001
- enabled: bEditable,
1002
- contextEditable: bEditable,
1003
- // change: onSmartFieldChange.bind(this),
1004
- editableChanged: function(oEvent) {
1005
- // Keep table SmartField editability deterministic in preview mode.
1006
- if (oEvent.getParameter("editable") !== bEditable) {
1007
- oEvent.getSource().setEditable(bEditable);
1008
- }
1009
- }
673
+
674
+ // Collect the names of changed properties (exclude internal metadata fields)
675
+ var aChangedProperties = Object.keys(oRowUpdate).filter(function(sKey) {
676
+ return sKey !== "_rowIndex" && sKey !== "_contextPath";
1010
677
  });
1011
678
 
1012
- if (bEditable) {
1013
- aEditableFields.push(oSmartField);
679
+ if (!aChangedProperties.length) {
680
+ return;
1014
681
  }
1015
- return oSmartField;
682
+
683
+ oTemplateUtils.oServices.oApplicationController.executeSideEffects(
684
+ oResolvedRow.context,
685
+ aChangedProperties
686
+ );
1016
687
  });
1017
- var oItem = new ColumnListItem({ cells: aCells });
1018
- if (oContext) {
1019
- oItem.setBindingContext(oContext);
1020
- }
1021
- return oItem;
1022
688
  });
1023
- return new Table({
1024
- columns: aColumns,
1025
- items: aItems,
1026
- fixedLayout: false,
1027
- autoPopinMode: true
1028
- }).addStyleClass("sapSmartTemplatesObjectPageEasyFillTablePreview");
1029
689
  }
1030
690
 
1031
- function renderCollectionPreviews(mFieldMapping, aTableValidationErrors) {
1032
- var oTablePreviewContainer = oController.byId("EasyFillTablePreviewContainer");
1033
- if (!oTablePreviewContainer) {
1034
- return;
1035
- }
1036
- oTablePreviewContainer.destroyItems();
1037
- // Stage LLM values onto OData row contexts; saves originals for revert on cancel
1038
- stageCollectionUpdatesForPreview();
1039
- var mInvalidCellByPath = Object.create(null);
1040
- (aTableValidationErrors || []).forEach(function(oError) {
1041
- if (!oError || !oError.rowPath) {
1042
- return;
1043
- }
1044
- mInvalidCellByPath[oError.rowPath + "|" + oError.propertyName] = true;
1045
- });
1046
-
1047
- Object.keys(mCollectionUpdates).forEach(function(sCollectionName) {
1048
- var oCollectionMeta = mFieldMapping[sCollectionName];
1049
- if (!oCollectionMeta || !oCollectionMeta.itemProperties) {
1050
- return;
1051
- }
1052
- var aRowUpdates = mCollectionUpdates[sCollectionName] || [];
1053
- var aPropertyNames = Object.keys(oCollectionMeta.itemProperties || {});
1054
- if (!aPropertyNames.length) {
1055
- return;
1056
- }
1057
- var mUpdatesByIdentity = Object.create(null);
1058
- aRowUpdates.forEach(function(oRowUpdate, iUpdateIndex) {
1059
- var oResolvedUpdate = resolveCollectionRowContext(sCollectionName, oRowUpdate);
1060
- mUpdatesByIdentity[getCollectionRowIdentity(oResolvedUpdate, oRowUpdate, iUpdateIndex)] = oRowUpdate;
1061
- });
1062
- var aPreviewRows = [];
1063
- var aCollectionRowContexts = mCollectionRowContexts[sCollectionName] || [];
1064
- aCollectionRowContexts.forEach(function(oContext, iRowIndex) {
1065
- var oResolved = {
1066
- context: oContext,
1067
- rowIndex: iRowIndex
1068
- };
1069
- var sIdentity = getCollectionRowIdentity(oResolved, null, iRowIndex);
1070
- var oRowUpdate = mUpdatesByIdentity[sIdentity];
1071
- aPreviewRows.push({
1072
- context: oContext,
1073
- rowIndex: iRowIndex,
1074
- update: oRowUpdate,
1075
- previousValues: buildCollectionPreviewRow(aPropertyNames, oResolved, oRowUpdate, false),
1076
- newValues: buildCollectionPreviewRow(aPropertyNames, oResolved, oRowUpdate, true)
1077
- });
1078
- });
1079
- aPreviewRows = aPreviewRows.slice(0, MAX_TABLE_PREVIEW_ROWS);
1080
-
1081
- var aPreviousRows = aPreviewRows.map(function(oPreviewRow) {
1082
- return oPreviewRow.previousValues;
1083
- });
1084
-
1085
- if (!aPreviewRows.length) {
1086
- return;
1087
- }
1088
-
1089
- var oPreviousTable = createPreviewTable(aPropertyNames, oCollectionMeta.itemProperties, aPreviousRows);
1090
- var oNewTable = createEditableNewTable(aPropertyNames, oCollectionMeta.itemProperties, aPreviewRows, sCollectionName);
1091
- oPreviousTable.setVisible(false);
1092
-
1093
- var oSectionTitle = new Title({
1094
- level: "H5",
1095
- text: (oCollectionMeta.description || sCollectionName) + " (" + aPreviewRows.length + ")"
1096
- });
1097
-
1098
- var oSegmentedButton = new SegmentedButton({
1099
- selectedKey: "new",
1100
- width:"auto",
1101
- items: [
1102
- new SegmentedButtonItem({ key: "previous", text: oRb.getText("EASY_FILL_TABLE_VIEW_PREVIOUS"), width:"auto" }),
1103
- new SegmentedButtonItem({ key: "new", text: oRb.getText("EASY_FILL_TABLE_VIEW_NEW"), width:"auto" })
1104
- ],
1105
- selectionChange: function() {
1106
- var sKey = oSegmentedButton.getSelectedKey();
1107
- oPreviousTable.setVisible(sKey === "previous");
1108
- oNewTable.setVisible(sKey === "new");
1109
- }
1110
- });
1111
-
1112
- var oHeader = new HBox({
1113
- justifyContent: "SpaceBetween",
1114
- items: [oSectionTitle, oSegmentedButton]
1115
- }).addStyleClass("sapUiSmallMarginTop");
1116
-
1117
- var oSection = new VBox({
1118
- items: [oHeader, oPreviousTable, oNewTable]
1119
- }).addStyleClass("sapSmartTemplatesObjectPageEasyFillTableSection");
691
+ /**
692
+ * Convenience accessor for the easyFillDialogModel JSONModel.
693
+ * @returns {sap.ui.model.json.JSONModel}
694
+ */
695
+ function oDialogModel() {
696
+ return oController.getView().getModel("easyFillDialogModel");
697
+ }
1120
698
 
1121
- oTablePreviewContainer.addItem(oSection);
699
+ function _findSmartTableForNavProperty(aSmartTables, sNavProperty) {
700
+ return aSmartTables.find(function(oTable) {
701
+ var oHandler = oTemplateUtils.oServices.oPresentationControlHandlerFactory.getPresentationControlHandler(oTable);
702
+ if (!oHandler) { return false; }
703
+ var sPath = oHandler.getBindingPath && oHandler.getBindingPath();
704
+ return sPath && sPath.split("/").pop() === sNavProperty;
1122
705
  });
1123
706
  }
1124
707
 
1125
- function isVisible(oProperty) {
1126
-
1127
- //Check for "com.sap.vocabularies.Common.v1.FieldControl"
1128
- const oAnnotation = oProperty["com.sap.vocabularies.Common.v1.FieldControl"];
1129
-
1130
- if (oAnnotation && oAnnotation["EnumMember"]) {
1131
- const enumNumberType = oAnnotation["EnumMember"];
1132
- return enumNumberType === 'com.sap.vocabularies.Common.v1.FieldControlType/Hidden' ? false : true;
708
+ /**
709
+ * Creates new table rows via CommonEventHandlers.addEntry (draft-aware) with navigation
710
+ * suppressed so the user stays on the ObjectPage.
711
+ * Using CommonEventHandlers.addEntry (bSuppressNavigation=true) instead of calling
712
+ * CRUDManager.addEntry directly ensures the SmartTable binding is refreshed and side
713
+ * effects are executed after the row is created — without that refresh the ObjectPage
714
+ * table would not show the newly created row.
715
+ *
716
+ * @param {object} mNewRowValues - map of { navProperty: plainValuesObject }
717
+ * @returns {Promise<void>}
718
+ */
719
+ async function _createNewRows(mNewRowValues) {
720
+ if (!mNewRowValues || !Object.keys(mNewRowValues).length) {
721
+ return;
1133
722
  }
723
+ var aSmartTables = EasyFillFieldCollector.getObjectPageSmartTables(oObjectPage);
724
+ var aNavProperties = Object.keys(mNewRowValues);
1134
725
 
1135
- //Check for Hidden annotation i.e com.sap.vocabularies.UI.v1.Hidden
1136
- const oHiddenAnnotation = oProperty["com.sap.vocabularies.UI.v1.Hidden"];
1137
- if (oHiddenAnnotation) {
1138
- let bIsVisible = false; // <Annotation Term="UI.Hidden"/>
1139
- if (oHiddenAnnotation["Bool"]) {
1140
- bIsVisible = (oHiddenAnnotation["Bool"] === "true") ? false : true;
1141
- } else if (oHiddenAnnotation["Path"]) {
1142
- bIsVisible = !oObjectPageContext.getObject()[oHiddenAnnotation["Path"]];
726
+ for (var i = 0; i < aNavProperties.length; i++) {
727
+ var sNavProp = aNavProperties[i];
728
+ var oSmartTable = _findSmartTableForNavProperty(aSmartTables, sNavProp);
729
+ if (!oSmartTable) {
730
+ continue;
731
+ }
732
+ try {
733
+ // bSuppressNavigation=true: stay on the OP (don't navigate to the new sub-item).
734
+ // bIgnoreTableRefresh not passed (undefined/falsy): the SmartTable binding is
735
+ // refreshed and side effects are executed so the new row becomes visible.
736
+ await oTemplateUtils.oCommonEventHandlers.addEntry(oSmartTable, true, undefined, mNewRowValues[sNavProp]);
737
+ } catch (e) {
738
+ // addEntry handles its own error messaging via CommonEventHandlers/CRUDManager
1143
739
  }
1144
- return bIsVisible;
1145
- }
1146
-
1147
- //Check for "sap:field-control"
1148
- const sFieldControl = oProperty["sap:field-control"];
1149
-
1150
- if (sFieldControl) {
1151
- const iFieldControlValue = oObjectPageContext.getObject()[sFieldControl];
1152
- return iFieldControlValue === 0 ? false : true;
1153
- }
1154
-
1155
- //If no restriction is present then by default all fields are visible
1156
- return true;
1157
- }
1158
-
1159
- function resetTransientContext() {
1160
- if (oTransientContextForEasyFill) {
1161
- oObjectPageModel.resetChanges([oTransientContextForEasyFill.getPath()],false,true);
1162
- oTransientContextForEasyFill = null;
1163
- }
1164
- }
1165
-
1166
- function makeTransientContext() {
1167
- if (oTransientContextForEasyFill) {
1168
- resetTransientContext();
1169
740
  }
1170
- oTransientContextForEasyFill = oObjectPageModel.createEntry("/" + oController.getOwnerComponent().getEntitySet(), {
1171
- "groupId" : "EasyFillFETransientChanges",
1172
- "changeSetId":"EasyFillFETransientChanges"
1173
- });
1174
741
  }
1175
742
 
1176
- //Yet to implement, temporarily disabled
1177
- // function onSmartFieldChange() {
1178
- // validateSmartForm();
1179
- // }
1180
-
743
+ // ─── Public API ──────────────────────────────────────────────────────────
1181
744
 
1182
745
  return {
746
+ /**
747
+ * Entry point called by the ObjectPage ControllerImplementation when the
748
+ * EasyFill button is pressed.
749
+ *
750
+ * If the ObjectPage is already in edit mode, the dialog opens immediately.
751
+ * If not, the page is transitioned to edit mode first and the resulting
752
+ * edit context is passed into the dialog.
753
+ *
754
+ * @param {sap.ui.base.Event} oEvent - the button click event
755
+ */
1183
756
  onEasyFillButtonClick: function(oEvent) {
1184
- // Once you click on the easyFill button the OP should be going to editable state, if its already in editState then directly open the dialog
1185
- //If its in non-editable state we should wait until the context switch, We need that context to finally save the data from the AI into the newly came editable context of the OP
1186
- const isEditable = oController.getView().getModel("ui").getProperty("/editable");
1187
- if (isEditable) {
757
+ var bIsEditable = oController.getView().getModel("ui").getProperty("/editable");
758
+ if (bIsEditable) {
1188
759
  openEasyFillDialog(oEvent);
1189
760
  } else {
1190
- oController._templateEventHandlers.onEditByEasyFill().then((oContext)=>{
1191
- oObjectPageContext = oContext;
1192
- openEasyFillDialog(oEvent,oContext);
1193
- }).catch(Function.prototype);
761
+ // Trigger the OP edit flow and wait for the new edit context before opening
762
+ oController._templateEventHandlers.onEditByEasyFill()
763
+ .then(function(oContext) {
764
+ oObjectPageContext = oContext;
765
+ openEasyFillDialog(oEvent, oContext);
766
+ })
767
+ .catch(Function.prototype);
1194
768
  }
1195
769
  }
1196
770
  };
1197
771
  }
1198
772
 
1199
773
  return BaseObject.extend("sap.suite.ui.generic.template.lib.ai.EasyFill.EasyFillHandler", {
1200
- constructor: function (oState, oController, oTemplateUtils, oObjectPage) {
774
+ /**
775
+ * Constructor — wires all helpers and exposes the public API via getMethods.
776
+ *
777
+ * @param {object} oState - shared template state (passed from ControllerImplementation)
778
+ * @param {object} oController - MVC controller instance
779
+ * @param {object} oTemplateUtils - FE template utilities (services, common utils, etc.)
780
+ * @param {object} oObjectPage - the ObjectPage control instance
781
+ */
782
+ constructor: function(oState, oController, oTemplateUtils, oObjectPage) {
1201
783
  extend(this, getMethods(oState, oController, oTemplateUtils, oObjectPage));
1202
784
  }
1203
785
  });
1204
-
1205
786
  });