@configura/web-api 2.0.0-alpha.18 → 2.0.0-alpha.20

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 (30) hide show
  1. package/dist/CatalogueAPI.d.ts +47 -17
  2. package/dist/CatalogueAPI.js +61 -6
  3. package/dist/CfgProduct.d.ts +17 -6
  4. package/dist/CfgProduct.js +55 -14
  5. package/dist/CfgReferencePathHelper.d.ts +3 -3
  6. package/dist/productConfiguration/CfgFeature.d.ts +6 -4
  7. package/dist/productConfiguration/CfgFeature.js +13 -6
  8. package/dist/productConfiguration/CfgOption.d.ts +3 -3
  9. package/dist/productConfiguration/CfgOption.js +5 -5
  10. package/dist/productConfiguration/CfgProductConfiguration.d.ts +16 -7
  11. package/dist/productConfiguration/CfgProductConfiguration.js +41 -14
  12. package/dist/productConfiguration/filters.d.ts +2 -2
  13. package/dist/productConfiguration/productParamsGenerator.d.ts +3 -3
  14. package/dist/productConfiguration/utilitiesProductConfiguration.d.ts +1 -1
  15. package/dist/productConfiguration/utilitiesProductConfiguration.js +11 -4
  16. package/dist/productLoader.d.ts +3 -3
  17. package/dist/syncGroups/SyncGroupsHandler.d.ts +4 -1
  18. package/dist/syncGroups/SyncGroupsHandler.js +6 -2
  19. package/dist/syncGroups/SyncGroupsTransaction.js +34 -21
  20. package/dist/tasks/TaskHandler.d.ts +2 -2
  21. package/dist/tests/testData/dummyProductForTest.d.ts +2 -2
  22. package/dist/tests/testData/testDataAdditionalProductInAdditionalProductInProductForTest.js +1 -0
  23. package/dist/tests/testData/testDataCachedGetProduct.js +1 -0
  24. package/dist/tests/testData/testDataCachedPostValidate.js +1 -0
  25. package/dist/tests/testData/testDataProductAggregatedPrice.js +1 -0
  26. package/dist/tests/testData/testDataUpcharge.js +1 -0
  27. package/dist/utilitiesCatalogueData.d.ts +14 -9
  28. package/dist/utilitiesCatalogueData.js +7 -0
  29. package/dist/utilitiesCataloguePermission.d.ts +4 -4
  30. package/package.json +3 -3
