@configura/web-api 1.6.1 → 1.7.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.
@@ -143,6 +143,16 @@ export interface GetPriceListsParams {
143
143
  vendor: string;
144
144
  priceList: string;
145
145
  }
146
+ /** GetProductLegacyV2Params represents the URL parameters of getProductLegacyV2 */
147
+ export interface GetProductLegacyV2Params {
148
+ lang: string;
149
+ enterprise: string;
150
+ prdCat: string;
151
+ prdCatVersion: string;
152
+ vendor: string;
153
+ priceList: string;
154
+ partNumber: string;
155
+ }
146
156
  /** GetProductParams represents the URL parameters of getProduct */
147
157
  export interface GetProductParams {
148
158
  lang: string;
@@ -287,6 +297,16 @@ export interface PostRenderParams {
287
297
  priceList: string;
288
298
  partNumber: string;
289
299
  }
300
+ /** PostValidateLegacyV1Params represents the URL parameters of postValidateLegacyV1 */
301
+ export interface PostValidateLegacyV1Params {
302
+ lang: string;
303
+ enterprise: string;
304
+ prdCat: string;
305
+ prdCatVersion: string;
306
+ vendor: string;
307
+ priceList: string;
308
+ partNumber: string;
309
+ }
290
310
  /** PostValidateParams represents the URL parameters of postValidate */
291
311
  export interface PostValidateParams {
292
312
  lang: string;
@@ -448,11 +468,19 @@ export interface Transform {
448
468
  rot: Orientation;
449
469
  }
450
470
  /** ValidateRequest */
451
- export interface ValidateRequest {
471
+ export interface ValidateRequest extends ValidateRequestLegacyV1 {
472
+ knownFeatureCodes: Array<string>;
473
+ }
474
+ /** ValidateRequestLegacyV1 */
475
+ export interface ValidateRequestLegacyV1 {
452
476
  selOptions: Array<SelectedOption>;
453
477
  }
454
478
  /** ValidateResponse */
455
- export interface ValidateResponse {
479
+ export interface ValidateResponse extends ValidateResponseLegacyV1 {
480
+ features: Array<Feature>;
481
+ }
482
+ /** ValidateResponseLegacyV1 */
483
+ export interface ValidateResponseLegacyV1 {
456
484
  productData: ProductData;
457
485
  uuid: string;
458
486
  validated: boolean;
@@ -495,8 +523,10 @@ export declare class CatalogueAPI {
495
523
  getApplicationAreas(params: GetApplicationAreasParams): Promise<ApplicationAreasResponse>;
496
524
  postExport(params: PostExportParams, body: ExportRequest): Promise<ExportResponse>;
497
525
  getPriceLists(params: GetPriceListsParams): Promise<PriceListsResponse>;
526
+ getProductLegacyV2(params: GetProductLegacyV2Params): Promise<ProductResponse>;
498
527
  getProduct(params: GetProductParams): Promise<ProductResponse>;
499
528
  postRender(params: PostRenderParams, body: RenderRequest): Promise<RenderResponse>;
529
+ postValidateLegacyV1(params: PostValidateLegacyV1Params, body: ValidateRequestLegacyV1): Promise<ValidateResponseLegacyV1>;
500
530
  postValidate(params: PostValidateParams, body: ValidateRequest): Promise<ValidateResponse>;
501
531
  getTocTree(params: GetTocTreeParams): Promise<TOCResponse>;
502
532
  getTocFlat(params: GetTocFlatParams): Promise<TOCResponse>;
@@ -1,5 +1,7 @@
1
1
  // TODO: Fix the linter issue in Tygen and regenerate this file.
2
2
  /* eslint-disable @typescript-eslint/explicit-module-boundary-types */
3
+ // WARNING: This file was auto generated by the code in web-rnd/tygen.
4
+ // Do not commit manual changes to this file.
3
5
  var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
4
6
  function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
5
7
  return new (P || (P = Promise))(function (resolve, reject) {
@@ -147,7 +149,7 @@ export class CatalogueAPI {
147
149
  return this.fetch(this.auth.endpoint + url, options);
148
150
  });
149
151
  }
150
- getProduct(params) {
152
+ getProductLegacyV2(params) {
151
153
  return __awaiter(this, void 0, void 0, function* () {
152
154
  if (this.auth === undefined) {
153
155
  throw new Error("missing auth");
@@ -163,6 +165,22 @@ export class CatalogueAPI {
163
165
  return this.fetch(this.auth.endpoint + url, options);
164
166
  });
165
167
  }
168
+ getProduct(params) {
169
+ return __awaiter(this, void 0, void 0, function* () {
170
+ if (this.auth === undefined) {
171
+ throw new Error("missing auth");
172
+ }
173
+ const url = `/v1/catalogue/${encodeURIComponent(params.lang)}/${encodeURIComponent(params.enterprise)}/${encodeURIComponent(params.prdCat)}/${encodeURIComponent(params.prdCatVersion)}/${encodeURIComponent(params.vendor)}/${encodeURIComponent(params.priceList)}/product-v3/${encodeURIComponent(params.partNumber)}`;
174
+ const options = {
175
+ method: "GET",
176
+ headers: { "X-API-Key": this.auth.secretToken || "" },
177
+ };
178
+ if (this._alternativeReferer) {
179
+ options.headers["Alternative-Referer"] = this._alternativeReferer;
180
+ }
181
+ return this.fetch(this.auth.endpoint + url, options);
182
+ });
183
+ }
166
184
  postRender(params, body) {
167
185
  return __awaiter(this, void 0, void 0, function* () {
168
186
  if (this.auth === undefined) {
@@ -180,7 +198,7 @@ export class CatalogueAPI {
180
198
  return this.fetch(this.auth.endpoint + url, options);
181
199
  });
182
200
  }
183
- postValidate(params, body) {
201
+ postValidateLegacyV1(params, body) {
184
202
  return __awaiter(this, void 0, void 0, function* () {
185
203
  if (this.auth === undefined) {
186
204
  throw new Error("missing auth");
@@ -197,6 +215,23 @@ export class CatalogueAPI {
197
215
  return this.fetch(this.auth.endpoint + url, options);
198
216
  });
199
217
  }
218
+ postValidate(params, body) {
219
+ return __awaiter(this, void 0, void 0, function* () {
220
+ if (this.auth === undefined) {
221
+ throw new Error("missing auth");
222
+ }
223
+ const url = `/v1/catalogue/${encodeURIComponent(params.lang)}/${encodeURIComponent(params.enterprise)}/${encodeURIComponent(params.prdCat)}/${encodeURIComponent(params.prdCatVersion)}/${encodeURIComponent(params.vendor)}/${encodeURIComponent(params.priceList)}/product/${encodeURIComponent(params.partNumber)}/validate-v2`;
224
+ const options = {
225
+ method: "POST",
226
+ headers: { "X-API-Key": this.auth.secretToken || "" },
227
+ body: JSON.stringify(body),
228
+ };
229
+ if (this._alternativeReferer) {
230
+ options.headers["Alternative-Referer"] = this._alternativeReferer;
231
+ }
232
+ return this.fetch(this.auth.endpoint + url, options);
233
+ });
234
+ }
200
235
  getTocTree(params) {
201
236
  return __awaiter(this, void 0, void 0, function* () {
202
237
  if (this.auth === undefined) {
@@ -76,6 +76,7 @@ export declare class _CfgProductInternal {
76
76
  private _additionalProductRef;
77
77
  private readonly _syncGroupHandler;
78
78
  static make: (productLoaderRaw: ProductLoader, productLoaderForGroupedLoad: ProductLoader | undefined, lang: string, catId: CatalogueParams, partNumber: string, settings: CfgProductSettings, optional: boolean, loadingObservable: AggregatedLoadingObservable, parent: _CfgProductInternal | undefined, root: _CfgProductInternal | undefined, additionalProductRef: AdditionalProductRef | undefined) => Promise<_CfgProductInternal>;
79
+ _initialClone: _CfgProductInternal | undefined;
79
80
  private constructor();
80
81
  readonly root: _CfgProductInternal;
81
82
  private _destroyed;
@@ -86,8 +87,18 @@ export declare class _CfgProductInternal {
86
87
  readonly changeObservable: Observable<CfgProductChangeNotification>;
87
88
  get selected(): boolean;
88
89
  readonly isAdditionalProduct: boolean;
90
+ /**
91
+ * Please note that cloning an additional product will make the clone believe is is
92
+ * an additional product, even if it has no parent and root.
93
+ * Providing the parent and root of what you clone as arguments is unwise as it will
94
+ * make changes you do on the clone be propagated up to the original non-clone root product.
95
+ */
89
96
  clone(parent?: _CfgProductInternal, root?: _CfgProductInternal): Promise<_CfgProductInternal>;
90
97
  destroy: () => void;
98
+ /**
99
+ * Reset will reset the product to its initial state
100
+ */
101
+ reset: () => Promise<void>;
91
102
  _updateAdditionalProdRef(p: AdditionalProductRef): void;
92
103
  get description(): string | undefined;
93
104
  get rootNodeSources(): RootNodeSource[] | undefined;
@@ -14,7 +14,7 @@ import { CfgProductConfiguration } from "./productConfiguration/CfgProductConfig
14
14
  import { collectAdditionalProductRefs } from "./productConfiguration/utilitiesProductConfiguration.js";
15
15
  import { wrapWithCache } from "./productLoader.js";
16
16
  import { SyncGroupsHandler } from "./syncGroups/SyncGroupsHandler.js";
17
- import { comparePricesObjects, correctDefaultsOnCatalogueParams, isSameCatalogueParams, isSameProductRef, makeProductKey, } from "./utilitiesCatalogueData.js";
17
+ import { compareCfgProductData, comparePricesObjects, correctDefaultsOnCatalogueParams, isSameCatalogueParams, isSameProductRef, makeProductKey, } from "./utilitiesCatalogueData.js";
18
18
  function completeSettings(incompleteSettings) {
19
19
  var _a;
20
20
  return {
@@ -51,7 +51,7 @@ function isDescriptionMatch(l, r) {
51
51
  * the class that should be used and interacted with.
52
52
  */
53
53
  export class _CfgProductInternal {
54
- constructor(initSuccess, initFail, _productLoaderRaw, lang, catId, partNumber, settings, optional, selected, rootFeatureRefs, allRawFeatures, uuid, _rawUnit, _rawProductData, apiSelection, loadingObservable, parent, root, _additionalProductRef, _syncGroupHandler) {
54
+ constructor(initSuccess, initFail, _productLoaderRaw, lang, catId, partNumber, settings, optional, selected, rootFeatureRefs, rawFeatures, uuid, _rawUnit, _rawProductData, apiSelection, loadingObservable, parent, root, _additionalProductRef, _syncGroupHandler) {
55
55
  var _a;
56
56
  this._productLoaderRaw = _productLoaderRaw;
57
57
  this.lang = lang;
@@ -76,6 +76,14 @@ export class _CfgProductInternal {
76
76
  additionalProduct.destroy();
77
77
  }
78
78
  };
79
+ /**
80
+ * Reset will reset the product to its initial state
81
+ */
82
+ this.reset = () => __awaiter(this, void 0, void 0, function* () {
83
+ if (this._initialClone !== undefined) {
84
+ yield this.copyFrom(this._initialClone, true);
85
+ }
86
+ });
79
87
  this._notifyAllOfChange = (bubbleMode) => __awaiter(this, void 0, void 0, function* () {
80
88
  if (bubbleMode === CfgProductBubbleMode.Stop) {
81
89
  return;
@@ -161,9 +169,16 @@ export class _CfgProductInternal {
161
169
  productLoaderForGroupedLoad || wrapWithCache(this._productLoaderRaw);
162
170
  let change = false;
163
171
  if (sourceProduct !== undefined) {
164
- this._rawProductData = sourceProduct.rawProductData;
165
- this.configuration._internal.populateFeatures(sourceProduct.configuration.rootFeatureRefs);
166
- change = true; // We can not know if this is an actual change, so we assume it is
172
+ if (!compareCfgProductData(this._rawProductData, sourceProduct.rawProductData)) {
173
+ this._rawProductData = sourceProduct.rawProductData;
174
+ change = true;
175
+ }
176
+ if (this.configuration._internal.addRawFeatures(sourceProduct.configuration.rawFeatures, false)) {
177
+ change = true;
178
+ }
179
+ if (this.configuration._internal.populateFeatures(sourceProduct.configuration.rootFeatureRefs)) {
180
+ change = true;
181
+ }
167
182
  }
168
183
  const configurationChange = yield this.configuration._internal.setApiSelection(s.selOptions, false);
169
184
  if (configurationChange) {
@@ -292,7 +307,10 @@ export class _CfgProductInternal {
292
307
  const token = this.loadingObservable.startChildLoading();
293
308
  this._revalidateInProgressToken = token;
294
309
  try {
295
- const response = yield productLoader.postValidate(Object.assign(Object.assign({ lang: this.lang }, correctDefaultsOnCatalogueParams(this.catId)), { partNumber: this.partNumber }), { selOptions: configuration.getApiSelection() });
310
+ const response = yield productLoader.postValidate(Object.assign(Object.assign({ lang: this.lang }, correctDefaultsOnCatalogueParams(this.catId)), { partNumber: this.partNumber }), {
311
+ selOptions: configuration.getApiSelection(),
312
+ knownFeatureCodes: configuration.rawFeatures.map((f) => f.code),
313
+ });
296
314
  // The revalidateInProgressToken is used to know if some other revalidate
297
315
  // call has happened after this call, thereby making this call obsolete.
298
316
  // This is a bit crude in that it does not actually cancel previous validate
@@ -311,9 +329,10 @@ export class _CfgProductInternal {
311
329
  if (this._destroyed) {
312
330
  return false;
313
331
  }
314
- const { productData, rootFeatureRefs } = response;
332
+ const { productData, rootFeatureRefs, features } = response;
315
333
  const pricesUpdated = !comparePricesObjects(this.prices, productData.partsData.prices);
316
334
  this._rawProductData = productData;
335
+ configuration._internal.addRawFeatures(features, true);
317
336
  if (rootFeatureRefs !== undefined) {
318
337
  configuration._internal.populateFeatures(rootFeatureRefs);
319
338
  }
@@ -403,19 +422,25 @@ export class _CfgProductInternal {
403
422
  this.root = root !== null && root !== void 0 ? root : this;
404
423
  this.key = makeProductKey(catId, (_a = _additionalProductRef === null || _additionalProductRef === void 0 ? void 0 : _additionalProductRef.refKey) !== null && _a !== void 0 ? _a : partNumber);
405
424
  this._selected = optional ? selected : undefined;
406
- this.isAdditionalProduct = parent !== undefined;
407
- this._configuration = CfgProductConfiguration.make(initSuccess, initFail, rootFeatureRefs, allRawFeatures, apiSelection, this, this.root);
425
+ this.isAdditionalProduct = _additionalProductRef !== undefined;
426
+ this._configuration = CfgProductConfiguration.make(initSuccess, initFail, rootFeatureRefs, rawFeatures, apiSelection, this, this.root);
408
427
  }
409
428
  get selected() {
410
429
  return this._selected !== false;
411
430
  }
431
+ /**
432
+ * Please note that cloning an additional product will make the clone believe is is
433
+ * an additional product, even if it has no parent and root.
434
+ * Providing the parent and root of what you clone as arguments is unwise as it will
435
+ * make changes you do on the clone be propagated up to the original non-clone root product.
436
+ */
412
437
  clone(parent, root) {
413
438
  return __awaiter(this, void 0, void 0, function* () {
414
439
  const product = yield new Promise((initSuccess, initFail) => {
415
440
  var _a;
416
441
  const p = new _CfgProductInternal(() => {
417
442
  initSuccess(p);
418
- }, initFail, this._productLoaderRaw, this.lang, this.catId, this.partNumber, 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());
443
+ }, initFail, this._productLoaderRaw, this.lang, this.catId, this.partNumber, this.settings, this.optional, this.selected, this._configuration.rootFeatureRefs, this._configuration.rawFeatures, 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());
419
444
  });
420
445
  for (const additionalProduct of this.additionalProducts) {
421
446
  product.additionalProducts.push(CfgProduct._makeNewRefFrom(yield additionalProduct._internal.clone(product, root || product)));
@@ -512,7 +537,19 @@ export class _CfgProductInternal {
512
537
  if (this._selected === v) {
513
538
  return false;
514
539
  }
540
+ // Vitally important that this happens before the call to reset. An optional
541
+ // additional product is always deselected at start, so this way the reset won't cause
542
+ // infinite loops
515
543
  this._selected = v;
544
+ if (!v) {
545
+ yield this.reset();
546
+ }
547
+ if (v) {
548
+ const syncGroupHandler = this.syncGroupHandler;
549
+ if (syncGroupHandler !== undefined) {
550
+ yield syncGroupHandler.init(this.root, wrapWithCache(this._productLoaderRaw));
551
+ }
552
+ }
516
553
  yield this._notifyAllOfChange(bubbleMode);
517
554
  return true;
518
555
  });
@@ -576,18 +613,22 @@ lang, catId, partNumber, settings, optional, loadingObservable, parent, root, ad
576
613
  : undefined;
577
614
  try {
578
615
  const productResponse = yield productLoaderForGroupedLoad.getProduct(Object.assign(Object.assign({ lang }, correctDefaultsOnCatalogueParams(catId)), { partNumber }));
579
- const { productData, rootFeatureRefs, features: allRawFeatures, uuid, unit, } = productResponse;
616
+ const { productData, rootFeatureRefs, features: rawFeatures, uuid, unit, } = productResponse;
580
617
  const product = yield new Promise((initSuccess, initFail) => {
618
+ var _a;
581
619
  const p = new _CfgProductInternal(() => {
582
620
  // We absolutely do not want anyone to assign to this._configuration. So we want that field private.
583
621
  // But we can not set the api selection synchronously. And the product configuration needs "this". So we use this callback.
584
622
  // Feel free to find a nicer more readable solution :)
585
623
  initSuccess(p);
586
- }, initFail, productLoaderRaw, lang, catId, partNumber, settings, optional, !optional, rootFeatureRefs, allRawFeatures, uuid, unit, productData, productData.partsData.selOptions || [], loadingObservable, parent, root, additionalProductRef, syncGroupHandler);
624
+ }, initFail, productLoaderRaw, lang, catId, partNumber, settings, optional, !optional, rootFeatureRefs, rawFeatures, uuid, unit, productData, (_a = productData.partsData.selOptions) !== null && _a !== void 0 ? _a : [], loadingObservable, parent, root, additionalProductRef, syncGroupHandler);
587
625
  });
588
626
  yield product._syncAndLoadAdditionalProducts(productLoaderForGroupedLoad);
589
- // Product is guaranteed to be root
590
- yield (syncGroupHandler === null || syncGroupHandler === void 0 ? void 0 : syncGroupHandler.init(product, productLoaderForGroupedLoad));
627
+ product._initialClone = yield product.clone();
628
+ if (syncGroupHandler !== undefined) {
629
+ // As syncGroupHandler is only set for root product we know that we will init with root
630
+ yield syncGroupHandler.init(product, productLoaderForGroupedLoad);
631
+ }
591
632
  return product;
592
633
  }
593
634
  catch (e) {
@@ -31,14 +31,13 @@ export declare type FeatureChangeNotification = {
31
31
  */
32
32
  export declare class _CfgFeatureInternal {
33
33
  readonly rawFeature: Feature;
34
- private readonly allRawFeatures;
35
- readonly key: string;
34
+ private readonly rawFeatures;
35
+ private _key;
36
36
  readonly parent: _CfgProductConfigurationInternal | _CfgOptionInternal;
37
37
  readonly parentConfiguration: _CfgProductConfigurationInternal;
38
38
  readonly parentProduct: _CfgProductInternal;
39
39
  readonly rootProduct: _CfgProductInternal;
40
- constructor(rawFeature: Feature, allRawFeatures: Feature[], key: string, // Unique amongst siblings
41
- parent: _CfgProductConfigurationInternal | _CfgOptionInternal, parentConfiguration: _CfgProductConfigurationInternal, parentProduct: _CfgProductInternal, rootProduct: _CfgProductInternal);
40
+ constructor(rawFeature: Feature, rawFeatures: Feature[], _key: string, parent: _CfgProductConfigurationInternal | _CfgOptionInternal, parentConfiguration: _CfgProductConfigurationInternal, parentProduct: _CfgProductInternal, rootProduct: _CfgProductInternal);
42
41
  readonly selectionType: SelectionType;
43
42
  private _options;
44
43
  private readonly _selectedOptions;
@@ -47,6 +46,8 @@ export declare class _CfgFeatureInternal {
47
46
  readonly changeObservable: Observable<FeatureChangeNotification>;
48
47
  get code(): string;
49
48
  get groupCode(): string | undefined;
49
+ get key(): string;
50
+ set key(k: string);
50
51
  get isUseNumericValue(): boolean;
51
52
  get numericValue(): number | undefined;
52
53
  setNumericValue: (val: number) => Promise<boolean>;
@@ -129,7 +130,7 @@ export declare class _CfgFeatureInternal {
129
130
  }
130
131
  export declare class CfgFeature {
131
132
  readonly _internal: _CfgFeatureInternal;
132
- static make(rawFeature: Feature, allRawFeatures: Feature[], key: string, parent: _CfgProductConfigurationInternal | _CfgOptionInternal, parentConfiguration: _CfgProductConfigurationInternal, parentProduct: _CfgProductInternal, rootProduct: _CfgProductInternal): CfgFeature;
133
+ static make(rawFeature: Feature, rawFeatures: Feature[], key: string, parent: _CfgProductConfigurationInternal | _CfgOptionInternal, parentConfiguration: _CfgProductConfigurationInternal, parentProduct: _CfgProductInternal, rootProduct: _CfgProductInternal): CfgFeature;
133
134
  /**
134
135
  * Makes an object wrapping the passed object. This is not a clone method,
135
136
  * it is a method to make a new outer reference. Like a shallow copy.
@@ -62,11 +62,10 @@ function doFreshRefOption(options, optionInternal, beforeNotify) {
62
62
  * should be used and interacted with.
63
63
  */
64
64
  export class _CfgFeatureInternal {
65
- constructor(rawFeature, allRawFeatures, key, // Unique amongst siblings
66
- parent, parentConfiguration, parentProduct, rootProduct) {
65
+ constructor(rawFeature, rawFeatures, _key, parent, parentConfiguration, parentProduct, rootProduct) {
67
66
  this.rawFeature = rawFeature;
68
- this.allRawFeatures = allRawFeatures;
69
- this.key = key;
67
+ this.rawFeatures = rawFeatures;
68
+ this._key = _key;
70
69
  this.parent = parent;
71
70
  this.parentConfiguration = parentConfiguration;
72
71
  this.parentProduct = parentProduct;
@@ -398,6 +397,12 @@ export class _CfgFeatureInternal {
398
397
  get groupCode() {
399
398
  return this.rawFeature.groupCode;
400
399
  }
400
+ get key() {
401
+ return this._key;
402
+ }
403
+ set key(k) {
404
+ this._key = k;
405
+ }
401
406
  get isUseNumericValue() {
402
407
  return this.rawFeature.numericOrder;
403
408
  }
@@ -503,7 +508,7 @@ export class _CfgFeatureInternal {
503
508
  const hasDuplicateDescription = someMatch(this.rawFeature.options, (l, r) => {
504
509
  return l.description.toLowerCase() === r.description.toLowerCase();
505
510
  });
506
- this._options = this.rawFeature.options.map((o) => CfgOption.make(o, this.allRawFeatures, hasDuplicateDescription, this, this.parentConfiguration, this.parentProduct, this.rootProduct));
511
+ this._options = this.rawFeature.options.map((o) => CfgOption.make(o, this.rawFeatures, hasDuplicateDescription, this, this.parentConfiguration, this.parentProduct, this.rootProduct));
507
512
  }
508
513
  return this._options;
509
514
  }
@@ -562,8 +567,8 @@ export class CfgFeature {
562
567
  this.listenForChange = (l) => this._internal.changeObservable.listen(l);
563
568
  this.stopListenForChange = (l) => this._internal.changeObservable.stopListen(l);
564
569
  }
565
- static make(rawFeature, allRawFeatures, key, parent, parentConfiguration, parentProduct, rootProduct) {
566
- return new this(new _CfgFeatureInternal(rawFeature, allRawFeatures, key, parent, parentConfiguration, parentProduct, rootProduct));
570
+ static make(rawFeature, rawFeatures, key, parent, parentConfiguration, parentProduct, rootProduct) {
571
+ return new this(new _CfgFeatureInternal(rawFeature, rawFeatures, key, parent, parentConfiguration, parentProduct, rootProduct));
567
572
  }
568
573
  /**
569
574
  * Makes an object wrapping the passed object. This is not a clone method,
@@ -582,6 +587,7 @@ export class CfgFeature {
582
587
  get selectionType() {
583
588
  return this._internal.selectionType;
584
589
  }
590
+ // Unique amongst siblings. Can change. Only use for presentation layer i.e. React.
585
591
  get key() {
586
592
  return this._internal.key;
587
593
  }
@@ -59,12 +59,12 @@ export declare enum ProductConfigurationBubbleMode {
59
59
  */
60
60
  export declare class _CfgOptionInternal {
61
61
  readonly rawOption: Option;
62
- private readonly allRawFeatures;
62
+ private readonly rawFeatures;
63
63
  readonly parent: _CfgFeatureInternal;
64
64
  readonly parentConfiguration: _CfgProductConfigurationInternal;
65
65
  readonly parentProduct: _CfgProductInternal;
66
66
  readonly rootProduct: _CfgProductInternal;
67
- constructor(rawOption: Option, allRawFeatures: Feature[], siblingHasDuplicateDescription: boolean, parent: _CfgFeatureInternal, parentConfiguration: _CfgProductConfigurationInternal, parentProduct: _CfgProductInternal, rootProduct: _CfgProductInternal);
67
+ constructor(rawOption: Option, rawFeatures: Feature[], siblingHasDuplicateDescription: boolean, parent: _CfgFeatureInternal, parentConfiguration: _CfgProductConfigurationInternal, parentProduct: _CfgProductInternal, rootProduct: _CfgProductInternal);
68
68
  private _features;
69
69
  private _mtrlApplications;
70
70
  readonly key: string;
@@ -98,7 +98,7 @@ export declare class _CfgOptionInternal {
98
98
  }
99
99
  export declare class CfgOption {
100
100
  readonly _internal: _CfgOptionInternal;
101
- static make(rawOption: Option, allRawFeatures: Feature[], siblingHasDuplicateDescription: boolean, parent: _CfgFeatureInternal, parentConfiguration: _CfgProductConfigurationInternal, parentProduct: _CfgProductInternal, rootProduct: _CfgProductInternal): CfgOption;
101
+ static make(rawOption: Option, rawFeatures: Feature[], siblingHasDuplicateDescription: boolean, parent: _CfgFeatureInternal, parentConfiguration: _CfgProductConfigurationInternal, parentProduct: _CfgProductInternal, rootProduct: _CfgProductInternal): CfgOption;
102
102
  /**
103
103
  * Makes an object wrapping the passed object. This is not a clone method,
104
104
  * it is a method to make a new outer reference. Like a shallow copy.
@@ -80,9 +80,9 @@ function doesChildrenShareOptionsCode(features) {
80
80
  * the class that should be used and interacted with.
81
81
  */
82
82
  export class _CfgOptionInternal {
83
- constructor(rawOption, allRawFeatures, siblingHasDuplicateDescription, parent, parentConfiguration, parentProduct, rootProduct) {
83
+ constructor(rawOption, rawFeatures, siblingHasDuplicateDescription, parent, parentConfiguration, parentProduct, rootProduct) {
84
84
  this.rawOption = rawOption;
85
- this.allRawFeatures = allRawFeatures;
85
+ this.rawFeatures = rawFeatures;
86
86
  this.parent = parent;
87
87
  this.parentConfiguration = parentConfiguration;
88
88
  this.parentProduct = parentProduct;
@@ -314,7 +314,7 @@ export class _CfgOptionInternal {
314
314
  get features() {
315
315
  if (this._features === undefined) {
316
316
  const allRefs = this.rawOption.featureRefs || [];
317
- const features = syncCfgFeatures(allRefs, [], this.allRawFeatures, this, this.parentConfiguration, this.parentProduct, this.rootProduct);
317
+ const features = syncCfgFeatures(allRefs, [], this.rawFeatures, this, this.parentConfiguration, this.parentProduct, this.rootProduct);
318
318
  if (doesChildrenShareOptionsCode(features)) {
319
319
  throw new Error("Stage does not yet properly support Options that has multiple sub-features with overlapping option codes.");
320
320
  }
@@ -353,8 +353,8 @@ export class CfgOption {
353
353
  this.listenForChange = (l) => this._internal.changeObservable.listen(l);
354
354
  this.stopListenForChange = (l) => this._internal.changeObservable.stopListen(l);
355
355
  }
356
- static make(rawOption, allRawFeatures, siblingHasDuplicateDescription, parent, parentConfiguration, parentProduct, rootProduct) {
357
- return new this(new _CfgOptionInternal(rawOption, allRawFeatures, siblingHasDuplicateDescription, parent, parentConfiguration, parentProduct, rootProduct));
356
+ static make(rawOption, rawFeatures, siblingHasDuplicateDescription, parent, parentConfiguration, parentProduct, rootProduct) {
357
+ return new this(new _CfgOptionInternal(rawOption, rawFeatures, siblingHasDuplicateDescription, parent, parentConfiguration, parentProduct, rootProduct));
358
358
  }
359
359
  /**
360
360
  * Makes an object wrapping the passed object. This is not a clone method,
@@ -20,14 +20,14 @@ export declare type StretchMap = Map<string, {
20
20
  * modified. CfgProductConfiguration is the class that should be used and interacted with.
21
21
  */
22
22
  export declare class _CfgProductConfigurationInternal {
23
- readonly allRawFeatures: Feature[];
24
23
  readonly parentProduct: _CfgProductInternal;
25
24
  readonly rootProduct: _CfgProductInternal;
26
- static _makeUninitialized(rootFeatureRefs: FeatureRef[], allRawFeatures: Feature[], // Flat packed. All the features that can appear anyplace in the selection tree.
25
+ static _makeUninitialized(rootFeatureRefs: FeatureRef[], rawFeatures: Feature[], // Flat packed. All the features that can currently appear anyplace in the selection tree.
27
26
  parentProduct: _CfgProductInternal, rootProduct: _CfgProductInternal): _CfgProductConfigurationInternal;
28
27
  private constructor();
29
28
  readonly key = "~";
30
29
  private _rootFeatureRefs;
30
+ readonly accumulatedRawFeatures: Feature[];
31
31
  private _features;
32
32
  readonly stretchReferenceLengthsByMeasureParamCode: StretchMap;
33
33
  readonly changeObservable: Observable<ProductConfigurationChangeNotification>;
@@ -51,7 +51,16 @@ export declare class _CfgProductConfigurationInternal {
51
51
  tryMatchSelection: (other: _CfgProductConfigurationInternal, descriptionMatch: boolean | undefined, validate: boolean) => Promise<boolean>;
52
52
  /** Only selected features. */
53
53
  _getFeaturesWithCode: (code: string) => _CfgFeatureInternal[];
54
- populateFeatures: (rootFeatureRefs: FeatureRef[]) => void;
54
+ /**
55
+ * Extends the list of loaded potentially used features. Will warn for but ignore duplicates.
56
+ * Returns true if a change happened.
57
+ */
58
+ addRawFeatures: (rawFeatures: Feature[], warnForDuplicates: boolean) => boolean;
59
+ /**
60
+ * Populates _features based on the passed @param rootFeatureRefs .
61
+ * @return true if a change happened.
62
+ */
63
+ populateFeatures: (rootFeatureRefs: FeatureRef[]) => boolean;
55
64
  setStretchReferenceLength: (measureParamCode: string, referenceLength: number | undefined, unit: LengthUnit, doNotify?: boolean) => Promise<boolean>;
56
65
  }
57
66
  export declare class CfgProductConfiguration {
@@ -61,7 +70,7 @@ export declare class CfgProductConfiguration {
61
70
  * CfgProductConfiguration, but is not properly initialized until the initDone-callback
62
71
  * has been called.
63
72
  */
64
- static make(initSuccess: (c: CfgProductConfiguration) => void, initFail: (error: Error) => void, rootFeatureRefs: FeatureRef[], allRawFeatures: Feature[], // Flat packed. All the features that can appear anyplace in the selection tree.
73
+ static make(initSuccess: (c: CfgProductConfiguration) => void, initFail: (error: Error) => void, rootFeatureRefs: FeatureRef[], rawFeatures: Feature[], // Flat packed. All the features that can appear anyplace in the selection tree.
65
74
  apiSelection: SelectedOption[], parentProduct: _CfgProductInternal, rootProduct: _CfgProductInternal): CfgProductConfiguration;
66
75
  /**
67
76
  * Makes an object wrapping the passed object. This is not a clone method, it is a method to
@@ -81,11 +90,11 @@ export declare class CfgProductConfiguration {
81
90
  get rootProduct(): CfgProduct;
82
91
  get key(): string;
83
92
  /**
84
- * Every (unprocessed) feature which might be used in this product. This is constant for a
85
- * product load. What features are actually used is controlled by rootFeatureRefs and what
93
+ * Every (unprocessed) feature that is currently loaded for this product. This can be extended
94
+ * by validation calls. What features are actually used is controlled by rootFeatureRefs and what
86
95
  * options are selected.
87
96
  */
88
- get allRawFeatures(): Feature[];
97
+ get rawFeatures(): Feature[];
89
98
  /** What features are used in the root of this. This can change with new validate calls. */
90
99
  get rootFeatureRefs(): FeatureRef[];
91
100
  /** The root features at the root of the selection tree. */
@@ -18,13 +18,12 @@ import { syncCfgFeatures } from "./utilitiesProductConfiguration.js";
18
18
  * modified. CfgProductConfiguration is the class that should be used and interacted with.
19
19
  */
20
20
  export class _CfgProductConfigurationInternal {
21
- constructor(allRawFeatures, // Flat packed. All the features that can appear anyplace in the selection tree.
22
- parentProduct, rootProduct) {
23
- this.allRawFeatures = allRawFeatures;
21
+ constructor(rawFeatures, parentProduct, rootProduct) {
24
22
  this.parentProduct = parentProduct;
25
23
  this.rootProduct = rootProduct;
26
24
  this.key = "~";
27
25
  this._rootFeatureRefs = [];
26
+ this.accumulatedRawFeatures = []; // Flat packed. May be extended in validate calls.
28
27
  this._features = [];
29
28
  this.changeObservable = new Observable();
30
29
  this._notifyAllOfChange = (bubbleMode) => __awaiter(this, void 0, void 0, function* () {
@@ -111,9 +110,37 @@ export class _CfgProductConfigurationInternal {
111
110
  agg.push(...feature._internal._getFeaturesWithCode(code));
112
111
  return agg;
113
112
  }, []);
113
+ /**
114
+ * Extends the list of loaded potentially used features. Will warn for but ignore duplicates.
115
+ * Returns true if a change happened.
116
+ */
117
+ this.addRawFeatures = (rawFeatures, warnForDuplicates) => {
118
+ let change = false;
119
+ const accumulatedRawFeatures = this.accumulatedRawFeatures;
120
+ for (const rawFeature of rawFeatures) {
121
+ const code = rawFeature.code;
122
+ if (accumulatedRawFeatures.find((f) => code === f.code)) {
123
+ if (warnForDuplicates) {
124
+ console.warn(`Feature ${code} was already loaded. Will ignore it.`);
125
+ }
126
+ continue;
127
+ }
128
+ accumulatedRawFeatures.push(rawFeature);
129
+ change = true;
130
+ }
131
+ return change;
132
+ };
133
+ /**
134
+ * Populates _features based on the passed @param rootFeatureRefs .
135
+ * @return true if a change happened.
136
+ */
114
137
  this.populateFeatures = (rootFeatureRefs) => {
138
+ if (compareArrays(this._rootFeatureRefs, rootFeatureRefs, (l, r) => l.code === r.code, true)) {
139
+ return false;
140
+ }
115
141
  this._rootFeatureRefs = rootFeatureRefs;
116
- this._features = syncCfgFeatures(rootFeatureRefs, this._features, this.allRawFeatures, this, this, this.parentProduct, this.rootProduct);
142
+ this._features = syncCfgFeatures(rootFeatureRefs, this._features, this.accumulatedRawFeatures, this, this, this.parentProduct, this.rootProduct);
143
+ return true;
117
144
  };
118
145
  this.setStretchReferenceLength = (measureParamCode, referenceLength, unit, doNotify = true) => __awaiter(this, void 0, void 0, function* () {
119
146
  if (measureParamCode === "") {
@@ -160,6 +187,7 @@ export class _CfgProductConfigurationInternal {
160
187
  console.log(`Use "window['${dbgName}']" or window.conf to access conf.`);
161
188
  }
162
189
  /* eslint-enable */
190
+ this.addRawFeatures(rawFeatures, true);
163
191
  /**
164
192
  * Although the measurement-datas are also passed in validate-calls they are only used at
165
193
  * initial product creation. The data is assumed to always be the same for a product, so
@@ -196,9 +224,9 @@ export class _CfgProductConfigurationInternal {
196
224
  }
197
225
  this.stretchReferenceLengthsByMeasureParamCode = stretchReferenceLengthsByMeasureParamCode;
198
226
  }
199
- static _makeUninitialized(rootFeatureRefs, allRawFeatures, // Flat packed. All the features that can appear anyplace in the selection tree.
227
+ static _makeUninitialized(rootFeatureRefs, rawFeatures, // Flat packed. All the features that can currently appear anyplace in the selection tree.
200
228
  parentProduct, rootProduct) {
201
- const configuration = new this(allRawFeatures, parentProduct, rootProduct);
229
+ const configuration = new this(rawFeatures, parentProduct, rootProduct);
202
230
  configuration.populateFeatures(rootFeatureRefs);
203
231
  return configuration;
204
232
  }
@@ -261,9 +289,9 @@ export class CfgProductConfiguration {
261
289
  * CfgProductConfiguration, but is not properly initialized until the initDone-callback
262
290
  * has been called.
263
291
  */
264
- static make(initSuccess, initFail, rootFeatureRefs, allRawFeatures, // Flat packed. All the features that can appear anyplace in the selection tree.
292
+ static make(initSuccess, initFail, rootFeatureRefs, rawFeatures, // Flat packed. All the features that can appear anyplace in the selection tree.
265
293
  apiSelection, parentProduct, rootProduct) {
266
- const internal = _CfgProductConfigurationInternal._makeUninitialized(rootFeatureRefs, allRawFeatures, parentProduct, rootProduct);
294
+ const internal = _CfgProductConfigurationInternal._makeUninitialized(rootFeatureRefs, rawFeatures, parentProduct, rootProduct);
267
295
  const external = new this(internal);
268
296
  // Note the async-callback at the end of the following line. The call is async.
269
297
  internal
@@ -291,12 +319,12 @@ export class CfgProductConfiguration {
291
319
  return this._internal.key;
292
320
  }
293
321
  /**
294
- * Every (unprocessed) feature which might be used in this product. This is constant for a
295
- * product load. What features are actually used is controlled by rootFeatureRefs and what
322
+ * Every (unprocessed) feature that is currently loaded for this product. This can be extended
323
+ * by validation calls. What features are actually used is controlled by rootFeatureRefs and what
296
324
  * options are selected.
297
325
  */
298
- get allRawFeatures() {
299
- return this._internal.allRawFeatures;
326
+ get rawFeatures() {
327
+ return this._internal.accumulatedRawFeatures;
300
328
  }
301
329
  /** What features are used in the root of this. This can change with new validate calls. */
302
330
  get rootFeatureRefs() {
@@ -8,7 +8,7 @@ import { CfgProductConfiguration, _CfgProductConfigurationInternal } from "./Cfg
8
8
  * Returns a new array of CfgFeatures that maps to the newFeatureRefs array. Uses CfgFeatures from
9
9
  * currentFeatures if they can be found, otherwise makes new.
10
10
  */
11
- export declare function syncCfgFeatures(newFeatureRefs: FeatureRef[], currentFeatures: CfgFeature[], allRawFeatures: Feature[], parent: _CfgProductConfigurationInternal | _CfgOptionInternal, parentConfiguration: _CfgProductConfigurationInternal, parentProduct: _CfgProductInternal, rootProduct: _CfgProductInternal): CfgFeature[];
11
+ export declare function syncCfgFeatures(newFeatureRefs: FeatureRef[], currentFeatures: CfgFeature[], rawFeatures: Feature[], parent: _CfgProductConfigurationInternal | _CfgOptionInternal, parentConfiguration: _CfgProductConfigurationInternal, parentProduct: _CfgProductInternal, rootProduct: _CfgProductInternal): CfgFeature[];
12
12
  export declare function getMtrlPreview(mtrlApplications: CfgMtrlApplication[] | undefined): string | undefined;
13
13
  /**
14
14
  * Recursively find all additional product references given a product configuration.
@@ -6,11 +6,11 @@ import { CfgProductConfiguration, } from "./CfgProductConfiguration.js";
6
6
  * Returns a new array of CfgFeatures that maps to the newFeatureRefs array. Uses CfgFeatures from
7
7
  * currentFeatures if they can be found, otherwise makes new.
8
8
  */
9
- export function syncCfgFeatures(newFeatureRefs, currentFeatures, allRawFeatures, parent, parentConfiguration, parentProduct, rootProduct) {
9
+ export function syncCfgFeatures(newFeatureRefs, currentFeatures, rawFeatures, parent, parentConfiguration, parentProduct, rootProduct) {
10
10
  const usedRawFeatures = newFeatureRefs
11
11
  .map((r) => r.code)
12
12
  .map((c) => {
13
- const rawFeature = allRawFeatures.find((f) => c === f.code);
13
+ const rawFeature = rawFeatures.find((f) => c === f.code);
14
14
  if (rawFeature === undefined) {
15
15
  throw new Error(`Feature not found. Requested feature code: "${c}".`);
16
16
  }
@@ -38,12 +38,19 @@ export function syncCfgFeatures(newFeatureRefs, currentFeatures, allRawFeatures,
38
38
  // products with similar feature-options tree and trying
39
39
  // to retain made selections
40
40
  const key = fDescription + (fDescription === "" || hasDuplicateDescription ? fCode : "");
41
- const existingFeature = currentFeatures.find((cfgF) => cfgF.code === fCode && cfgF.key === key);
41
+ const existingFeature = currentFeatures.find((cfgF) => cfgF.code === fCode);
42
42
  if (existingFeature !== undefined) {
43
+ if (hasDuplicateDescription) {
44
+ // HasDuplicateDescription could mean the key need to be more specific compared
45
+ // to the one we already have, so then we set it. An old duplicate description
46
+ // key will cause no harm as it is guaranteed to be at least as specific as this.
47
+ // Not changing the key will make React not rerender, so we don't change.
48
+ existingFeature._internal.key = key;
49
+ }
43
50
  newFeatures.push(existingFeature);
44
51
  continue;
45
52
  }
46
- newFeatures.push(CfgFeature.make(f, allRawFeatures, key, parent, parentConfiguration, parentProduct, rootProduct));
53
+ newFeatures.push(CfgFeature.make(f, rawFeatures, key, parent, parentConfiguration, parentProduct, rootProduct));
47
54
  }
48
55
  return newFeatures;
49
56
  }
@@ -24,7 +24,10 @@ export declare class SyncGroupsHandler {
24
24
  clone(): SyncGroupsHandler;
25
25
  get verboseLogging(): boolean;
26
26
  set verboseLogging(v: boolean);
27
- /** Used to initially apply the sync state onto a new product so that it is "in sync". */
27
+ /**
28
+ * Used to initially apply the sync state onto a new product so that it is "in sync"
29
+ * and to reapply the sync state when an optional additional product is selected.
30
+ */
28
31
  init(product: _CfgProductInternal, productLoader: ProductLoader): Promise<void>;
29
32
  /**
30
33
  * Used when an Option is selected or deselected to apply all consequences of the sync groups.
@@ -123,7 +123,8 @@ import { SyncGroupsTransaction } from "./SyncGroupsTransaction.js";
123
123
  * C) The SyncState has an option code for this SyncGroup.
124
124
  * D) The option code in the SyncState for the SyncGroup is not the Option selected.
125
125
  * E) The Feature has an Option with the right option code.
126
- * F) The Feature has not previously been affected in this transaction.
126
+ * F) The Feature has not previously been affected in this transaction, unless it was affected
127
+ * at Feature initialization.
127
128
  *
128
129
  * ...for SelectMany (done for every Option):
129
130
  * C) The SyncState has a value (on or off) for this SyncGroup and option code.
@@ -274,7 +275,10 @@ export class SyncGroupsHandler {
274
275
  set verboseLogging(v) {
275
276
  this._syncState.verboseLogging = v;
276
277
  }
277
- /** Used to initially apply the sync state onto a new product so that it is "in sync". */
278
+ /**
279
+ * Used to initially apply the sync state onto a new product so that it is "in sync"
280
+ * and to reapply the sync state when an optional additional product is selected.
281
+ */
278
282
  init(product, productLoader) {
279
283
  return __awaiter(this, void 0, void 0, function* () {
280
284
  const transaction = yield this.newTransaction(product, productLoader, true);
@@ -13,6 +13,12 @@ import { SelectionType, } from "../productConfiguration/CfgFeature.js";
13
13
  import { ProductConfigurationBubbleMode, } from "../productConfiguration/CfgOption.js";
14
14
  import { SyncGroupsApplyMode } from "./SyncGroupsApplyMode.js";
15
15
  import { SyncGroupsPathHelper } from "./SyncGroupsPathHelper.js";
16
+ var AffectedLevel;
17
+ (function (AffectedLevel) {
18
+ AffectedLevel[AffectedLevel["None"] = 0] = "None";
19
+ AffectedLevel[AffectedLevel["Initialized"] = 1] = "Initialized";
20
+ AffectedLevel[AffectedLevel["Set"] = 2] = "Set";
21
+ })(AffectedLevel || (AffectedLevel = {}));
16
22
  /**
17
23
  * The Transaction is a transient object used to track all the changes made to the supplied
18
24
  * SyncGroupState as a result of a (normally) single initial event, like when opening a product
@@ -39,7 +45,7 @@ import { SyncGroupsPathHelper } from "./SyncGroupsPathHelper.js";
39
45
  export class SyncGroupsTransaction {
40
46
  constructor(syncState, updateMode, productLoader, original, target, initial) {
41
47
  this._closed = false;
42
- this.affectedSelectOneFeatures = new Set();
48
+ this.affectedSelectOneFeatures = new Map();
43
49
  this.affectedSelectManyOptions = new Set();
44
50
  this.affectedSelectOneSyncGroups = new Set();
45
51
  this.affectedSelectManySyncGroupsAndOptions = new Map();
@@ -104,7 +110,7 @@ export class SyncGroupsTransaction {
104
110
  change = true;
105
111
  switch (feature.selectionType) {
106
112
  case SelectionType.SelectOne:
107
- this.affectedSelectOneFeatures.add(feature);
113
+ this.affectedSelectOneFeatures.set(feature, AffectedLevel.Set);
108
114
  break;
109
115
  case SelectionType.SelectMany:
110
116
  this.affectedSelectManyOptions.add(option);
@@ -266,14 +272,19 @@ export class SyncGroupsTransaction {
266
272
  return __awaiter(this, void 0, void 0, function* () {
267
273
  const feature = featureWithInitial.target;
268
274
  const featureDidJustComeIntoScope = featureWithInitial.initial === undefined;
269
- if (this.affectedSelectOneFeatures.has(feature)) {
270
- // This feature has already changed selection once for this transaction. We expect
271
- // this to happen very rarely, as the algorithm should settle the selection tree
272
- // further and further out. Nevertheless, this safeguard is needed to avoid infinite
273
- // looping if for example the server would return the same data over and over.
275
+ const hasBeenAffectedLevel = this.affectedSelectOneFeatures.get(feature);
276
+ const syncGroupHasBeenUpdated = this.affectedSelectOneSyncGroups.has(syncCode);
277
+ if (hasBeenAffectedLevel === AffectedLevel.Set ||
278
+ (!syncGroupHasBeenUpdated && hasBeenAffectedLevel === AffectedLevel.Initialized)) {
279
+ // A feature can change value for two reasons:
280
+ // 1. The feature did just come into scope and loads from the sync group to set its "defaults"
281
+ // 2. The sync group has updated with a new value
282
+ // The rule is that in one sync group transaction the feature value is allowed to be updated
283
+ // once or twice. Once for initialization and once for new sync group value. But they have to
284
+ // happen in the right order.
274
285
  return "recurseDown";
275
286
  }
276
- if (!featureDidJustComeIntoScope && !this.affectedSelectOneSyncGroups.has(syncCode)) {
287
+ if (!featureDidJustComeIntoScope && !syncGroupHasBeenUpdated) {
277
288
  return "recurseDown";
278
289
  }
279
290
  const currentSyncGroupValue = this.syncState.getForSelectOne(syncCode);
@@ -295,7 +306,7 @@ export class SyncGroupsTransaction {
295
306
  // Do not recurse further as we will change the state and so what is selected now won't be
296
307
  // selected then.
297
308
  yield feature.selectOption(optionToSelect._internal, true, ProductConfigurationBubbleMode.ToRoot);
298
- this.affectedSelectOneFeatures.add(feature);
309
+ this.affectedSelectOneFeatures.set(feature, syncGroupHasBeenUpdated ? AffectedLevel.Set : AffectedLevel.Initialized);
299
310
  productsToValidate.add(feature.parentProduct);
300
311
  return "stop";
301
312
  });
@@ -435,7 +446,7 @@ export class SyncGroupsTransaction {
435
446
  return change;
436
447
  });
437
448
  }
438
- applySelectOneFeature(feature, activeSelectionForce, featureDidJustComeIntoScope) {
449
+ applySelectOneFeature(feature, userInitiated, featureDidJustComeIntoScope) {
439
450
  const selectionType = feature.selectionType;
440
451
  if (selectionType !== SelectionType.SelectOne) {
441
452
  throw new Error("can only be used for selectOne");
@@ -453,7 +464,7 @@ export class SyncGroupsTransaction {
453
464
  // Options with no default are never written
454
465
  return false;
455
466
  }
456
- if (activeSelectionForce) {
467
+ if (userInitiated) {
457
468
  // To make re-apply happen, even if it actually does not update the sync group
458
469
  this.affectedSelectOneSyncGroups.add(syncCode);
459
470
  }
@@ -464,7 +475,7 @@ export class SyncGroupsTransaction {
464
475
  // can not be set to the current sync group value, then it will set in the opposite
465
476
  // direction. Like if the sync group was empty. To avoid bouncing back and forth we will
466
477
  // need to enforce that a sync group can only be updated once per transaction
467
- if (!(activeSelectionForce ||
478
+ if (!(userInitiated ||
468
479
  currentSyncGroupOptionCode === undefined ||
469
480
  (featureDidJustComeIntoScope &&
470
481
  feature.options.every((o) => currentSyncGroupOptionCode !== o.code)))) {
@@ -486,7 +497,7 @@ export class SyncGroupsTransaction {
486
497
  }
487
498
  return change;
488
499
  }
489
- applySelectManyOption(option, activeSelectionForce) {
500
+ applySelectManyOption(option, userInitiated) {
490
501
  const feature = option.parent;
491
502
  if (feature.selectionType !== SelectionType.SelectMany) {
492
503
  throw new Error("can only be used for selectMany");
@@ -500,14 +511,14 @@ export class SyncGroupsTransaction {
500
511
  }
501
512
  const optionSelected = option.selected;
502
513
  const currentSyncGroupValue = this.syncState.getForSelectMany(syncCode, option.code);
503
- if (activeSelectionForce) {
514
+ if (userInitiated) {
504
515
  // To make re-apply happen, even if it actually does not update the sync group
505
516
  this.addSyncGroupAffectedForSelectMany(syncCode, option);
506
517
  }
507
- // We only initialize if the option is selected or we force.
508
- // activeSelectionForce = active selection by the user.
509
- if (!(currentSyncGroupValue === undefined && optionSelected) &&
510
- !(activeSelectionForce && currentSyncGroupValue !== optionSelected)) {
518
+ // We only initialize if the option has not been initiated or if it is userInitiated.
519
+ // userInitiated = active selection by the user.
520
+ if (currentSyncGroupValue !== undefined &&
521
+ !(userInitiated && currentSyncGroupValue !== optionSelected)) {
511
522
  return false;
512
523
  }
513
524
  this.addSyncGroupAffectedForSelectMany(syncCode, option);
@@ -529,7 +540,9 @@ export class SyncGroupsTransaction {
529
540
  }
530
541
  function getAdditionalProducts(product) {
531
542
  const initial = product.initial;
532
- return product.target.additionalProducts.map((childTarget) => {
543
+ return product.target.additionalProducts
544
+ .filter((p) => p.selected)
545
+ .map((childTarget) => {
533
546
  const refKey = childTarget.refKey;
534
547
  const childInitial = initial === null || initial === void 0 ? void 0 : initial.additionalProducts.find((p) => refKey === p.refKey);
535
548
  return {
@@ -558,8 +571,8 @@ function getOptions(feature) {
558
571
  }
559
572
  function pairFeatures(targets, initials) {
560
573
  return targets.map((childTarget) => {
561
- const key = childTarget.key;
562
- const childInitial = initials === null || initials === void 0 ? void 0 : initials.find((f) => key === f.key);
574
+ const code = childTarget.code;
575
+ const childInitial = initials === null || initials === void 0 ? void 0 : initials.find((f) => code === f.code);
563
576
  return {
564
577
  target: childTarget._internal,
565
578
  initial: childInitial === null || childInitial === void 0 ? void 0 : childInitial._internal,
@@ -254,6 +254,7 @@ export const cfgProductTest = (testFunc, prepFunc) => __awaiter(void 0, void 0,
254
254
  uuid: "",
255
255
  validated: true,
256
256
  productData,
257
+ features: []
257
258
  };
258
259
  collect.pushNotification(`Validate ${params.enterprise} ${params.prdCat} ${params.prdCatVersion} ${params.priceList} ${params.vendor} ${params.partNumber}`);
259
260
  return validateResponse;
@@ -173,6 +173,7 @@ export const cachedProductLoaderTest = () => __awaiter(void 0, void 0, void 0, f
173
173
  uuid: "",
174
174
  validated: true,
175
175
  productData,
176
+ features: []
176
177
  };
177
178
  return validateResponse;
178
179
  }),
@@ -173,6 +173,7 @@ export const cachedProductLoaderTest = () => __awaiter(void 0, void 0, void 0, f
173
173
  uuid: "",
174
174
  validated: true,
175
175
  productData,
176
+ features: [],
176
177
  };
177
178
  validateCounter.count++;
178
179
  return validateResponse;
@@ -176,6 +176,7 @@ export const cfgProductPriceTest = (testFunc, featureOption) => __awaiter(void 0
176
176
  uuid: "",
177
177
  validated: true,
178
178
  productData,
179
+ features: [],
179
180
  };
180
181
  return validateResponse;
181
182
  }),
@@ -99,6 +99,7 @@ export const getUpchargeProduct = (testFunc) => __awaiter(void 0, void 0, void 0
99
99
  uuid: "",
100
100
  validated: true,
101
101
  productData,
102
+ features: [],
102
103
  };
103
104
  return validateResponse;
104
105
  }),
@@ -9,6 +9,11 @@ export declare type CfgProductData = Omit<Omit<ProductData, "models">, "partsDat
9
9
  selOptions: SelectedOption[];
10
10
  };
11
11
  };
12
+ /**
13
+ * Deep compare. Uses the JSON.stringify method for comparison
14
+ * @returns true if equal. Key and array order is respected.
15
+ */
16
+ export declare function compareCfgProductData(pd1: CfgProductData, pd2: CfgProductData): boolean;
12
17
  export declare type CfgProductResponse = Omit<ProductResponse, "productData"> & {
13
18
  productData: CfgProductData;
14
19
  };
@@ -16,6 +16,13 @@ export const makeSelOptionsKey = (options) => options.reduce((p, option) => {
16
16
  p += "_}";
17
17
  return p;
18
18
  }, "");
19
+ /**
20
+ * Deep compare. Uses the JSON.stringify method for comparison
21
+ * @returns true if equal. Key and array order is respected.
22
+ */
23
+ export function compareCfgProductData(pd1, pd2) {
24
+ return JSON.stringify(pd1) === JSON.stringify(pd2);
25
+ }
19
26
  export function isModel(arg) {
20
27
  return typeof arg === "object" && arg !== null && "cid" in arg && "uri" in arg;
21
28
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@configura/web-api",
3
- "version": "1.6.1",
3
+ "version": "1.7.0",
4
4
  "license": "Apache-2.0",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -23,7 +23,7 @@
23
23
  "access": "public"
24
24
  },
25
25
  "dependencies": {
26
- "@configura/web-utilities": "1.6.1"
26
+ "@configura/web-utilities": "1.7.0"
27
27
  },
28
- "gitHead": "bffef8762f32d8629a89837cafc20d34702409a3"
28
+ "gitHead": "f6e1e62d91488535ef5b65eb93bbd69bdf1aabe7"
29
29
  }