@configura/web-api 2.0.0-alpha.9 → 2.1.0-alpha.0

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 (58) hide show
  1. package/.eslintrc.json +1 -14
  2. package/dist/CatalogueAPI.d.ts +103 -32
  3. package/dist/CatalogueAPI.js +62 -6
  4. package/dist/CfgProduct.d.ts +90 -14
  5. package/dist/CfgProduct.js +266 -56
  6. package/dist/CfgReferencePathHelper.d.ts +3 -3
  7. package/dist/index.d.ts +1 -1
  8. package/dist/index.js +1 -1
  9. package/dist/io/CfgHistoryManager.d.ts +33 -1
  10. package/dist/io/CfgHistoryManager.js +68 -6
  11. package/dist/io/CfgHistoryToProdConfConnector.d.ts +11 -10
  12. package/dist/io/CfgHistoryToProdConfConnector.js +32 -38
  13. package/dist/io/CfgIOManager.d.ts +5 -0
  14. package/dist/io/CfgIOManager.js +20 -1
  15. package/dist/io/CfgIOProdConfConnector.d.ts +17 -18
  16. package/dist/io/CfgIOProdConfConnector.js +52 -58
  17. package/dist/io/CfgIOWarningSupplier.d.ts +4 -0
  18. package/dist/io/CfgIOWarningSupplier.js +1 -0
  19. package/dist/io/CfgObservableStateToProdConfConnector.d.ts +4 -4
  20. package/dist/io/CfgObservableStateToProdConfConnector.js +3 -3
  21. package/dist/io/CfgWindowMessageManager.js +4 -0
  22. package/dist/io/CfgWindowMessageToProdConfConnector.d.ts +4 -4
  23. package/dist/io/CfgWindowMessageToProdConfConnector.js +3 -3
  24. package/dist/productConfiguration/CfgFeature.d.ts +12 -7
  25. package/dist/productConfiguration/CfgFeature.js +33 -14
  26. package/dist/productConfiguration/CfgOption.d.ts +16 -10
  27. package/dist/productConfiguration/CfgOption.js +46 -18
  28. package/dist/productConfiguration/CfgProductConfiguration.d.ts +27 -16
  29. package/dist/productConfiguration/CfgProductConfiguration.js +53 -29
  30. package/dist/productConfiguration/filters.d.ts +6 -4
  31. package/dist/productConfiguration/filters.js +94 -23
  32. package/dist/productConfiguration/productParamsGenerator.d.ts +3 -3
  33. package/dist/productConfiguration/utilitiesProductConfiguration.d.ts +1 -1
  34. package/dist/productConfiguration/utilitiesProductConfiguration.js +11 -4
  35. package/dist/productLoader.d.ts +3 -3
  36. package/dist/productLoader.js +1 -1
  37. package/dist/syncGroups/SyncGroupsHandler.d.ts +9 -2
  38. package/dist/syncGroups/SyncGroupsHandler.js +15 -4
  39. package/dist/syncGroups/SyncGroupsState.d.ts +5 -1
  40. package/dist/syncGroups/SyncGroupsState.js +44 -2
  41. package/dist/syncGroups/SyncGroupsTransaction.js +34 -21
  42. package/dist/tasks/TaskHandler.d.ts +2 -2
  43. package/dist/tasks/TaskHandler.js +2 -1
  44. package/dist/tests/testData/dummyProductForTest.d.ts +2 -2
  45. package/dist/tests/testData/testDataAdditionalProductInAdditionalProductInProductForTest.js +14 -9
  46. package/dist/tests/testData/testDataCachedGetProduct.js +2 -0
  47. package/dist/tests/testData/testDataCachedPostValidate.js +2 -0
  48. package/dist/tests/testData/testDataProductAggregatedPrice.js +3 -1
  49. package/dist/tests/testData/testDataUpcharge.js +2 -0
  50. package/dist/utilitiesCatalogueData.d.ts +14 -9
  51. package/dist/utilitiesCatalogueData.js +7 -0
  52. package/dist/utilitiesCataloguePermission.d.ts +4 -4
  53. package/dist/utilitiesConfiguration.d.ts +29 -0
  54. package/dist/utilitiesConfiguration.js +200 -0
  55. package/dist/utilitiesNumericValues.js +13 -8
  56. package/package.json +3 -3
  57. package/dist/ConfigurationConverter.d.ts +0 -5
  58. package/dist/ConfigurationConverter.js +0 -72
@@ -9,13 +9,13 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
9
9
  };
10
10
  import { AggregatedLoadingObservable, assert, assertDefined, augmentErrorMessage, compareArrays, count, Observable, toLengthUnit, } from "@configura/web-utilities";
11
11
  import { CfgMeasureDefinition } from "./CfgMeasure.js";
12
- import { convertDtoConfProdToV1 } from "./ConfigurationConverter.js";
13
12
  import { ProductConfigurationBubbleMode } from "./productConfiguration/CfgOption.js";
14
13
  import { CfgProductConfiguration } from "./productConfiguration/CfgProductConfiguration.js";
15
14
  import { collectAdditionalProductRefs } from "./productConfiguration/utilitiesProductConfiguration.js";
16
15
  import { wrapWithCache } from "./productLoader.js";
17
16
  import { SyncGroupsHandler } from "./syncGroups/SyncGroupsHandler.js";
