@configura/web-api 3.0.0-alpha.0 → 3.0.0-alpha.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.
- package/dist/CatalogueAPI.d.ts +68 -1
- package/dist/CatalogueAPI.js +184 -219
- package/dist/CfgMeasure.js +1 -2
- package/dist/CfgProduct.js +131 -159
- package/dist/io/CfgIOManager.js +2 -11
- package/dist/io/CfgIOProdConfConnector.js +14 -25
- package/dist/io/CfgObservableStateManager.js +1 -1
- package/dist/productConfiguration/CfgFeature.d.ts +5 -0
- package/dist/productConfiguration/CfgFeature.js +64 -54
- package/dist/productConfiguration/CfgOption.d.ts +44 -0
- package/dist/productConfiguration/CfgOption.js +107 -71
- package/dist/productConfiguration/CfgProductConfiguration.js +22 -34
- package/dist/productConfiguration/productParamsGenerator.js +43 -57
- package/dist/productLoader.js +2 -13
- package/dist/syncGroups/SyncGroupsHandler.js +60 -83
- package/dist/syncGroups/SyncGroupsPathHelper.js +5 -5
- package/dist/syncGroups/SyncGroupsState.js +4 -5
- package/dist/syncGroups/SyncGroupsTransaction.js +259 -303
- package/dist/tasks/TaskHandler.js +53 -64
- package/dist/tests/testData/dummyProductForTest.js +4 -1
- package/dist/tests/testData/testDataAdditionalProductInAdditionalProductInProductForTest.js +18 -21
- package/dist/tests/testData/testDataCachedGetProduct.js +20 -20
- package/dist/tests/testData/testDataCachedPostValidate.js +6 -15
- package/dist/tests/testData/testDataProductAggregatedPrice.js +21 -21
- package/dist/tests/testData/testDataUpcharge.js +31 -22
- package/dist/utilitiesCatalogueData.js +21 -9
- package/dist/utilitiesCataloguePermission.js +5 -2
- package/dist/utilitiesConfiguration.js +21 -23
- package/package.json +3 -3
|
@@ -1,12 +1,3 @@
|
|
|
1
|
-
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
2
|
-
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
3
|
-
return new (P || (P = Promise))(function (resolve, reject) {
|
|
4
|
-
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
5
|
-
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
6
|
-
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
7
|
-
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
|
-
});
|
|
9
|
-
};
|
|
10
1
|
import { assert } from "@configura/web-utilities";
|
|
11
2
|
import { CfgProductBubbleMode } from "../CfgProduct.js";
|
|
12
3
|
import { SelectionType, } from "../productConfiguration/CfgFeature.js";
|
|
@@ -57,11 +48,9 @@ export class SyncGroupsTransaction {
|
|
|
57
48
|
this.target = target;
|
|
58
49
|
this.initial = initial;
|
|
59
50
|
}
|
|
60
|
-
static make(syncState, updateMode, product, productLoader, assumeNoStartState) {
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
return t;
|
|
64
|
-
});
|
|
51
|
+
static async make(syncState, updateMode, product, productLoader, assumeNoStartState) {
|
|
52
|
+
const t = new this(syncState, updateMode, productLoader, product, await product.clone(), assumeNoStartState ? undefined : await product.clone());
|
|
53
|
+
return t;
|
|
65
54
|
}
|
|
66
55
|
/************************************************************************
|
|
67
56
|
* Public API (intentionally limited)
|
|
@@ -79,59 +68,55 @@ export class SyncGroupsTransaction {
|
|
|
79
68
|
* but as this method will hand over to applyProduct if it updates the SyncState it can cause
|
|
80
69
|
* the Features to change. And we need to pass that information back to know when to stop.
|
|
81
70
|
*/
|
|
82
|
-
applyRootProduct() {
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
return yield this.updateRootProduct(undefined);
|
|
90
|
-
});
|
|
71
|
+
async applyRootProduct() {
|
|
72
|
+
const productWithInitial = { target: this.target, initial: this.initial };
|
|
73
|
+
if (!(await this.applyProduct(productWithInitial))) {
|
|
74
|
+
// All done!
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
return await this.updateRootProduct(undefined);
|
|
91
78
|
}
|
|
92
79
|
/**
|
|
93
80
|
* This is the entry point for an active (often user) selection of an option.
|
|
94
81
|
*/
|
|
95
|
-
selectOption(optionPath, on) {
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
change = true;
|
|
111
|
-
switch (feature.selectionType) {
|
|
112
|
-
case SelectionType.SelectOne:
|
|
113
|
-
this.affectedSelectOneFeatures.set(feature, AffectedLevel.Set);
|
|
114
|
-
break;
|
|
115
|
-
case SelectionType.SelectMany:
|
|
116
|
-
this.affectedSelectManyOptions.add(option);
|
|
117
|
-
break;
|
|
118
|
-
default:
|
|
119
|
-
throw new Error("Should not happen");
|
|
120
|
-
}
|
|
121
|
-
}
|
|
82
|
+
async selectOption(optionPath, on) {
|
|
83
|
+
const option = SyncGroupsPathHelper.getOptionFromPath(optionPath, this.target);
|
|
84
|
+
const feature = option.parent;
|
|
85
|
+
let change = false;
|
|
86
|
+
let productToValidate = undefined;
|
|
87
|
+
// At this point in handling sync groups we go back to normal selecting.
|
|
88
|
+
//
|
|
89
|
+
// This is because a normal selection can bring features into scope which should push to or
|
|
90
|
+
// (if the SyncGroup is defined) pull from the Sync State.
|
|
91
|
+
//
|
|
92
|
+
// We use BubbleSelected (without validation) as we can delay the validation until after we
|
|
93
|
+
// have applied the SyncGroup (if any).
|
|
94
|
+
if (await feature.selectOption(option, on, ProductConfigurationBubbleMode.BubbleSelected)) {
|
|
95
|
+
productToValidate = feature.parentProduct;
|
|
96
|
+
change = true;
|
|
122
97
|
switch (feature.selectionType) {
|
|
123
98
|
case SelectionType.SelectOne:
|
|
124
|
-
this.
|
|
99
|
+
this.affectedSelectOneFeatures.set(feature, AffectedLevel.Set);
|
|
125
100
|
break;
|
|
126
101
|
case SelectionType.SelectMany:
|
|
127
|
-
this.
|
|
102
|
+
this.affectedSelectManyOptions.add(option);
|
|
128
103
|
break;
|
|
104
|
+
default:
|
|
105
|
+
throw new Error("Should not happen");
|
|
129
106
|
}
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
107
|
+
}
|
|
108
|
+
switch (feature.selectionType) {
|
|
109
|
+
case SelectionType.SelectOne:
|
|
110
|
+
this.applySelectOneFeature(feature, true, false);
|
|
111
|
+
break;
|
|
112
|
+
case SelectionType.SelectMany:
|
|
113
|
+
this.applySelectManyOption(option, true);
|
|
114
|
+
break;
|
|
115
|
+
}
|
|
116
|
+
if (await this.updateRootProduct(productToValidate)) {
|
|
117
|
+
change = true;
|
|
118
|
+
}
|
|
119
|
+
return change;
|
|
135
120
|
}
|
|
136
121
|
/**
|
|
137
122
|
* Overwrites the original Product and SyncGroupState (supplied when creating the Transaction)
|
|
@@ -139,12 +124,10 @@ export class SyncGroupsTransaction {
|
|
|
139
124
|
*
|
|
140
125
|
* @throws error if the transaction has already been closed.
|
|
141
126
|
*/
|
|
142
|
-
commit() {
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
yield this.originalProduct.copyFrom(this.target, false, this.productLoader);
|
|
147
|
-
});
|
|
127
|
+
async commit() {
|
|
128
|
+
assert(this.isClosed === false, "Trying to commit a closed Transaction");
|
|
129
|
+
this.originalSyncState.copyFrom(this.syncState);
|
|
130
|
+
await this.originalProduct.copyFrom(this.target, false, this.productLoader);
|
|
148
131
|
}
|
|
149
132
|
/************************************************************************
|
|
150
133
|
* Updating things with the Transaction's SyncState
|
|
@@ -153,81 +136,73 @@ export class SyncGroupsTransaction {
|
|
|
153
136
|
* Apply current sync groups on those who wants to listen until there is no more to settle.
|
|
154
137
|
* @returns true if at least one Feature changed selected Option
|
|
155
138
|
*/
|
|
156
|
-
updateRootProduct(productToValidate) {
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
return true;
|
|
188
|
-
});
|
|
139
|
+
async updateRootProduct(productToValidate) {
|
|
140
|
+
const productsToValidate = new Set();
|
|
141
|
+
if (productToValidate !== undefined) {
|
|
142
|
+
productsToValidate.add(productToValidate);
|
|
143
|
+
}
|
|
144
|
+
const productWithInitial = { target: this.target, initial: this.initial };
|
|
145
|
+
await this.updateProduct(productWithInitial, productsToValidate);
|
|
146
|
+
if (productsToValidate.size === 0) {
|
|
147
|
+
// All have been settled, continue to pullPhase
|
|
148
|
+
return await this.applyRootProduct();
|
|
149
|
+
}
|
|
150
|
+
if (this.isClosed) {
|
|
151
|
+
// We could exit in more places when the transaction has been aborted, but as
|
|
152
|
+
// revalidate is really the only thing that could be expensive time consuming we only
|
|
153
|
+
// check here.
|
|
154
|
+
return false;
|
|
155
|
+
}
|
|
156
|
+
const promises = [];
|
|
157
|
+
for (const product of productsToValidate) {
|
|
158
|
+
promises.push(product._revalidate(CfgProductBubbleMode.ToRoot, this.productLoader, true));
|
|
159
|
+
}
|
|
160
|
+
const revalidationResults = await Promise.all(promises);
|
|
161
|
+
if (revalidationResults.every((r) => !r)) {
|
|
162
|
+
this.close();
|
|
163
|
+
return false;
|
|
164
|
+
}
|
|
165
|
+
// Apply over again, to settle deeper down. Our theory is that the front of "settled" will
|
|
166
|
+
// move deeper and deeper into the tree.
|
|
167
|
+
await this.updateRootProduct(undefined);
|
|
168
|
+
// We had productsToValidate, and so there must have been a change.
|
|
169
|
+
return true;
|
|
189
170
|
}
|
|
190
171
|
/**
|
|
191
172
|
* Applies the SyncState to the Product and it's AdditionalProducts (sub-products).
|
|
192
173
|
* @param productsToValidate To this all products that will need validation are added.
|
|
193
174
|
*/
|
|
194
|
-
updateProduct(product, productsToValidate) {
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
yield Promise.all(promises);
|
|
202
|
-
});
|
|
175
|
+
async updateProduct(product, productsToValidate) {
|
|
176
|
+
const promises = [];
|
|
177
|
+
promises.push(this.updateFeatures(getFeaturesFromProduct(product), productsToValidate));
|
|
178
|
+
for (const additionalProduct of getAdditionalProducts(product)) {
|
|
179
|
+
promises.push(this.updateProduct(additionalProduct, productsToValidate));
|
|
180
|
+
}
|
|
181
|
+
await Promise.all(promises);
|
|
203
182
|
}
|
|
204
183
|
/**
|
|
205
184
|
* Applies the SyncState to an array of Features.
|
|
206
185
|
* @param productsToValidate To this all products that will need validation are added.
|
|
207
186
|
*/
|
|
208
|
-
updateFeatures(features, productsToValidate) {
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
})));
|
|
219
|
-
});
|
|
187
|
+
async updateFeatures(features, productsToValidate) {
|
|
188
|
+
await Promise.all(features.map(async (feature) => {
|
|
189
|
+
switch (await this.updateFeature(feature, productsToValidate)) {
|
|
190
|
+
case "stop":
|
|
191
|
+
return;
|
|
192
|
+
case "recurseDown":
|
|
193
|
+
await this.updateOptions(getSelectedOptions(feature), productsToValidate);
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
}));
|
|
220
197
|
}
|
|
221
198
|
/**
|
|
222
199
|
* Applies the SyncState to an array of Options.
|
|
223
200
|
* @param productsToValidate To this all products that will need validation are added.
|
|
224
201
|
*/
|
|
225
|
-
updateOptions(options, productsToValidate) {
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
}));
|
|
230
|
-
});
|
|
202
|
+
async updateOptions(options, productsToValidate) {
|
|
203
|
+
await Promise.all(options.map((option) => {
|
|
204
|
+
return this.updateFeatures(getFeaturesFromOption(option), productsToValidate);
|
|
205
|
+
}));
|
|
231
206
|
}
|
|
232
207
|
/**
|
|
233
208
|
* Applies the SyncState to a Feature
|
|
@@ -235,30 +210,28 @@ export class SyncGroupsTransaction {
|
|
|
235
210
|
* @returns Whether we shall stop recursing down (because we are in a state which we
|
|
236
211
|
* expect to be resolved later), we shall continue recursing down.
|
|
237
212
|
*/
|
|
238
|
-
updateFeature(featureWithInitial, productsToValidate) {
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
213
|
+
async updateFeature(featureWithInitial, productsToValidate) {
|
|
214
|
+
const feature = featureWithInitial.target;
|
|
215
|
+
const syncCode = feature.getSyncCode("pull");
|
|
216
|
+
if (syncCode === undefined) {
|
|
217
|
+
// Just continue down, features with no sync groups are always settled
|
|
218
|
+
return "recurseDown";
|
|
219
|
+
}
|
|
220
|
+
if (syncCode === false) {
|
|
221
|
+
// Here we only handle pull. Initializing those missing in SyncMap
|
|
222
|
+
// we do later. We wait until we have settled as much as we can with what
|
|
223
|
+
// we have now, to increase chances of the values being written to the
|
|
224
|
+
// SyncState being the right ones.
|
|
225
|
+
return "stop";
|
|
226
|
+
}
|
|
227
|
+
switch (feature.selectionType) {
|
|
228
|
+
case SelectionType.Group:
|
|
244
229
|
return "recurseDown";
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
// SyncState being the right ones.
|
|
251
|
-
return "stop";
|
|
252
|
-
}
|
|
253
|
-
switch (feature.selectionType) {
|
|
254
|
-
case SelectionType.Group:
|
|
255
|
-
return "recurseDown";
|
|
256
|
-
case SelectionType.SelectOne:
|
|
257
|
-
return yield this.updateSelectOneFeature(syncCode, featureWithInitial, productsToValidate);
|
|
258
|
-
case SelectionType.SelectMany:
|
|
259
|
-
return yield this.updateSelectManyFeature(syncCode, featureWithInitial, productsToValidate);
|
|
260
|
-
}
|
|
261
|
-
});
|
|
230
|
+
case SelectionType.SelectOne:
|
|
231
|
+
return await this.updateSelectOneFeature(syncCode, featureWithInitial, productsToValidate);
|
|
232
|
+
case SelectionType.SelectMany:
|
|
233
|
+
return await this.updateSelectManyFeature(syncCode, featureWithInitial, productsToValidate);
|
|
234
|
+
}
|
|
262
235
|
}
|
|
263
236
|
/**
|
|
264
237
|
* Decides if the SyncState can be applied to the SelectOne Feature, and then changes
|
|
@@ -268,48 +241,46 @@ export class SyncGroupsTransaction {
|
|
|
268
241
|
* @returns Whether we shall stop recursing down (because we are in a state which we
|
|
269
242
|
* expect to be resolved later), we shall continue recursing down.
|
|
270
243
|
*/
|
|
271
|
-
updateSelectOneFeature(syncCode, featureWithInitial, productsToValidate) {
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
// This branch will have to be settled later. Don't go further down here.
|
|
293
|
-
return "stop";
|
|
294
|
-
}
|
|
295
|
-
const selectedOption = feature.selectedOptions[0];
|
|
296
|
-
if (selectedOption !== undefined && selectedOption.code === currentSyncGroupValue) {
|
|
297
|
-
// Settled, continue
|
|
298
|
-
return "recurseDown";
|
|
299
|
-
}
|
|
300
|
-
const optionToSelect = feature.options.find((o) => o.code === currentSyncGroupValue);
|
|
301
|
-
if (optionToSelect === undefined) {
|
|
302
|
-
// No option which can be selected (keep current value and recurse down)
|
|
303
|
-
return "recurseDown";
|
|
304
|
-
}
|
|
305
|
-
// Update the value. Validations are collected so that we do not send more than necessary.
|
|
306
|
-
// Do not recurse further as we will change the state and so what is selected now won't be
|
|
307
|
-
// selected then.
|
|
308
|
-
yield feature.selectOption(optionToSelect._internal, true, ProductConfigurationBubbleMode.ToRoot);
|
|
309
|
-
this.affectedSelectOneFeatures.set(feature, syncGroupHasBeenUpdated ? AffectedLevel.Set : AffectedLevel.Initialized);
|
|
310
|
-
productsToValidate.add(feature.parentProduct);
|
|
244
|
+
async updateSelectOneFeature(syncCode, featureWithInitial, productsToValidate) {
|
|
245
|
+
const feature = featureWithInitial.target;
|
|
246
|
+
const featureDidJustComeIntoScope = featureWithInitial.initial === undefined;
|
|
247
|
+
const hasBeenAffectedLevel = this.affectedSelectOneFeatures.get(feature);
|
|
248
|
+
const syncGroupHasBeenUpdated = this.affectedSelectOneSyncGroups.has(syncCode);
|
|
249
|
+
if (hasBeenAffectedLevel === AffectedLevel.Set ||
|
|
250
|
+
(!syncGroupHasBeenUpdated && hasBeenAffectedLevel === AffectedLevel.Initialized)) {
|
|
251
|
+
// A feature can change value for two reasons:
|
|
252
|
+
// 1. The feature did just come into scope and loads from the sync group to set its "defaults"
|
|
253
|
+
// 2. The sync group has updated with a new value
|
|
254
|
+
// The rule is that in one sync group transaction the feature value is allowed to be updated
|
|
255
|
+
// once or twice. Once for initialization and once for new sync group value. But they have to
|
|
256
|
+
// happen in the right order.
|
|
257
|
+
return "recurseDown";
|
|
258
|
+
}
|
|
259
|
+
if (!featureDidJustComeIntoScope && !syncGroupHasBeenUpdated) {
|
|
260
|
+
return "recurseDown";
|
|
261
|
+
}
|
|
262
|
+
const currentSyncGroupValue = this.syncState.getForSelectOne(syncCode);
|
|
263
|
+
if (currentSyncGroupValue === undefined) {
|
|
264
|
+
// This branch will have to be settled later. Don't go further down here.
|
|
311
265
|
return "stop";
|
|
312
|
-
}
|
|
266
|
+
}
|
|
267
|
+
const selectedOption = feature.selectedOptions[0];
|
|
268
|
+
if (selectedOption !== undefined && selectedOption.code === currentSyncGroupValue) {
|
|
269
|
+
// Settled, continue
|
|
270
|
+
return "recurseDown";
|
|
271
|
+
}
|
|
272
|
+
const optionToSelect = feature.options.find((o) => o.code === currentSyncGroupValue);
|
|
273
|
+
if (optionToSelect === undefined) {
|
|
274
|
+
// No option which can be selected (keep current value and recurse down)
|
|
275
|
+
return "recurseDown";
|
|
276
|
+
}
|
|
277
|
+
// Update the value. Validations are collected so that we do not send more than necessary.
|
|
278
|
+
// Do not recurse further as we will change the state and so what is selected now won't be
|
|
279
|
+
// selected then.
|
|
280
|
+
await feature.selectOption(optionToSelect._internal, true, ProductConfigurationBubbleMode.ToRoot);
|
|
281
|
+
this.affectedSelectOneFeatures.set(feature, syncGroupHasBeenUpdated ? AffectedLevel.Set : AffectedLevel.Initialized);
|
|
282
|
+
productsToValidate.add(feature.parentProduct);
|
|
283
|
+
return "stop";
|
|
313
284
|
}
|
|
314
285
|
/**
|
|
315
286
|
* Decides if the SyncState can be applied to Options in the SelectMany Feature, and
|
|
@@ -318,22 +289,20 @@ export class SyncGroupsTransaction {
|
|
|
318
289
|
* @param productsToValidate To this all products that will need validation are added
|
|
319
290
|
* @returns Always "stop" as recursion is handled internally. Return for consistency.
|
|
320
291
|
*/
|
|
321
|
-
updateSelectManyFeature(syncCode, feature, productsToValidate) {
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
continue;
|
|
331
|
-
}
|
|
292
|
+
async updateSelectManyFeature(syncCode, feature, productsToValidate) {
|
|
293
|
+
const optionsToContinueDown = [];
|
|
294
|
+
for (const option of getOptions(feature)) {
|
|
295
|
+
switch (await this.updateSelectManyOption(syncCode, option, productsToValidate)) {
|
|
296
|
+
case "stop":
|
|
297
|
+
continue;
|
|
298
|
+
case "recurseDown":
|
|
299
|
+
optionsToContinueDown.push(option);
|
|
300
|
+
continue;
|
|
332
301
|
}
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
302
|
+
}
|
|
303
|
+
await this.updateOptions(optionsToContinueDown, productsToValidate);
|
|
304
|
+
// stop as the method above handles the recursion down
|
|
305
|
+
return "stop";
|
|
337
306
|
}
|
|
338
307
|
/**
|
|
339
308
|
* Decides if the SyncState can be applied to the SelectMany Option, and then changes
|
|
@@ -343,108 +312,100 @@ export class SyncGroupsTransaction {
|
|
|
343
312
|
* @returns Whether we shall stop recursing down (because we are in a state which we
|
|
344
313
|
* expect to be resolved later), we shall continue recursing down.
|
|
345
314
|
*/
|
|
346
|
-
updateSelectManyOption(syncCode, optionWithInitial, productsToValidate) {
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
return "stop";
|
|
380
|
-
});
|
|
315
|
+
async updateSelectManyOption(syncCode, optionWithInitial, productsToValidate) {
|
|
316
|
+
const option = optionWithInitial.target;
|
|
317
|
+
const featureDidJustComeIntoScope = optionWithInitial.initial === undefined;
|
|
318
|
+
const optionSelected = option.selected;
|
|
319
|
+
const recurseOrStopIfNoChange = optionSelected ? "recurseDown" : "stop";
|
|
320
|
+
if (this.affectedSelectManyOptions.has(option)) {
|
|
321
|
+
// This option has already changed selection once for this transaction. We expect
|
|
322
|
+
// this to happen very rarely, as the algorithm should settle the selection tree
|
|
323
|
+
// further and further out. Nevertheless, this safeguard is needed to avoid infinite
|
|
324
|
+
// looping if for example the server would return the same data over and over.
|
|
325
|
+
return recurseOrStopIfNoChange;
|
|
326
|
+
}
|
|
327
|
+
if (!featureDidJustComeIntoScope &&
|
|
328
|
+
!this.hasSyncGroupAffectedForSelectMany(syncCode, option)) {
|
|
329
|
+
return "recurseDown";
|
|
330
|
+
}
|
|
331
|
+
const syncGroupValueForOption = this.syncState.getForSelectMany(syncCode, option.code);
|
|
332
|
+
if (syncGroupValueForOption === undefined) {
|
|
333
|
+
// The sync group has no opinion on this. If it is selected, just continue down.
|
|
334
|
+
return recurseOrStopIfNoChange;
|
|
335
|
+
}
|
|
336
|
+
if (syncGroupValueForOption === optionSelected) {
|
|
337
|
+
// We are in sync for this option
|
|
338
|
+
return recurseOrStopIfNoChange;
|
|
339
|
+
}
|
|
340
|
+
const feature = option.parent;
|
|
341
|
+
// Update the value. Validations are collected so that we do not
|
|
342
|
+
// send more than necessary. Do not recurse further as we will change
|
|
343
|
+
// the state and so what is selected now won't be selected then.
|
|
344
|
+
await feature.selectOption(option, syncGroupValueForOption, ProductConfigurationBubbleMode.ToRoot);
|
|
345
|
+
this.affectedSelectManyOptions.add(option);
|
|
346
|
+
productsToValidate.add(feature.parentProduct);
|
|
347
|
+
return "stop";
|
|
381
348
|
}
|
|
382
349
|
/************************************************************************
|
|
383
350
|
* Applying things to the Transaction's SyncState
|
|
384
351
|
************************************************************************/
|
|
385
|
-
applyProduct(product) {
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
if (
|
|
352
|
+
async applyProduct(product) {
|
|
353
|
+
let change = false;
|
|
354
|
+
if (await this.applyFeatures(getFeaturesFromProduct(product))) {
|
|
355
|
+
if (this.updateMode === SyncGroupsApplyMode.Strict) {
|
|
356
|
+
return true;
|
|
357
|
+
}
|
|
358
|
+
change = true;
|
|
359
|
+
}
|
|
360
|
+
for (const additionalProduct of getAdditionalProducts(product)) {
|
|
361
|
+
if (await this.applyProduct(additionalProduct)) {
|
|
389
362
|
if (this.updateMode === SyncGroupsApplyMode.Strict) {
|
|
390
363
|
return true;
|
|
391
364
|
}
|
|
392
365
|
change = true;
|
|
393
366
|
}
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
367
|
+
}
|
|
368
|
+
return change;
|
|
369
|
+
}
|
|
370
|
+
async applyFeatures(featureWithInitials) {
|
|
371
|
+
let change = false;
|
|
372
|
+
for (const featureWithInitial of featureWithInitials) {
|
|
373
|
+
const feature = featureWithInitial.target;
|
|
374
|
+
switch (feature.selectionType) {
|
|
375
|
+
case SelectionType.SelectOne:
|
|
376
|
+
if (this.applySelectOneFeature(feature, false, featureWithInitial.initial === undefined)) {
|
|
377
|
+
change = true;
|
|
398
378
|
}
|
|
399
|
-
|
|
400
|
-
|
|
379
|
+
break;
|
|
380
|
+
case SelectionType.SelectMany:
|
|
381
|
+
if (this.applySelectManyFeature(featureWithInitial)) {
|
|
382
|
+
change = true;
|
|
383
|
+
}
|
|
384
|
+
break;
|
|
401
385
|
}
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
let change = false;
|
|
408
|
-
for (const featureWithInitial of featureWithInitials) {
|
|
409
|
-
const feature = featureWithInitial.target;
|
|
410
|
-
switch (feature.selectionType) {
|
|
411
|
-
case SelectionType.SelectOne:
|
|
412
|
-
if (this.applySelectOneFeature(feature, false, featureWithInitial.initial === undefined)) {
|
|
413
|
-
change = true;
|
|
414
|
-
}
|
|
415
|
-
break;
|
|
416
|
-
case SelectionType.SelectMany:
|
|
417
|
-
if (this.applySelectManyFeature(featureWithInitial)) {
|
|
418
|
-
change = true;
|
|
419
|
-
}
|
|
420
|
-
break;
|
|
421
|
-
}
|
|
422
|
-
if (change && this.updateMode === SyncGroupsApplyMode.Strict) {
|
|
386
|
+
if (change && this.updateMode === SyncGroupsApplyMode.Strict) {
|
|
387
|
+
return true;
|
|
388
|
+
}
|
|
389
|
+
if (await this.applyOptions(getSelectedOptions(featureWithInitial))) {
|
|
390
|
+
if (this.updateMode === SyncGroupsApplyMode.Strict) {
|
|
423
391
|
return true;
|
|
424
392
|
}
|
|
425
|
-
|
|
426
|
-
if (this.updateMode === SyncGroupsApplyMode.Strict) {
|
|
427
|
-
return true;
|
|
428
|
-
}
|
|
429
|
-
change = true;
|
|
430
|
-
}
|
|
393
|
+
change = true;
|
|
431
394
|
}
|
|
432
|
-
|
|
433
|
-
|
|
395
|
+
}
|
|
396
|
+
return change;
|
|
434
397
|
}
|
|
435
|
-
applyOptions(options) {
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
if (
|
|
440
|
-
|
|
441
|
-
return true;
|
|
442
|
-
}
|
|
443
|
-
change = true;
|
|
398
|
+
async applyOptions(options) {
|
|
399
|
+
let change = false;
|
|
400
|
+
for (const option of options) {
|
|
401
|
+
if (await this.applyFeatures(getFeaturesFromOption(option))) {
|
|
402
|
+
if (this.updateMode === SyncGroupsApplyMode.Strict) {
|
|
403
|
+
return true;
|
|
444
404
|
}
|
|
405
|
+
change = true;
|
|
445
406
|
}
|
|
446
|
-
|
|
447
|
-
|
|
407
|
+
}
|
|
408
|
+
return change;
|
|
448
409
|
}
|
|
449
410
|
applySelectOneFeature(feature, userInitiated, featureDidJustComeIntoScope) {
|
|
450
411
|
const selectionType = feature.selectionType;
|
|
@@ -534,8 +495,7 @@ export class SyncGroupsTransaction {
|
|
|
534
495
|
forSyncCode.add(option.code);
|
|
535
496
|
}
|
|
536
497
|
hasSyncGroupAffectedForSelectMany(syncCode, option) {
|
|
537
|
-
|
|
538
|
-
return ((_a = this.affectedSelectManySyncGroupsAndOptions.get(syncCode)) === null || _a === void 0 ? void 0 : _a.has(option.code)) === true;
|
|
498
|
+
return this.affectedSelectManySyncGroupsAndOptions.get(syncCode)?.has(option.code) === true;
|
|
539
499
|
}
|
|
540
500
|
}
|
|
541
501
|
function getAdditionalProducts(product) {
|
|
@@ -544,46 +504,42 @@ function getAdditionalProducts(product) {
|
|
|
544
504
|
.filter((p) => p.selected)
|
|
545
505
|
.map((childTarget) => {
|
|
546
506
|
const refKey = childTarget.refKey;
|
|
547
|
-
const childInitial = initial
|
|
507
|
+
const childInitial = initial?.additionalProducts.find((p) => refKey === p.refKey);
|
|
548
508
|
return {
|
|
549
509
|
target: childTarget._internal,
|
|
550
|
-
initial: childInitial
|
|
510
|
+
initial: childInitial?._internal,
|
|
551
511
|
};
|
|
552
512
|
});
|
|
553
513
|
}
|
|
554
514
|
function pairOptions(targets, initials) {
|
|
555
515
|
return targets.map((childTarget) => {
|
|
556
516
|
const key = childTarget.key;
|
|
557
|
-
const childInitial = initials
|
|
517
|
+
const childInitial = initials?.find((o) => key === o.key);
|
|
558
518
|
return {
|
|
559
519
|
target: childTarget._internal,
|
|
560
|
-
initial: childInitial
|
|
520
|
+
initial: childInitial?._internal,
|
|
561
521
|
};
|
|
562
522
|
});
|
|
563
523
|
}
|
|
564
524
|
function getSelectedOptions(feature) {
|
|
565
|
-
|
|
566
|
-
return pairOptions(feature.target.selectedOptions, (_a = feature.initial) === null || _a === void 0 ? void 0 : _a.selectedOptions);
|
|
525
|
+
return pairOptions(feature.target.selectedOptions, feature.initial?.selectedOptions);
|
|
567
526
|
}
|
|
568
527
|
function getOptions(feature) {
|
|
569
|
-
|
|
570
|
-
return pairOptions(feature.target.options, (_a = feature.initial) === null || _a === void 0 ? void 0 : _a.options);
|
|
528
|
+
return pairOptions(feature.target.options, feature.initial?.options);
|
|
571
529
|
}
|
|
572
530
|
function pairFeatures(targets, initials) {
|
|
573
531
|
return targets.map((childTarget) => {
|
|
574
532
|
const code = childTarget.code;
|
|
575
|
-
const childInitial = initials
|
|
533
|
+
const childInitial = initials?.find((f) => code === f.code);
|
|
576
534
|
return {
|
|
577
535
|
target: childTarget._internal,
|
|
578
|
-
initial: childInitial
|
|
536
|
+
initial: childInitial?._internal,
|
|
579
537
|
};
|
|
580
538
|
});
|
|
581
539
|
}
|
|
582
540
|
function getFeaturesFromProduct(product) {
|
|
583
|
-
|
|
584
|
-
return pairFeatures(product.target.configuration.features, (_a = product.initial) === null || _a === void 0 ? void 0 : _a.configuration.features);
|
|
541
|
+
return pairFeatures(product.target.configuration.features, product.initial?.configuration.features);
|
|
585
542
|
}
|
|
586
543
|
function getFeaturesFromOption(option) {
|
|
587
|
-
|
|
588
|
-
return pairFeatures(option.target.features, (_a = option.initial) === null || _a === void 0 ? void 0 : _a.features);
|
|
544
|
+
return pairFeatures(option.target.features, option.initial?.getFeaturesOutsideSync());
|
|
589
545
|
}
|