@@ -17,15 +17,15 @@ export declare type StretchMap = Map<string, {
17
17
  * modified. CfgProductConfiguration is the class that should be used and interacted with.
18
18
  */
19
19
  export declare class _CfgProductConfigurationInternal {
20
- readonly allRawFeatures: DtoFeature[];
21
20
  readonly parentProduct: _CfgProductInternal;
22
21
  readonly rootProduct: _CfgProductInternal;
23
22
  private readonly _initialRootFeatureRefs;
24
- static _makeUninitialized(rootFeatureRefs: DtoFeatureRef[], allRawFeatures: DtoFeature[], // Flat packed. All the features that can appear anyplace in the selection tree.
23
+ static _makeUninitialized(rootFeatureRefs: DtoFeatureRef[], rawFeatures: DtoFeature[], // Flat packed. All the features that can currently appear anyplace in the selection tree.
25
24
  parentProduct: _CfgProductInternal, rootProduct: _CfgProductInternal): _CfgProductConfigurationInternal;
26
25
  private constructor();
27
26
  readonly key = "~";
28
27
  private _rootFeatureRefs;
28
+ readonly accumulatedRawFeatures: DtoFeature[];
29
29
  private _features;
30
30
  readonly stretchReferenceLengthsByMeasureParamCode: StretchMap;
31
31
  readonly changeObservable: Observable<ProductConfigurationChangeNotification>;
@@ -51,13 +51,22 @@ 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
+ /**
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: DtoFeature[], warnForDuplicates: boolean) => boolean;
54
59
  private _hasRootFeaturesChanged;
55
60
  /**
56
61
  * True if what root Features are used is not the same as at initial load.
57
62
  * This means that functional selection has happened.
58
63
  */
59
64
  get hasRootFeaturesChanged(): boolean;
60
- populateFeatures: (rootFeatureRefs: DtoFeatureRef[]) => void;
65
+ /**
66
+ * Populates _features based on the passed @param rootFeatureRefs .
67
+ * @return true if a change happened.
68
+ */
69
+ populateFeatures: (rootFeatureRefs: DtoFeatureRef[]) => boolean;
61
70
  setStretchReferenceLength: (measureParamCode: string, referenceLength: number | undefined, unit: LengthUnit) => Promise<boolean>;
62
71
  }
63
72
  export declare class CfgProductConfiguration {
@@ -67,7 +76,7 @@ export declare class CfgProductConfiguration {
67
76
  * CfgProductConfiguration, but is not properly initialized until the initDone-callback
68
77
  * has been called.
69
78
  */
70
- static make(initSuccess: (c: CfgProductConfiguration) => void, initFail: (error: Error) => void, rootFeatureRefs: DtoFeatureRef[], allRawFeatures: DtoFeature[], // Flat packed. All the features that can appear anyplace in the selection tree.
79
+ static make(initSuccess: (c: CfgProductConfiguration) => void, initFail: (error: Error) => void, rootFeatureRefs: DtoFeatureRef[], rawFeatures: DtoFeature[], // Flat packed. All the features that can currently appear anyplace in the selection tree.
71
80
  apiSelection: DtoSelectedOption[], parentProduct: _CfgProductInternal, rootProduct: _CfgProductInternal): CfgProductConfiguration;
72
81
  /**
73
82
  * Makes an object wrapping the passed object. This is not a clone method, it is a method to
@@ -87,11 +96,11 @@ export declare class CfgProductConfiguration {
87
96
  get rootProduct(): CfgProduct;
88
97
  get key(): string;
89
98
  /**
90
- * Every (unprocessed) feature which might be used in this product. This is constant for a
91
- * product load. What features are actually used is controlled by rootFeatureRefs and what
99
+ * Every (unprocessed) feature that is currently loaded for this product. This can be extended
100
+ * by validation calls. What features are actually used is controlled by rootFeatureRefs and what
92
101
  * options are selected.
93
102
  */
94
- get allRawFeatures(): DtoFeature[];
103
+ get rawFeatures(): DtoFeature[];
95
104
  /** What features are used in the root of this. This can change with new validate calls. */
96
105
  get rootFeatureRefs(): DtoFeatureRef[];
97
106
  /** The root features at the root of the selection tree. */
@@ -19,14 +19,13 @@ import { syncCfgFeatures } from "./utilitiesProductConfiguration.js";
19
19
  * modified. CfgProductConfiguration is the class that should be used and interacted with.
20
20
  */
21
21
  export class _CfgProductConfigurationInternal {
22
- constructor(allRawFeatures, // Flat packed. All the features that can appear anyplace in the selection tree.
23
- parentProduct, rootProduct, _initialRootFeatureRefs) {
24
- this.allRawFeatures = allRawFeatures;
22
+ constructor(rawFeatures, parentProduct, rootProduct, _initialRootFeatureRefs) {
25
23
  this.parentProduct = parentProduct;
26
24
  this.rootProduct = rootProduct;
27
25
  this._initialRootFeatureRefs = _initialRootFeatureRefs;
28
26
  this.key = "~";
29
27
  this._rootFeatureRefs = [];
28
+ this.accumulatedRawFeatures = []; // Flat packed. May be extended in validate calls.
30
29
  this._features = [];
31
30
  this.changeObservable = new Observable();
32
31
  this._notifyAllOfChange = (bubbleMode, committed) => __awaiter(this, void 0, void 0, function* () {
@@ -114,11 +113,39 @@ export class _CfgProductConfigurationInternal {
114
113
  agg.push(...feature._internal._getFeaturesWithCode(code));
115
114
  return agg;
116
115
  }, []);
116
+ /**
117
+ * Extends the list of loaded potentially used features. Will warn for but ignore duplicates.
118
+ * Returns true if a change happened.
119
+ */
120
+ this.addRawFeatures = (rawFeatures, warnForDuplicates) => {
121
+ let change = false;
122
+ const accumulatedRawFeatures = this.accumulatedRawFeatures;
123
+ for (const rawFeature of rawFeatures) {
124
+ const code = rawFeature.code;
125
+ if (accumulatedRawFeatures.find((f) => code === f.code)) {
126
+ if (warnForDuplicates) {
127
+ console.warn(`Feature ${code} was already loaded. Will ignore it.`);
128
+ }
129
+ continue;
130
+ }
131
+ accumulatedRawFeatures.push(rawFeature);
132
+ change = true;
133
+ }
134
+ return change;
135
+ };
117
136
  this._hasRootFeaturesChanged = false;
137
+ /**
138
+ * Populates _features based on the passed @param rootFeatureRefs .
139
+ * @return true if a change happened.
140
+ */
118
141
  this.populateFeatures = (rootFeatureRefs) => {
142
+ if (compareArrays(this._rootFeatureRefs, rootFeatureRefs, (l, r) => l.code === r.code, true)) {
143
+ return false;
144
+ }
119
145
  this._rootFeatureRefs = rootFeatureRefs;
120
146
  this._hasRootFeaturesChanged = !compareArrays(this._initialRootFeatureRefs, rootFeatureRefs, (l, r) => l.code === r.code);
121
- this._features = syncCfgFeatures(rootFeatureRefs, this._features, this.allRawFeatures, this, this, this.parentProduct, this.rootProduct);
147
+ this._features = syncCfgFeatures(rootFeatureRefs, this._features, this.accumulatedRawFeatures, this, this, this.parentProduct, this.rootProduct);
148
+ return true;
122
149
  };
123
150
  this.setStretchReferenceLength = (measureParamCode, referenceLength, unit) => __awaiter(this, void 0, void 0, function* () {
124
151
  if (measureParamCode === "") {
@@ -163,6 +190,8 @@ export class _CfgProductConfigurationInternal {
163
190
  console.log(`Use "window['${dbgName}']" or window.conf to access conf.`);
164
191
  }
165
192
  /* eslint-enable */
193
+ this.addRawFeatures(rawFeatures, true);
194
+ this.populateFeatures(_initialRootFeatureRefs);
166
195
  /**
167
196
  * Although the measurement-datas are also passed in validate-calls they are only used at
168
197
  * initial product creation. The data is assumed to always be the same for a product, so
@@ -199,11 +228,9 @@ export class _CfgProductConfigurationInternal {
199
228
  }
200
229
  this.stretchReferenceLengthsByMeasureParamCode = stretchReferenceLengthsByMeasureParamCode;
201
230
  }
202
- static _makeUninitialized(rootFeatureRefs, allRawFeatures, // Flat packed. All the features that can appear anyplace in the selection tree.
231
+ static _makeUninitialized(rootFeatureRefs, rawFeatures, // Flat packed. All the features that can currently appear anyplace in the selection tree.
203
232
  parentProduct, rootProduct) {
204
- const configuration = new this(allRawFeatures, parentProduct, rootProduct, rootFeatureRefs);
205
- configuration.populateFeatures(rootFeatureRefs);
206
- return configuration;
233
+ return new this(rawFeatures, parentProduct, rootProduct, rootFeatureRefs);
207
234
  }
208
235
  get rootFeatureRefs() {
209
236
  return this._rootFeatureRefs;
@@ -265,9 +292,9 @@ export class CfgProductConfiguration {
265
292
  * CfgProductConfiguration, but is not properly initialized until the initDone-callback
266
293
  * has been called.
267
294
  */
268
- static make(initSuccess, initFail, rootFeatureRefs, allRawFeatures, // Flat packed. All the features that can appear anyplace in the selection tree.
295
+ static make(initSuccess, initFail, rootFeatureRefs, rawFeatures, // Flat packed. All the features that can currently appear anyplace in the selection tree.
269
296
  apiSelection, parentProduct, rootProduct) {
270
- const internal = _CfgProductConfigurationInternal._makeUninitialized(rootFeatureRefs, allRawFeatures, parentProduct, rootProduct);
297
+ const internal = _CfgProductConfigurationInternal._makeUninitialized(rootFeatureRefs, rawFeatures, parentProduct, rootProduct);
271
298
  const external = new this(internal);
272
299
  // Note the async-callback at the end of the following line. The call is async.
273
300
  internal
@@ -295,12 +322,12 @@ export class CfgProductConfiguration {
295
322
  return this._internal.key;
296
323
  }
297
324
  /**
298
- * Every (unprocessed) feature which might be used in this product. This is constant for a
299
- * product load. What features are actually used is controlled by rootFeatureRefs and what
325
+ * Every (unprocessed) feature that is currently loaded for this product. This can be extended
326
+ * by validation calls. What features are actually used is controlled by rootFeatureRefs and what
300
327
  * options are selected.
301
328
  */
302
- get allRawFeatures() {
303
- return this._internal.allRawFeatures;
329
+ get rawFeatures() {
330
+ return this._internal.accumulatedRawFeatures;
304
331
  }
305
332
  /** What features are used in the root of this. This can change with new validate calls. */
306
333
  get rootFeatureRefs() {
@@ -1,6 +1,6 @@
1
1
  import { Filters, Matches } from "@configura/web-utilities";
2
- import { DtoCatalogueParamsWithoutCid, DtoLevel, DtoProductRef } from "../CatalogueAPI.js";
3
- export declare function applyCatalogueFilters<T extends DtoCatalogueParamsWithoutCid>(filters: Filters<DtoCatalogueParamsWithoutCid>, catalogues: T[]): [Matches<DtoCatalogueParamsWithoutCid>, T[]];
2
+ import { DtoCatalogueParams, DtoLevel, DtoProductRef } from "../CatalogueAPI.js";
3
+ export declare function applyCatalogueFilters<T extends DtoCatalogueParams>(filters: Filters<DtoCatalogueParams>, catalogues: T[]): [Matches<DtoCatalogueParams>, T[]];
4
4
  export interface ProductRefParams {
5
5
  partNr: string;
6
6
  }
@@ -1,16 +1,16 @@
1
1
  import { Filters } from "@configura/web-utilities";
2
- import { CatalogueAPI, DtoApplicationAreasResponse, DtoCatalogueParams, DtoCatalogueParamsWithLang } from "../CatalogueAPI.js";
2
+ import { CatalogueAPI, DtoApplicationAreasResponse, DtoCatalogueParamsWithCid, DtoCatalogueParamsWithCidAndLang } from "../CatalogueAPI.js";
3
3
  import { CfgProduct, CfgProductSettings } from "../CfgProduct.js";
4
4
  import { ProductRefParams } from "./filters.js";
5
5
  export interface GeneratedProductConfiguration {
6
6
  applicationAreasResponse: DtoApplicationAreasResponse;
7
7
  catalogueCount: number;
8
8
  catalogueIndex: number;
9
- catalogueParams: DtoCatalogueParamsWithLang;
9
+ catalogueParams: DtoCatalogueParamsWithCidAndLang;
10
10
  getProductDuration: number;
11
11
  product: CfgProduct;
12
12
  productCount: number;
13
13
  productIndex: number;
14
14
  }
15
- export declare function generateProductConfigurations(api: CatalogueAPI, lang: string, catalogues: DtoCatalogueParams[], filters: Filters<ProductRefParams>, settings?: Partial<CfgProductSettings>): AsyncIterableIterator<GeneratedProductConfiguration | Error>;
15
+ export declare function generateProductConfigurations(api: CatalogueAPI, lang: string, catalogues: DtoCatalogueParamsWithCid[], filters: Filters<ProductRefParams>, settings?: Partial<CfgProductSettings>): AsyncIterableIterator<GeneratedProductConfiguration | Error>;
16
16
  //# sourceMappingURL=productParamsGenerator.d.ts.map
@@ -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: DtoFeatureRef[], currentFeatures: CfgFeature[], allRawFeatures: DtoFeature[], parent: _CfgProductConfigurationInternal | _CfgOptionInternal, parentConfiguration: _CfgProductConfigurationInternal, parentProduct: _CfgProductInternal, rootProduct: _CfgProductInternal): CfgFeature[];
11
+ export declare function syncCfgFeatures(newFeatureRefs: DtoFeatureRef[], currentFeatures: CfgFeature[], rawFeatures: DtoFeature[], 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
  }
@@ -1,7 +1,7 @@
1
- import { DtoProductParamsWithLang, DtoValidateRequest } from "./CatalogueAPI.js";
1
+ import { DtoProductParamsWithCidAndLang, DtoValidateRequest } from "./CatalogueAPI.js";
2
2
  import { CfgProductResponse, CfgValidateResponse } from "./utilitiesCatalogueData.js";
3
- export declare type GetProduct = (params: DtoProductParamsWithLang) => Promise<CfgProductResponse>;
4
- export declare type PostValidate = (params: DtoProductParamsWithLang, body: DtoValidateRequest) => Promise<CfgValidateResponse>;
3
+ export declare type GetProduct = (params: DtoProductParamsWithCidAndLang) => Promise<CfgProductResponse>;
4
+ export declare type PostValidate = (params: DtoProductParamsWithCidAndLang, body: DtoValidateRequest) => Promise<CfgValidateResponse>;
5
5
  export declare type ProductLoader = {
6
6
  getProduct: GetProduct;
7
7
  postValidate: PostValidate;
@@ -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.
@@ -275,7 +276,10 @@ export class SyncGroupsHandler {
275
276
  set verboseLogging(v) {
276
277
  this._syncState.verboseLogging = v;
277
278
  }
278
- /** Used to initially apply the sync state onto a new product so that it is "in sync". */
279
+ /**
280
+ * Used to initially apply the sync state onto a new product so that it is "in sync"
281
+ * and to reapply the sync state when an optional additional product is selected.
282
+ */
279
283
  init(product, productLoader) {
280
284
  return __awaiter(this, void 0, void 0, function* () {
281
285
  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,
@@ -1,5 +1,5 @@
1
1
  import { Observable, SingleArgCallback } from "@configura/web-utilities";
2
- import { CatalogueAPI, DtoAdditionalProductConfiguration, DtoExportStatus, DtoExportStatusStatus, DtoProductParamsWithLang, DtoRenderStatus, DtoRenderStatusStatus, DtoTargetCameraArgs } from "../CatalogueAPI.js";
2
+ import { CatalogueAPI, DtoAdditionalProductConfiguration, DtoExportStatus, DtoExportStatusStatus, DtoProductParamsWithCidAndLang, DtoRenderStatus, DtoRenderStatusStatus, DtoTargetCameraArgs } from "../CatalogueAPI.js";
3
3
  import { CfgProduct } from "../CfgProduct.js";
4
4
  import { RenderOrExportFormat } from "./formats.js";
5
5
  export declare type TasksChangeNotification = {
@@ -51,7 +51,7 @@ export declare abstract class Task<F extends RenderOrExportFormat> {
51
51
  protected constructor(taskHandler: _TaskHandlerInternal, format: F, product: CfgProduct, getPreviewUrl: (() => Promise<string>) | undefined);
52
52
  protected abstract postInit(): Promise<DtoRenderStatus | DtoExportStatus>;
53
53
  protected abstract pollStatus(): Promise<DtoRenderStatus | DtoExportStatus>;
54
- protected _productParams: DtoProductParamsWithLang;
54
+ protected _productParams: DtoProductParamsWithCidAndLang;
55
55
  protected _apiSelection: DtoAdditionalProductConfiguration;
56
56
  private _status;
57
57
  private _uuid;
@@ -1,5 +1,5 @@
1
- import { DtoCatalogueParamsWithLang } from "../../CatalogueAPI.js";
1
+ import { DtoCatalogueParamsWithCidAndLang } from "../../CatalogueAPI.js";
2
2
  import { CfgProduct } from "../../CfgProduct.js";
3
- export declare const dummyCatId: DtoCatalogueParamsWithLang;
3
+ export declare const dummyCatId: DtoCatalogueParamsWithCidAndLang;
4
4
  export declare const getDummyCfgProduct: () => Promise<CfgProduct>;
5
5
  //# sourceMappingURL=dummyProductForTest.d.ts.map
@@ -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
  }),
@@ -1,12 +1,12 @@
1
- import { DtoAdditionalProductRef, DtoCatalogueParams, DtoCatalogueParamsWithLang, DtoCatalogueParamsWithoutCid, DtoMeasureParam, DtoModel, DtoOrientation, DtoPartsData, DtoPrices, DtoProductData, DtoProductParams, DtoProductParamsWithLang, DtoProductResponse, DtoSelectedOption, DtoTransform, DtoValidateResponse, DtoVector } from "./CatalogueAPI.js";
1
+ import { DtoAdditionalProductRef, DtoCatalogueParams, DtoCatalogueParamsWithCid, DtoCatalogueParamsWithCidAndLang, DtoMeasureParam, DtoModel, DtoOrientation, DtoPartsData, DtoPrices, DtoProductData, DtoProductParamsWithCid, DtoProductParamsWithCidAndLang, DtoProductResponse, DtoSelectedOption, DtoTransform, DtoValidateResponse, DtoVector } from "./CatalogueAPI.js";
2
2
  /**
3
3
  * Makes a string from the params which can be used as a key in for example React. Language is not respected.
4
4
  */
5
- export declare const makeCatalogueKey: (cat: DtoCatalogueParams) => string;
5
+ export declare const makeCatalogueKey: (cat: DtoCatalogueParamsWithCid) => string;
6
6
  /**
7
7
  * Makes a string from the params which can be used as a key in for example React. Language is not respected.
8
8
  */
9
- export declare const makeProductKey: (prod: DtoProductParams) => string;
9
+ export declare const makeProductKey: (prod: DtoProductParamsWithCid) => string;
10
10
  /**
11
11
  * Makes a string from selected options, can be used as a key for a selection.
12
12
  */
@@ -18,6 +18,11 @@ export declare type CfgProductData = Omit<Omit<DtoProductData, "models">, "parts
18
18
  selOptions: DtoSelectedOption[];
19
19
  };
20
20
  };
21
+ /**
22
+ * Deep compare. Uses the JSON.stringify method for comparison
23
+ * @returns true if equal. Key and array order is respected.
24
+ */
25
+ export declare function compareCfgProductData(pd1: CfgProductData, pd2: CfgProductData): boolean;
21
26
  export declare type CfgProductResponse = Omit<DtoProductResponse, "productData"> & {
22
27
  productData: CfgProductData;
23
28
  };
@@ -27,17 +32,17 @@ export declare type CfgValidateResponse = Omit<DtoValidateResponse, "productData
27
32
  };
28
33
  export declare function isModel(arg: unknown): arg is DtoModel;
29
34
  /** Replace empty strings with "-" for compatibility with the API. */
30
- export declare function correctDefaultsOnCatalogueParams<T extends DtoCatalogueParamsWithoutCid>(catId: T): T;
35
+ export declare function correctDefaultsOnCatalogueParams<T extends DtoCatalogueParams>(catId: T): T;
31
36
  export declare function recursivelyGetPriceCodeValue(priceCodes: string[], prices: DtoPrices | undefined): number | undefined;
32
37
  export declare function comparePricesObjects(prices1: DtoPrices | undefined, prices2: DtoPrices | undefined): boolean;
33
- export declare const decodeCatalogueParams: <T extends DtoCatalogueParamsWithoutCid>(params: T) => T;
34
- export declare const decodeProductParams: <T extends DtoProductParams>(params: T) => T;
35
- export declare function isSameCatalogueParams(left: DtoCatalogueParamsWithoutCid, right: DtoCatalogueParamsWithoutCid): boolean;
38
+ export declare const decodeCatalogueParams: <T extends DtoCatalogueParams>(params: T) => T;
39
+ export declare const decodeProductParams: <T extends DtoProductParamsWithCid>(params: T) => T;
40
+ export declare function isSameCatalogueParams(left: DtoCatalogueParams, right: DtoCatalogueParams): boolean;
36
41
  export declare function isSameVector(left: DtoVector, right: DtoVector): boolean;
37
42
  export declare function isSameOrientation(left: DtoOrientation, right: DtoOrientation): boolean;
38
43
  export declare function isSameAnchor(left: DtoMeasureParam, right: DtoMeasureParam): boolean;
39
44
  export declare function isSameTransform(left: DtoTransform, right: DtoTransform): boolean;
40
45
  export declare function isSameProductRef(left: DtoAdditionalProductRef, right: DtoAdditionalProductRef): boolean;
41
- export declare const isSameDtoCatalogueParamsWithLang: (left: DtoCatalogueParamsWithLang, right: DtoCatalogueParamsWithLang) => boolean;
42
- export declare const isSameDtoProductParamsWithLang: (left: DtoProductParamsWithLang, right: DtoProductParamsWithLang) => boolean;
46
+ export declare const isSameDtoCatalogueParamsWithLang: (left: DtoCatalogueParamsWithCidAndLang, right: DtoCatalogueParamsWithCidAndLang) => boolean;
47
+ export declare const isSameDtoProductParamsWithLang: (left: DtoProductParamsWithCidAndLang, right: DtoProductParamsWithCidAndLang) => boolean;
43
48
  //# sourceMappingURL=utilitiesCatalogueData.d.ts.map
@@ -25,6 +25,13 @@ export const makeSelOptionsKey = (options) => options.reduce((p, option) => {
25
25
  p += "_}";
26
26
  return p;
27
27
  }, "");
28
+ /**
29
+ * Deep compare. Uses the JSON.stringify method for comparison
30
+ * @returns true if equal. Key and array order is respected.
31
+ */
32
+ export function compareCfgProductData(pd1, pd2) {
33
+ return JSON.stringify(pd1) === JSON.stringify(pd2);
34
+ }
28
35
  export function isModel(arg) {
29
36
  return typeof arg === "object" && arg !== null && "cid" in arg && "uri" in arg;
30
37
  }
@@ -1,10 +1,10 @@
1
- import { DtoCatalogueParams, DtoCataloguePermission } from "./CatalogueAPI.js";
1
+ import { DtoCatalogueParamsWithCid, DtoCataloguePermission } from "./CatalogueAPI.js";
2
2
  interface CataloguePermissionByEnterpriseKey {
3
3
  [key: string]: DtoCataloguePermission[];
4
4
  }
5
5
  export declare const groupAndSortCataloguePermissions: (cataloguePermissions: DtoCataloguePermission[]) => CataloguePermissionByEnterpriseKey;
6
6
  export declare const isParamSet: (param: string | undefined) => boolean;
7
- export declare const createCataloguePermissionsFilter: (catParams: Partial<DtoCatalogueParams>) => (perm: DtoCataloguePermission) => boolean;
7
+ export declare const createCataloguePermissionsFilter: (catParams: Partial<DtoCatalogueParamsWithCid>) => (perm: DtoCataloguePermission) => boolean;
8
8
  /**
9
9
  * Sometimes you will want to use the latest available prdCatVersion. This method will
10
10
  * find the highest prdCatVersion version in the cataloguePermissions. If the versions are
@@ -25,7 +25,7 @@ export declare const getPrdCatVersionFromPermissions: (cataloguePermissions: Dto
25
25
  * @param auth
26
26
  * @param params
27
27
  */
28
- export declare const getPrdCatVersionOrLatestFromPermissions: (params: DtoCatalogueParams, cataloguePermissions: DtoCataloguePermission[]) => string;
28
+ export declare const getPrdCatVersionOrLatestFromPermissions: (params: DtoCatalogueParamsWithCid, cataloguePermissions: DtoCataloguePermission[]) => string;
29
29
  /**
30
30
  * Sometimes you will want a missing prdCatVersion to represent "Get the current highest version".
31
31
  * This method will, if the prdCatVersion is not set, fetch the highest available from the
@@ -34,6 +34,6 @@ export declare const getPrdCatVersionOrLatestFromPermissions: (params: DtoCatalo
34
34
  * @param auth
35
35
  * @param params
36
36
  */
37
- export declare const fillMissingPrdCatVersionFromPermissions: <T extends DtoCatalogueParams>(params: T, cataloguePermissions: DtoCataloguePermission[]) => T;
37
+ export declare const fillMissingPrdCatVersionFromPermissions: <T extends DtoCatalogueParamsWithCid>(params: T, cataloguePermissions: DtoCataloguePermission[]) => T;
38
38
  export {};
39
39
  //# sourceMappingURL=utilitiesCataloguePermission.d.ts.map