18
- import { comparePricesObjects, correctDefaultsOnCatalogueParams, isSameCatalogueParams, isSameProductRef, makeProductKey, } from "./utilitiesCatalogueData.js";
17
+ import { compareCfgProductData, correctDefaultsOnCatalogueParams, isSameCatalogueParams, isSameProductRef, makeProductKey, } from "./utilitiesCatalogueData.js";
18
+ import { CfgProdConfParts, convertDtoProductConfToV1, isAdditionalProductConfiguration, isProductConf, } from "./utilitiesConfiguration.js";
19
19
  function completeSettings(incompleteSettings) {
20
20
  var _a;
21
21
  return {
@@ -52,7 +52,7 @@ function isDescriptionMatch(l, r) {
52
52
  * the class that should be used and interacted with.
53
53
  */
54
54
  export class _CfgProductInternal {
55
- constructor(initSuccess, initFail, _productLoaderRaw, prodParams, settings, optional, selected, rootFeatureRefs, allRawFeatures, uuid, _rawUnit, _rawProductData, apiSelection, loadingObservable, parent, root, _additionalProductRef, _syncGroupHandler) {
55
+ constructor(initSuccess, initFail, _productLoaderRaw, prodParams, settings, optional, selected, rootFeatureRefs, rawFeatures, notes, uuid, _rawUnit, _rawProductData, apiSelection, loadingObservable, parent, root, _additionalProductRef, _syncGroupHandler) {
56
56
  var _a;
57
57
  this._productLoaderRaw = _productLoaderRaw;
58
58
  this.prodParams = prodParams;
@@ -66,15 +66,26 @@ export class _CfgProductInternal {
66
66
  this._syncGroupHandler = _syncGroupHandler;
67
67
  this._destroyed = false;
68
68
  this.additionalProducts = [];
69
+ this._notes = new Map();
69
70
  this.changeObservable = new Observable();
71
+ /** Mark this and its descendants as destroyed and remove all listeners */
70
72
  this.destroy = () => {
73
+ var _a;
71
74
  this._destroyed = true;
72
75
  this.changeObservable.stopAllListen();
73
76
  this.configuration.stopAllListenForChange();
74
- for (const additionalProduct of this.additionalProducts || []) {
77
+ for (const additionalProduct of (_a = this.additionalProducts) !== null && _a !== void 0 ? _a : []) {
75
78
  additionalProduct.destroy();
76
79
  }
77
80
  };
81
+ /**
82
+ * Reset will reset the product to its initial state
83
+ */
84
+ this.reset = () => __awaiter(this, void 0, void 0, function* () {
85
+ if (this._initialClone !== undefined) {
86
+ yield this.copyFrom(this._initialClone, true);
87
+ }
88
+ });
78
89
  this._notifyAllOfChange = (bubbleMode, committed) => __awaiter(this, void 0, void 0, function* () {
79
90
  if (bubbleMode === CfgProductBubbleMode.Stop) {
80
91
  return;
@@ -93,7 +104,7 @@ export class _CfgProductInternal {
93
104
  if (bubbleMode === CfgProductBubbleMode.ToRootAndBubbleSelected &&
94
105
  this.optional &&
95
106
  !this.selected) {
96
- yield this.setSelected(true, bubbleMode);
107
+ yield this.setSelected(true, bubbleMode, false);
97
108
  return;
98
109
  }
99
110
  yield this._notifyAllOfChange(bubbleMode, committed);
@@ -140,18 +151,26 @@ export class _CfgProductInternal {
140
151
  return;
141
152
  }
142
153
  });
143
- this.getDtoConf = (includeExtendedData, includeProductParams) => {
154
+ this.getDtoConf = (include) => {
155
+ var _a;
144
156
  const conf = {};
145
- const features = this.configuration._internal.getDtoConf(includeExtendedData);
157
+ const features = this.configuration._internal.getDtoConf((include & CfgProdConfParts.ExtendedData) === CfgProdConfParts.ExtendedData);
146
158
  if (0 < features.length) {
147
159
  conf.features = features;
148
160
  }
149
- if (includeProductParams) {
161
+ if ((include & CfgProdConfParts.ProdParams) === CfgProdConfParts.ProdParams) {
150
162
  conf.prodParams = this.prodParams;
151
163
  }
164
+ // Only store the syncGroupState for the root product. The same state
165
+ // is used for the entire product, including the additional product,
166
+ // so no need to store it for additional products.
167
+ if ((include & CfgProdConfParts.SyncGroupState) === CfgProdConfParts.SyncGroupState &&
168
+ this.parent === undefined) {
169
+ conf.syncGroupState = (_a = this.syncGroupHandler) === null || _a === void 0 ? void 0 : _a.getCompactSyncGroupState();
170
+ }
152
171
  const additionalProducts = this.additionalProducts;
153
172
  if (0 < additionalProducts.length) {
154
- conf.additionalProducts = additionalProducts.map((p) => p._internal.getDtoConf(includeExtendedData, includeProductParams));
173
+ conf.additionalProducts = additionalProducts.map((p) => p._internal.getDtoConf(include));
155
174
  }
156
175
  if (this.isAdditionalProduct) {
157
176
  const refKey = this.refKey;
@@ -165,15 +184,25 @@ export class _CfgProductInternal {
165
184
  return conf;
166
185
  };
167
186
  this.setDtoConf = (s, doValidate, productLoaderForGroupedLoad) => __awaiter(this, void 0, void 0, function* () {
168
- return yield this.setApiSelection(convertDtoConfProdToV1(s), doValidate, productLoaderForGroupedLoad);
187
+ // The DtoProdConf format can contain a sync group state, but
188
+ // not the DtoAdditionalProductConfiguration format. So we apply
189
+ // any passed state here.
190
+ if (this.root === this) {
191
+ const syncGroupHandler = this.syncGroupHandler;
192
+ const newSyncGroupState = s.syncGroupState;
193
+ if (syncGroupHandler !== undefined && newSyncGroupState !== undefined) {
194
+ syncGroupHandler.setCompactSyncGroupState(newSyncGroupState);
195
+ }
196
+ }
197
+ return yield this.setApiSelection(convertDtoProductConfToV1(s), doValidate, productLoaderForGroupedLoad);
169
198
  });
170
199
  this.setApiSelection = (s, doValidate, productLoaderForGroupedLoad) => __awaiter(this, void 0, void 0, function* () {
171
200
  return yield this._setApiSelectionWithOtherProduct(s, doValidate, productLoaderForGroupedLoad, undefined);
172
201
  });
173
202
  this.copyFrom = (source, doValidate, productLoaderForGroupedLoad) => __awaiter(this, void 0, void 0, function* () {
174
- return yield this._setApiSelectionWithOtherProduct(convertDtoConfProdToV1(source.getDtoConf(false, false)), doValidate, productLoaderForGroupedLoad, source);
203
+ return yield this._setApiSelectionWithOtherProduct(convertDtoProductConfToV1(source.getDtoConf(CfgProdConfParts.NoExtra)), doValidate, productLoaderForGroupedLoad, source);
175
204
  });
176
- this._setApiSelectionWithOtherProduct = (s, doValidate, productLoaderForGroupedLoad, sourceProduct) => __awaiter(this, void 0, void 0, function* () {
205
+ this._setApiSelectionWithOtherProduct = (productConfiguration, doValidate, productLoaderForGroupedLoad, sourceProduct) => __awaiter(this, void 0, void 0, function* () {
177
206
  // Wrap with cache will make getProduct for this function call use the same server call
178
207
  // for the same product with the same params. Used for getProduct (when a new additional
179
208
  // product is loaded) and postValidate.
@@ -181,21 +210,42 @@ export class _CfgProductInternal {
181
210
  productLoaderForGroupedLoad || wrapWithCache(this._productLoaderRaw);
182
211
  let change = false;
183
212
  if (sourceProduct !== undefined) {
184
- this._rawProductData = sourceProduct.rawProductData;
185
- this.configuration._internal.populateFeatures(sourceProduct.configuration.rootFeatureRefs);
186
- change = true; // We can not know if this is an actual change, so we assume it is
213
+ if (!compareCfgProductData(this._rawProductData, sourceProduct.rawProductData)) {
214
+ this._rawProductData = sourceProduct.rawProductData;
215
+ change = true;
216
+ }
217
+ if (this.configuration._internal.addRawFeatures(sourceProduct.configuration.rawFeatures, false)) {
218
+ change = true;
219
+ }
220
+ if (this.configuration._internal.populateFeatures(sourceProduct.configuration.rootFeatureRefs)) {
221
+ change = true;
222
+ }
223
+ const targetNotes = this._notes;
224
+ const sourceNotes = sourceProduct._notes;
225
+ for (const targetKey of targetNotes.keys()) {
226
+ if (!sourceNotes.has(targetKey)) {
227
+ targetNotes.delete(targetKey);
228
+ change = true;
229
+ }
230
+ }
231
+ for (const [sourceKey, sourceNote] of sourceNotes) {
232
+ if (!targetNotes.has(sourceKey)) {
233
+ targetNotes.set(sourceKey, sourceNote);
234
+ change = true;
235
+ }
236
+ }
187
237
  }
188
- const configurationChange = yield this.configuration._internal.setApiSelection(s.selOptions, false);
238
+ const configurationChange = yield this.configuration._internal.setApiSelection(productConfiguration.selOptions, false);
189
239
  if (configurationChange) {
190
240
  change = true;
191
241
  }
192
242
  if (this.optional) {
193
- if (yield this.setSelected(s.selected !== false, CfgProductBubbleMode.Stop)) {
243
+ if (yield this.setSelected(productConfiguration.selected !== false, CfgProductBubbleMode.Stop, false)) {
194
244
  change = true;
195
245
  }
196
246
  }
197
- yield this._syncAndLoadAdditionalProducts(productLoaderForGroupedLoad);
198
- const apiSelectionAdditionalProducts = s.additionalProducts || [];
247
+ yield this._syncAndLoadAdditionalProducts(productLoaderForGroupedLoad, productConfiguration);
248
+ const apiSelectionAdditionalProducts = productConfiguration.additionalProducts || [];
199
249
  const additionalProducts = this.additionalProducts.slice();
200
250
  const additionalProductsCount = additionalProducts.length;
201
251
  const apiSelectionAdditionalProductsCount = apiSelectionAdditionalProducts.length;
@@ -248,7 +298,7 @@ export class _CfgProductInternal {
248
298
  productLoaderForGroupedLoad || wrapWithCache(this._productLoaderRaw);
249
299
  let change = false;
250
300
  if (this.optional && other.optional) {
251
- if (yield this.setSelected(other.selected, CfgProductBubbleMode.Stop)) {
301
+ if (yield this.setSelected(other.selected, CfgProductBubbleMode.Stop, false)) {
252
302
  change = true;
253
303
  }
254
304
  }
@@ -312,7 +362,10 @@ export class _CfgProductInternal {
312
362
  const token = this.loadingObservable.startChildLoading();
313
363
  this._revalidateInProgressToken = token;
314
364
  try {
315
- const response = yield productLoader.postValidate(correctDefaultsOnCatalogueParams(this.prodParams), { selOptions: configuration.getApiSelection() });
365
+ const response = yield productLoader.postValidate(correctDefaultsOnCatalogueParams(this.prodParams), {
366
+ selOptions: configuration._internal.getApiSelection(),
367
+ knownFeatureCodes: configuration.rawFeatures.map((f) => f.code),
368
+ });
316
369
  // The revalidateInProgressToken is used to know if some other revalidate
317
370
  // call has happened after this call, thereby making this call obsolete.
318
371
  // This is a bit crude in that it does not actually cancel previous validate
@@ -331,18 +384,17 @@ export class _CfgProductInternal {
331
384
  if (this._destroyed) {
332
385
  return false;
333
386
  }
334
- const { productData, rootFeatureRefs } = response;
335
- const pricesUpdated = !comparePricesObjects(this.prices, productData.partsData.prices);
387
+ const { productData, rootFeatureRefs, features, notes } = response;
388
+ if (notes !== undefined) {
389
+ this.addNotes(notes.values());
390
+ }
336
391
  this._rawProductData = productData;
392
+ configuration._internal.addRawFeatures(features, true);
337
393
  if (rootFeatureRefs !== undefined) {
338
394
  configuration._internal.populateFeatures(rootFeatureRefs);
339
395
  }
340
- if (pricesUpdated) {
341
- this._configuration._internal._freshRefDescendants();
342
- this._configuration = CfgProductConfiguration._makeNewRefFrom(this._configuration._internal);
343
- }
344
396
  yield configuration._internal.setApiSelection(productData.partsData.selOptions || [], false);
345
- yield this._syncAndLoadAdditionalProducts(productLoader);
397
+ yield this._syncAndLoadAdditionalProducts(productLoader, undefined);
346
398
  if (this._destroyed) {
347
399
  return false;
348
400
  }
@@ -350,7 +402,7 @@ export class _CfgProductInternal {
350
402
  return true;
351
403
  }
352
404
  catch (e) {
353
- throw augmentErrorMessage(e, "Validate product configuration request failure");
405
+ throw augmentErrorMessage(e, `Validate product configuration request (${this.prodParams.partNumber}) failure`);
354
406
  }
355
407
  finally {
356
408
  this.loadingObservable.stopChildLoading(token);
@@ -360,7 +412,7 @@ export class _CfgProductInternal {
360
412
  * Based on this configuration find what additional products should be shown and not, unload
361
413
  * (i.e. destroy) those that should no longer be shown, load the new ones.
362
414
  */
363
- this._syncAndLoadAdditionalProducts = (productLoaderForGroupedLoad) => __awaiter(this, void 0, void 0, function* () {
415
+ this._syncAndLoadAdditionalProducts = (productLoaderForGroupedLoad, initialProductConfiguration) => __awaiter(this, void 0, void 0, function* () {
364
416
  const { _productLoaderRaw: productLoaderRaw, rawProductData: productData, configuration, additionalProducts: currentAdditionalProducts, } = this;
365
417
  const additionalProductRefs = [
366
418
  ...(productData.additionalProductRefs || []),
@@ -402,10 +454,17 @@ export class _CfgProductInternal {
402
454
  change = true;
403
455
  }
404
456
  const newAdditionalProducts = yield Promise.all(additionalProductRefs.map((p) => (() => __awaiter(this, void 0, void 0, function* () {
457
+ var _c;
405
458
  const additionalProductRef = p.prodRef;
459
+ const refKey = additionalProductRef.refKey;
460
+ const additionalProductInitialProductConfiguration = (_c = initialProductConfiguration === null || initialProductConfiguration === void 0 ? void 0 : initialProductConfiguration.additionalProducts) === null || _c === void 0 ? void 0 : _c.find((aP) => refKey === aP.refKey);
461
+ if (initialProductConfiguration !== undefined &&
462
+ additionalProductInitialProductConfiguration === undefined) {
463
+ console.warn(`Did not find initial product configuration for additional product with refKey ${refKey}`);
464
+ }
406
465
  return {
407
466
  originalIndex: p.originalIndex,
408
- product: CfgProduct._makeNewRefFrom(yield _CfgProductInternal.make(productLoaderRaw, productLoaderForGroupedLoad, Object.assign(Object.assign({}, additionalProductRef.catId), { cid: this.prodParams.cid, lang: this.prodParams.lang, partNumber: additionalProductRef.partNumber }), this.settings, additionalProductRef.optional === true, this.loadingObservable, this, this.root, additionalProductRef)),
467
+ product: CfgProduct._makeNewRefFrom(yield _CfgProductInternal.make(productLoaderRaw, productLoaderForGroupedLoad, Object.assign(Object.assign({}, additionalProductRef.catId), { cid: this.prodParams.cid, lang: this.prodParams.lang, partNumber: additionalProductRef.partNumber }), this.settings, additionalProductRef.optional === true, this.loadingObservable, this, this.root, additionalProductRef, additionalProductInitialProductConfiguration)),
409
468
  };
410
469
  }))()));
411
470
  if (this._destroyed) {
@@ -423,19 +482,28 @@ export class _CfgProductInternal {
423
482
  this.root = root !== null && root !== void 0 ? root : this;
424
483
  this.key = makeProductKey(Object.assign(Object.assign({}, prodParams), { partNumber: (_a = _additionalProductRef === null || _additionalProductRef === void 0 ? void 0 : _additionalProductRef.refKey) !== null && _a !== void 0 ? _a : prodParams.partNumber }));
425
484
  this._selected = optional ? selected : undefined;
426
- this.isAdditionalProduct = parent !== undefined;
427
- this._configuration = CfgProductConfiguration.make(initSuccess, initFail, rootFeatureRefs, allRawFeatures, apiSelection, this, this.root);
485
+ this.isAdditionalProduct = _additionalProductRef !== undefined;
486
+ if (notes !== undefined) {
487
+ this.addNotes(notes);
488
+ }
489
+ this._configuration = CfgProductConfiguration.make(initSuccess, initFail, rootFeatureRefs, rawFeatures, apiSelection, this, this.root);
428
490
  }
429
491
  get selected() {
430
492
  return this._selected !== false;
431
493
  }
494
+ /**
495
+ * Please note that cloning an additional product will make the clone believe is is
496
+ * an additional product, even if it has no parent and root.
497
+ * Providing the parent and root of what you clone as arguments is unwise as it will
498
+ * make changes you do on the clone be propagated up to the original non-clone root product.
499
+ */
432
500
  clone(parent, root) {
433
501
  return __awaiter(this, void 0, void 0, function* () {
434
502
  const product = yield new Promise((initSuccess, initFail) => {
435
503
  var _a;
436
504
  const p = new _CfgProductInternal(() => {
437
505
  initSuccess(p);
438
- }, initFail, this._productLoaderRaw, this.prodParams, this.settings, this.optional, this.selected, this._configuration.rootFeatureRefs, this._configuration.allRawFeatures, this.uuid, this._rawUnit, this._rawProductData, this.configuration.getApiSelection(), new AggregatedLoadingObservable(), parent, root, this._additionalProductRef, (_a = this._syncGroupHandler) === null || _a === void 0 ? void 0 : _a.clone());
506
+ }, initFail, this._productLoaderRaw, this.prodParams, this.settings, this.optional, this.selected, this._configuration.rootFeatureRefs, this._configuration.rawFeatures, this._notes.values(), this.uuid, this._rawUnit, this._rawProductData, this.configuration._internal.getApiSelection(), new AggregatedLoadingObservable(), parent, root, this._additionalProductRef, (_a = this._syncGroupHandler) === null || _a === void 0 ? void 0 : _a.clone());
439
507
  });
440
508
  for (const additionalProduct of this.additionalProducts) {
441
509
  product.additionalProducts.push(CfgProduct._makeNewRefFrom(yield additionalProduct._internal.clone(product, root || product)));
@@ -443,12 +511,56 @@ export class _CfgProductInternal {
443
511
  return product;
444
512
  });
445
513
  }
514
+ /**
515
+ * Internal use. Used when this product is an additional product, and
516
+ * changing a parent product has made the settings for this product
517
+ * change.
518
+ */
446
519
  _updateAdditionalProdRef(p) {
447
520
  this._additionalProductRef = p;
448
521
  if (p.optional !== this.optional) {
449
522
  this._selected = p.optional ? false : undefined;
450
523
  }
451
524
  }
525
+ /**
526
+ * Return a DtoNode using noteRef as a key.
527
+ * Throws an error if no note is found.
528
+ */
529
+ getNote(noteRef) {
530
+ const note = this._notes.get(noteRef);
531
+ if (note === undefined) {
532
+ throw new Error(`Note with noteRef ${noteRef} not found.`);
533
+ }
534
+ return note;
535
+ }
536
+ /**
537
+ * noteRefs is a list of keys coming from CfgOption, CfgFeature or CfgProduct.
538
+ * The keys are used to get a DtoNote[] from notes at CfgProduct.
539
+ */
540
+ getNotes(noteRefs) {
541
+ return noteRefs.map((noteRef) => this.getNote(noteRef));
542
+ }
543
+ addNotes(notes) {
544
+ for (const note of notes) {
545
+ const code = note.code;
546
+ if (!code) {
547
+ throw new Error("Note with no code");
548
+ }
549
+ this._notes.set(code, note);
550
+ }
551
+ }
552
+ get notes() {
553
+ var _a;
554
+ return this.getNotes((_a = this.rawProductData.noteRefs) !== null && _a !== void 0 ? _a : []);
555
+ }
556
+ get miscFiles() {
557
+ var _a;
558
+ return (_a = this._rawProductData.miscFiles) !== null && _a !== void 0 ? _a : [];
559
+ }
560
+ get hasRootFeaturesChanged() {
561
+ return (this._configuration._internal.hasRootFeaturesChanged ||
562
+ this.additionalProducts.some((p) => p._internal.hasRootFeaturesChanged));
563
+ }
452
564
  get description() {
453
565
  var _a, _b;
454
566
  return (_b = (_a = this._additionalProductRef) === null || _a === void 0 ? void 0 : _a.refDescription) !== null && _b !== void 0 ? _b : this._rawProductData.description;
@@ -523,16 +635,31 @@ export class _CfgProductInternal {
523
635
  get optional() {
524
636
  return this._selected !== undefined;
525
637
  }
526
- setSelected(v, bubbleMode) {
638
+ setSelected(selected, bubbleMode, interactive) {
527
639
  return __awaiter(this, void 0, void 0, function* () {
528
640
  if (!this.optional) {
529
641
  console.warn("This product is not optional. Nothing will happen");
530
642
  return false;
531
643
  }
532
- if (this._selected === v) {
644
+ if (this._selected === selected) {
533
645
  return false;
534
646
  }
535
- this._selected = v;
647
+ // Vitally important that this happens before the call to reset,
648
+ // so that the reset won't cause infinite loops
649
+ this._selected = selected;
650
+ if (interactive) {
651
+ if (selected) {
652
+ const syncGroupHandler = this.syncGroupHandler;
653
+ if (syncGroupHandler !== undefined) {
654
+ yield syncGroupHandler.init(this.root, wrapWithCache(this._productLoaderRaw));
655
+ }
656
+ }
657
+ else {
658
+ yield this.reset();
659
+ // In case a passed initial configuration has made it reset to selected
660
+ this._selected = selected;
661
+ }
662
+ }
536
663
  yield this._notifyAllOfChange(bubbleMode, true);
537
664
  return true;
538
665
  });
@@ -585,33 +712,66 @@ export class _CfgProductInternal {
585
712
  }
586
713
  }
587
714
  _CfgProductInternal.make = (productLoaderRaw, productLoaderForGroupedLoad, // Used when instantiating the current product
588
- prodParams, settings, optional, loadingObservable, parent, root, additionalProductRef) => __awaiter(void 0, void 0, void 0, function* () {
715
+ prodParams, settings, optional, loadingObservable, parent, root, additionalProductRef, initialProductConfiguration) => __awaiter(void 0, void 0, void 0, function* () {
589
716
  // Wrap with cache will make getProduct for this function call use the same server call
590
717
  // for the same product with the same params. Not retained for future calls, only used
591
718
  // at this initial load.
592
719
  productLoaderForGroupedLoad =
593
720
  productLoaderForGroupedLoad || wrapWithCache(productLoaderRaw);
594
- const syncGroupHandler = root === undefined
595
- ? SyncGroupsHandler.make(settings.syncGroupsApplyMode, loadingObservable)
596
- : undefined;
721
+ let syncGroupHandler = undefined;
722
+ // No root means we are root. We only create SyncGroupsHandler if this is the root product
723
+ if (root === undefined) {
724
+ let initialSyncGroupsState = undefined;
725
+ // DtoProductConf supports containing the sync group state, but the old
726
+ // DtoAdditionalProductConfiguration does not
727
+ if (initialProductConfiguration !== undefined &&
728
+ isProductConf(initialProductConfiguration)) {
729
+ initialSyncGroupsState = initialProductConfiguration.syncGroupState;
730
+ }
731
+ syncGroupHandler = SyncGroupsHandler.make(settings.syncGroupsApplyMode, loadingObservable, initialSyncGroupsState);
732
+ }
597
733
  try {
598
- const productResponse = yield productLoaderForGroupedLoad.getProduct(correctDefaultsOnCatalogueParams(prodParams));
599
- const { productData, rootFeatureRefs, features: allRawFeatures, uuid, unit, } = productResponse;
734
+ const defaultCorrectedProdParams = correctDefaultsOnCatalogueParams(prodParams);
735
+ let initialProductConfigurationV1 = undefined;
736
+ if (initialProductConfiguration !== undefined) {
737
+ if (isProductConf(initialProductConfiguration)) {
738
+ initialProductConfigurationV1 = convertDtoProductConfToV1(initialProductConfiguration);
739
+ }
740
+ else if (isAdditionalProductConfiguration(initialProductConfiguration)) {
741
+ initialProductConfigurationV1 = initialProductConfiguration;
742
+ }
743
+ }
744
+ const productResponse = yield (initialProductConfigurationV1
745
+ ? productLoaderForGroupedLoad.postValidate(defaultCorrectedProdParams, {
746
+ knownFeatureCodes: [],
747
+ selOptions: initialProductConfigurationV1.selOptions,
748
+ })
749
+ : productLoaderForGroupedLoad.getProduct(defaultCorrectedProdParams));
750
+ const { productData, rootFeatureRefs, features: rawFeatures, notes, uuid, unit, } = productResponse;
751
+ const initiallySelected = !optional || (initialProductConfigurationV1 === null || initialProductConfigurationV1 === void 0 ? void 0 : initialProductConfigurationV1.selected) === true;
600
752
  const product = yield new Promise((initSuccess, initFail) => {
753
+ var _a;
601
754
  const p = new _CfgProductInternal(() => {
602
755
  // We absolutely do not want anyone to assign to this._configuration. So we want that field private.
603
756
  // But we can not set the api selection synchronously. And the product configuration needs "this". So we use this callback.
604
757
  // Feel free to find a nicer more readable solution :)
605
758
  initSuccess(p);
606
- }, initFail, productLoaderRaw, prodParams, settings, optional, !optional, rootFeatureRefs, allRawFeatures, uuid, unit, productData, productData.partsData.selOptions || [], loadingObservable, parent, root, additionalProductRef, syncGroupHandler);
759
+ }, initFail, productLoaderRaw, prodParams, settings, optional, initiallySelected, rootFeatureRefs !== null && rootFeatureRefs !== void 0 ? rootFeatureRefs : [], rawFeatures, notes === null || notes === void 0 ? void 0 : notes.values(), uuid, unit, productData, (_a = productData.partsData.selOptions) !== null && _a !== void 0 ? _a : [], loadingObservable, parent, root, additionalProductRef, syncGroupHandler);
607
760
  });
608
- yield product._syncAndLoadAdditionalProducts(productLoaderForGroupedLoad);
609
- // Product is guaranteed to be root
610
- yield (syncGroupHandler === null || syncGroupHandler === void 0 ? void 0 : syncGroupHandler.init(product, productLoaderForGroupedLoad));
761
+ yield product._syncAndLoadAdditionalProducts(productLoaderForGroupedLoad, initialProductConfigurationV1);
762
+ product._initialClone = yield product.clone();
763
+ if (syncGroupHandler !== undefined && !initialProductConfiguration) {
764
+ // As syncGroupHandler is only set for root product we know that we will init with root.
765
+ // If we have an initialProductConfiguration we do not run init for the syncGroups.
766
+ // The idea with an initial configuration is that the product should look as it did when
767
+ // the configuration was saved. Not running init does that. Also, the DtoProductConf
768
+ // object can contain a sync state which is then used to populate the sync state.
769
+ yield syncGroupHandler.init(product, productLoaderForGroupedLoad);
770
+ }
611
771
  return product;
612
772
  }
613
773
  catch (e) {
614
- throw augmentErrorMessage(e, "Load product request failure");
774
+ throw augmentErrorMessage(e, `Load product request (${prodParams.partNumber}) failure`);
615
775
  }
616
776
  });
617
777
  export class CfgProduct {
@@ -630,7 +790,7 @@ export class CfgProduct {
630
790
  * Setting this does not cause a validation call as toggling an optional additional product is
631
791
  * assumed to always be legal.
632
792
  */
633
- this.setSelected = (v) => __awaiter(this, void 0, void 0, function* () { return yield this._internal.setSelected(v, CfgProductBubbleMode.ToRootAndBubbleSelected); });
793
+ this.setSelected = (v) => __awaiter(this, void 0, void 0, function* () { return yield this._internal.setSelected(v, CfgProductBubbleMode.ToRootAndBubbleSelected, true); });
634
794
  /**
635
795
  * Experimental. Additional products lacks descriptions or keys that are suitably for structure
636
796
  * compare, so we use strict-order when trying to match the additional products. This makes
@@ -650,11 +810,40 @@ export class CfgProduct {
650
810
  * Gets what selections has been made on the product, recursively including product
651
811
  * configuration, optional products and additional products. Used when a full view of all
652
812
  * selections on a product is needed, such as when doing Render or Export.
813
+ * @deprecated getDtoConf provides a newer format.
814
+ * @see getDtoConf
815
+ */
816
+ this.getApiSelection = () => convertDtoProductConfToV1(this._internal.getDtoConf(CfgProdConfParts.NoExtra), true);
817
+ /**
818
+ * Applies the configuration (selections) in the passed object onto the product recursively
819
+ * including product configuration, optional products and additional products.
820
+ * @param doValidate Makes a server side validation call. These are necessary to ensure that
821
+ * the right models are loaded.
822
+ * @deprecated setDtoConf uses a newer format.
823
+ */
824
+ this.setApiSelection = (configuration, doValidate = true) => __awaiter(this, void 0, void 0, function* () { return yield this._internal.setApiSelection(configuration, doValidate); });
825
+ /**
826
+ * A newer alternative version of getApiSelection. This returns the configuration (selections)
827
+ * on the product, recursively including product configuration, optional products and additional
828
+ * products.
829
+ * This version has the following advantages over getApiSelection:
830
+ * - The format is clearer, designed to be readable
831
+ * - Makes less assumptions about the structure in the Product being unchanging over time. In
832
+ * particular, the Feature codes are included in the data, so that changes to what Features
833
+ * are used in a Product is less likely to lead to unexpected results.
834
+ * - You can request ExtendedData, ProductParams and/or SyncGroupState to be included in the
835
+ * result. This extra data is ignored when passed back into the API, but it can be very useful
836
+ * for external applications.
837
+ * The other version (getApiSelection) has the advantage of using a format directly compatible with the API:s.
838
+ * @param include Includes extra data which is not an actual part of the configuration
653
839
  */
654
- this.getApiSelection = () => convertDtoConfProdToV1(this._internal.getDtoConf(false, false), true);
655
- this.getDtoConf = (includeExtendedData = false, includeProductParams = false) => this._internal.getDtoConf(includeExtendedData, includeProductParams);
656
- this.setDtoConf = (s, doValidate = false) => __awaiter(this, void 0, void 0, function* () { return yield this._internal.setDtoConf(s, doValidate); });
657
- this.setApiSelection = (s, doValidate = false) => __awaiter(this, void 0, void 0, function* () { return yield this._internal.setApiSelection(s, doValidate); });
840
+ this.getDtoConf = (include = CfgProdConfParts.NoExtra) => this._internal.getDtoConf(include);
841
+ /**
842
+ * A newer alternative version of setApiSelection.
843
+ * @param doValidate Makes a server side validation call. These are necessary to ensure that
844
+ * the right models are loaded.
845
+ */
846
+ this.setDtoConf = (configuration, doValidate = true) => __awaiter(this, void 0, void 0, function* () { return yield this._internal.setDtoConf(configuration, doValidate); });
658
847
  this.listenForChange = (l) => this._internal.changeObservable.listen(l);
659
848
  this.stopListenForChange = (l) => this._internal.changeObservable.stopListen(l);
660
849
  this.stopAllListenForChange = () => this._internal.changeObservable.stopAllListen();
@@ -662,9 +851,9 @@ export class CfgProduct {
662
851
  this.stopListenForLoading = (l) => this._internal.loadingObservable.stopListen(l);
663
852
  this.stopAllListenForLoading = () => this._internal.loadingObservable.stopAllListen();
664
853
  }
665
- static make(productLoader, prodParams, settings) {
854
+ static make(productLoader, prodParams, settings, initialProductConfiguration) {
666
855
  return __awaiter(this, void 0, void 0, function* () {
667
- return this._makeNewRefFrom(yield _CfgProductInternal.make(productLoader, undefined, prodParams, completeSettings(settings), false, new AggregatedLoadingObservable(), undefined, undefined, undefined));
856
+ return this._makeNewRefFrom(yield _CfgProductInternal.make(productLoader, undefined, prodParams, completeSettings(settings), false, new AggregatedLoadingObservable(), undefined, undefined, undefined, initialProductConfiguration));
668
857
  });
669
858
  }
670
859
  /**
@@ -690,6 +879,12 @@ export class CfgProduct {
690
879
  get refKey() {
691
880
  return this._internal.refKey;
692
881
  }
882
+ get notes() {
883
+ return this._internal.notes;
884
+ }
885
+ get miscFiles() {
886
+ return this._internal.miscFiles;
887
+ }
693
888
  get prodParams() {
694
889
  return this._internal.prodParams;
695
890
  }
@@ -727,12 +922,27 @@ export class CfgProduct {
727
922
  /**
728
923
  * Please note that this relates to the visibility in the Configuration tree.
729
924
  * It does not affect the visibility of anything in the 3D view at all.
730
- * Visibility is affects the Configuration for this Product, but any Additional Products
925
+ * Visibility affects the Configuration for this Product, but any Additional Products
731
926
  * will not be affected.
732
927
  */
733
928
  get visible() {
734
929
  return this._internal.visible;
735
930
  }
931
+ // A similar text to the one below exists in global-message-managers.md and should be kept in sync.
932
+ /**
933
+ * Functional selection is a Catalogues feature where selecting Options on Features result in that you
934
+ * "jump" to another Product as a result of the Validate call. You normally do not notice that a functional
935
+ * selection has occurred except for the styleNr changing. Functional selection can change which Features
936
+ * from the original product call are used as root Features. This can in turn affect if serialized
937
+ * configuration can be applied or not.
938
+ *
939
+ * The SDK can currently only apply serialized configuration if the list of root Features has not changed.
940
+ * For this reason, when functional selection has happened, extracting data for external systems might work
941
+ * well, but reapplying back into Stage will probably fail.
942
+ */
943
+ get hasRootFeaturesChanged() {
944
+ return this._internal.hasRootFeaturesChanged;
945
+ }
736
946
  get rawProductData() {
737
947
  return this._internal.rawProductData;
738
948
  }
@@ -1,4 +1,4 @@
1
- import { DtoCatalogueParamsWithLang, DtoProductParamsWithLang } from "./CatalogueAPI";
1
+ import { DtoCatalogueParamsWithCidAndLang, DtoProductParamsWithCidAndLang } from "./CatalogueAPI";
2
2
  /**
3
3
  * These methods aims to provide a default suggested way of building
4
4
  * URLs to Products and Catalogues. By using consistent URL:s copy-paste
@@ -14,7 +14,7 @@ export declare class CfgReferencePathHelper {
14
14
  * @param catParams What catalogue to generate URL for.
15
15
  * @returns An URL to a catalogue
16
16
  */
17
- static getCataloguePath: (browsingRootUrl: string, catParams: DtoCatalogueParamsWithLang) => string;
17
+ static getCataloguePath: (browsingRootUrl: string, catParams: DtoCatalogueParamsWithCidAndLang) => string;
18
18
  /**
19
19
  * Use to generate URLs in our reference format. This is the format Configura uses in our integrations.
20
20
  * @param browsingRootUrl The URL where Stage browsing begins
@@ -22,6 +22,6 @@ export declare class CfgReferencePathHelper {
22
22
  * @param separator Optional, defaults to "product", but can be changed to indicate another function.
23
23
  * @returns An URL to a product
24
24
  */
25
- static getProductPath: (browsingRootUrl: string, productParams: DtoProductParamsWithLang, separator?: string) => string;
25
+ static getProductPath: (browsingRootUrl: string, productParams: DtoProductParamsWithCidAndLang, separator?: string) => string;
26
26
  }
27
27
  //# sourceMappingURL=CfgReferencePathHelper.d.ts.map
package/dist/index.d.ts CHANGED
@@ -1,7 +1,6 @@
1
1
  export * from "./CatalogueAPI.js";
2
2
  export * from "./CfgProduct.js";
3
3
  export * from "./CfgReferencePathHelper.js";
4
- export * from "./ConfigurationConverter.js";
5
4
  export * from "./io/index.js";
6
5
  export * from "./material/CfgMaterialMapping.js";
7
6
  export * from "./material/CfgMtrlApplication.js";
@@ -20,5 +19,6 @@ export * from "./tasks/formats.js";
20
19
  export * from "./tasks/TaskHandler.js";
21
20
  export * from "./utilitiesCatalogueData.js";
22
21
  export * from "./utilitiesCataloguePermission.js";
22
+ export * from "./utilitiesConfiguration.js";
23
23
  export * from "./utilitiesNumericValues.js";
24
24
  //# sourceMappingURL=index.d.ts.map
package/dist/index.js CHANGED
@@ -1,7 +1,6 @@
1
1
  export * from "./CatalogueAPI.js";
2
2
  export * from "./CfgProduct.js";
3
3
  export * from "./CfgReferencePathHelper.js";
4
- export * from "./ConfigurationConverter.js";
5
4
  export * from "./io/index.js";
6
5
  export * from "./material/CfgMaterialMapping.js";
7
6
  export * from "./material/CfgMtrlApplication.js";
@@ -20,4 +19,5 @@ export * from "./tasks/formats.js";
20
19
  export * from "./tasks/TaskHandler.js";
21
20
  export * from "./utilitiesCatalogueData.js";
22
21
  export * from "./utilitiesCataloguePermission.js";
22
+ export * from "./utilitiesConfiguration.js";
23
23
  export * from "./utilitiesNumericValues.js";