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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/.eslintrc.json +1 -14
  2. package/dist/CatalogueAPI.d.ts +103 -32
  3. package/dist/CatalogueAPI.js +62 -6
  4. package/dist/CfgProduct.d.ts +90 -14
  5. package/dist/CfgProduct.js +266 -56
  6. package/dist/CfgReferencePathHelper.d.ts +3 -3
  7. package/dist/index.d.ts +1 -1
  8. package/dist/index.js +1 -1
  9. package/dist/io/CfgHistoryManager.d.ts +33 -1
  10. package/dist/io/CfgHistoryManager.js +68 -6
  11. package/dist/io/CfgHistoryToProdConfConnector.d.ts +11 -10
  12. package/dist/io/CfgHistoryToProdConfConnector.js +32 -38
  13. package/dist/io/CfgIOManager.d.ts +5 -0
  14. package/dist/io/CfgIOManager.js +20 -1
  15. package/dist/io/CfgIOProdConfConnector.d.ts +17 -18
  16. package/dist/io/CfgIOProdConfConnector.js +52 -58
  17. package/dist/io/CfgIOWarningSupplier.d.ts +4 -0
  18. package/dist/io/CfgIOWarningSupplier.js +1 -0
  19. package/dist/io/CfgObservableStateToProdConfConnector.d.ts +4 -4
  20. package/dist/io/CfgObservableStateToProdConfConnector.js +3 -3
  21. package/dist/io/CfgWindowMessageManager.js +4 -0
  22. package/dist/io/CfgWindowMessageToProdConfConnector.d.ts +4 -4
  23. package/dist/io/CfgWindowMessageToProdConfConnector.js +3 -3
  24. package/dist/productConfiguration/CfgFeature.d.ts +12 -7
  25. package/dist/productConfiguration/CfgFeature.js +33 -14
  26. package/dist/productConfiguration/CfgOption.d.ts +16 -10
  27. package/dist/productConfiguration/CfgOption.js +46 -18
  28. package/dist/productConfiguration/CfgProductConfiguration.d.ts +27 -16
  29. package/dist/productConfiguration/CfgProductConfiguration.js +53 -29
  30. package/dist/productConfiguration/filters.d.ts +6 -4
  31. package/dist/productConfiguration/filters.js +94 -23
  32. package/dist/productConfiguration/productParamsGenerator.d.ts +3 -3
  33. package/dist/productConfiguration/utilitiesProductConfiguration.d.ts +1 -1
  34. package/dist/productConfiguration/utilitiesProductConfiguration.js +11 -4
  35. package/dist/productLoader.d.ts +3 -3
  36. package/dist/productLoader.js +1 -1
  37. package/dist/syncGroups/SyncGroupsHandler.d.ts +9 -2
  38. package/dist/syncGroups/SyncGroupsHandler.js +15 -4
  39. package/dist/syncGroups/SyncGroupsState.d.ts +5 -1
  40. package/dist/syncGroups/SyncGroupsState.js +44 -2
  41. package/dist/syncGroups/SyncGroupsTransaction.js +34 -21
  42. package/dist/tasks/TaskHandler.d.ts +2 -2
  43. package/dist/tasks/TaskHandler.js +2 -1
  44. package/dist/tests/testData/dummyProductForTest.d.ts +2 -2
  45. package/dist/tests/testData/testDataAdditionalProductInAdditionalProductInProductForTest.js +14 -9
  46. package/dist/tests/testData/testDataCachedGetProduct.js +2 -0
  47. package/dist/tests/testData/testDataCachedPostValidate.js +2 -0
  48. package/dist/tests/testData/testDataProductAggregatedPrice.js +3 -1
  49. package/dist/tests/testData/testDataUpcharge.js +2 -0
  50. package/dist/utilitiesCatalogueData.d.ts +14 -9
  51. package/dist/utilitiesCatalogueData.js +7 -0
  52. package/dist/utilitiesCataloguePermission.d.ts +4 -4
  53. package/dist/utilitiesConfiguration.d.ts +29 -0
  54. package/dist/utilitiesConfiguration.js +200 -0
  55. package/dist/utilitiesNumericValues.js +13 -8
  56. package/package.json +3 -3
  57. package/dist/ConfigurationConverter.d.ts +0 -5
  58. package/dist/ConfigurationConverter.js +0 -72
@@ -9,8 +9,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
9
9
  };
10
10
  import { compareArrays, count, isEqualLength, isLengthUnit, Observable, toError, toLengthUnit, } from "@configura/web-utilities";
11
11
  import { CfgProduct } from "../CfgProduct.js";
12
- import { convertDtoConfFeaturesToSelOptions } from "../ConfigurationConverter.js";
13
- import { CfgFeature } from "./CfgFeature.js";
12
+ import { convertDtoFeatureConfsToSelOptions } from "../utilitiesConfiguration.js";
14
13
  import { ProductConfigurationBubbleMode } from "./CfgOption.js";
15
14
  import { syncCfgFeatures } from "./utilitiesProductConfiguration.js";
16
15
  /**
@@ -19,13 +18,13 @@ import { syncCfgFeatures } from "./utilitiesProductConfiguration.js";
19
18
  * modified. CfgProductConfiguration is the class that should be used and interacted with.
20
19
  */
21
20
  export class _CfgProductConfigurationInternal {
22
- constructor(allRawFeatures, // Flat packed. All the features that can appear anyplace in the selection tree.
23
- parentProduct, rootProduct) {
24
- this.allRawFeatures = allRawFeatures;
21
+ constructor(rawFeatures, parentProduct, rootProduct, _initialRootFeatureRefs) {
25
22
  this.parentProduct = parentProduct;
26
23
  this.rootProduct = rootProduct;
24
+ this._initialRootFeatureRefs = _initialRootFeatureRefs;
27
25
  this.key = "~";
28
26
  this._rootFeatureRefs = [];
27
+ this.accumulatedRawFeatures = []; // Flat packed. May be extended in validate calls.
29
28
  this._features = [];
30
29
  this.changeObservable = new Observable();
31
30
  this._notifyAllOfChange = (bubbleMode, committed) => __awaiter(this, void 0, void 0, function* () {
@@ -56,9 +55,11 @@ export class _CfgProductConfigurationInternal {
56
55
  yield this._notifyAllOfChange(bubbleMode, committed);
57
56
  });
58
57
  this.getDtoConf = (includeExtendedData) => this._features.map((f) => f._internal.getDtoConf(includeExtendedData));
58
+ this.getApiSelection = () => convertDtoFeatureConfsToSelOptions(this.getDtoConf(false), true);
59
59
  /**
60
60
  * When used internally the notifications are taken care off by the caller, but if set from
61
61
  * outside we want notifications to bubble all the way to the root.
62
+ * This method will not cause validation calls. Data is assumed to already be validated.
62
63
  */
63
64
  this.setApiSelection = (selectedOptions, bubbleToRoot) => __awaiter(this, void 0, void 0, function* () {
64
65
  const featuresLength = this._features.length;
@@ -111,9 +112,39 @@ export class _CfgProductConfigurationInternal {
111
112
  agg.push(...feature._internal._getFeaturesWithCode(code));
112
113
  return agg;
113
114
  }, []);
115
+ /**
116
+ * Extends the list of loaded potentially used features. Will warn for but ignore duplicates.
117
+ * Returns true if a change happened.
118
+ */
119
+ this.addRawFeatures = (rawFeatures, warnForDuplicates) => {
120
+ let change = false;
121
+ const accumulatedRawFeatures = this.accumulatedRawFeatures;
122
+ for (const rawFeature of rawFeatures) {
123
+ const code = rawFeature.code;
124
+ if (accumulatedRawFeatures.find((f) => code === f.code)) {
125
+ if (warnForDuplicates) {
126
+ console.warn(`Feature ${code} was already loaded. Will ignore it.`);
127
+ }
128
+ continue;
129
+ }
130
+ accumulatedRawFeatures.push(rawFeature);
131
+ change = true;
132
+ }
133
+ return change;
134
+ };
135
+ this._hasRootFeaturesChanged = false;
136
+ /**
137
+ * Populates _features based on the passed @param rootFeatureRefs .
138
+ * @return true if a change happened.
139
+ */
114
140
  this.populateFeatures = (rootFeatureRefs) => {
141
+ if (compareArrays(this._rootFeatureRefs, rootFeatureRefs, (l, r) => l.code === r.code, true)) {
142
+ return false;
143
+ }
115
144
  this._rootFeatureRefs = rootFeatureRefs;
116
- this._features = syncCfgFeatures(rootFeatureRefs, this._features, this.allRawFeatures, this, this, this.parentProduct, this.rootProduct);
145
+ this._hasRootFeaturesChanged = !compareArrays(this._initialRootFeatureRefs, rootFeatureRefs, (l, r) => l.code === r.code);
146
+ this._features = syncCfgFeatures(rootFeatureRefs, this._features, this.accumulatedRawFeatures, this, this, this.parentProduct, this.rootProduct);
147
+ return true;
117
148
  };
118
149
  this.setStretchReferenceLength = (measureParamCode, referenceLength, unit) => __awaiter(this, void 0, void 0, function* () {
119
150
  if (measureParamCode === "") {
@@ -158,6 +189,8 @@ export class _CfgProductConfigurationInternal {
158
189
  console.log(`Use "window['${dbgName}']" or window.conf to access conf.`);
159
190
  }
160
191
  /* eslint-enable */
192
+ this.addRawFeatures(rawFeatures, true);
193
+ this.populateFeatures(_initialRootFeatureRefs);
161
194
  /**
162
195
  * Although the measurement-datas are also passed in validate-calls they are only used at
163
196
  * initial product creation. The data is assumed to always be the same for a product, so
@@ -194,11 +227,9 @@ export class _CfgProductConfigurationInternal {
194
227
  }
195
228
  this.stretchReferenceLengthsByMeasureParamCode = stretchReferenceLengthsByMeasureParamCode;
196
229
  }
197
- static _makeUninitialized(rootFeatureRefs, allRawFeatures, // Flat packed. All the features that can appear anyplace in the selection tree.
230
+ static _makeUninitialized(rootFeatureRefs, rawFeatures, // Flat packed. All the features that can currently appear anyplace in the selection tree.
198
231
  parentProduct, rootProduct) {
199
- const configuration = new this(allRawFeatures, parentProduct, rootProduct);
200
- configuration.populateFeatures(rootFeatureRefs);
201
- return configuration;
232
+ return new this(rawFeatures, parentProduct, rootProduct, rootFeatureRefs);
202
233
  }
203
234
  get rootFeatureRefs() {
204
235
  return this._rootFeatureRefs;
@@ -207,13 +238,12 @@ export class _CfgProductConfigurationInternal {
207
238
  get features() {
208
239
  return this._features;
209
240
  }
210
- _freshRefDescendants() {
211
- const features = this._features;
212
- for (let i = 0; i < features.length; i++) {
213
- const featureInternal = features[i]._internal;
214
- featureInternal._freshRefDescendants();
215
- features[i] = CfgFeature._makeNewRefFrom(featureInternal);
216
- }
241
+ /**
242
+ * True if what root Features are used is not the same as at initial load.
243
+ * This means that functional selection has happened.
244
+ */
245
+ get hasRootFeaturesChanged() {
246
+ return this._hasRootFeaturesChanged;
217
247
  }
218
248
  }
219
249
  export class CfgProductConfiguration {
@@ -233,12 +263,6 @@ export class CfgProductConfiguration {
233
263
  */
234
264
  this.tryMatchSelection = (other, descriptionMatch = false // Match on case insensitive description, not code
235
265
  ) => __awaiter(this, void 0, void 0, function* () { return yield this._internal.tryMatchSelection(other._internal, descriptionMatch, true); });
236
- this.getApiSelection = () => convertDtoConfFeaturesToSelOptions(this._internal.getDtoConf(false), true);
237
- /**
238
- * This method does not propagate its selections.
239
- * This method will not cause validation calls. Data is assumed to already be validated.
240
- */
241
- this.setApiSelection = (selectedOptions) => __awaiter(this, void 0, void 0, function* () { return yield this._internal.setApiSelection(selectedOptions, true); });
242
266
  /**
243
267
  * Set how stretched a certain measure should be measureParamCode is the measure to be
244
268
  * stretched referenceLength is a value relative to the initial length of the measure. If the
@@ -259,9 +283,9 @@ export class CfgProductConfiguration {
259
283
  * CfgProductConfiguration, but is not properly initialized until the initDone-callback
260
284
  * has been called.
261
285
  */
262
- static make(initSuccess, initFail, rootFeatureRefs, allRawFeatures, // Flat packed. All the features that can appear anyplace in the selection tree.
286
+ static make(initSuccess, initFail, rootFeatureRefs, rawFeatures, // Flat packed. All the features that can currently appear anyplace in the selection tree.
263
287
  apiSelection, parentProduct, rootProduct) {
264
- const internal = _CfgProductConfigurationInternal._makeUninitialized(rootFeatureRefs, allRawFeatures, parentProduct, rootProduct);
288
+ const internal = _CfgProductConfigurationInternal._makeUninitialized(rootFeatureRefs, rawFeatures, parentProduct, rootProduct);
265
289
  const external = new this(internal);
266
290
  // Note the async-callback at the end of the following line. The call is async.
267
291
  internal
@@ -289,12 +313,12 @@ export class CfgProductConfiguration {
289
313
  return this._internal.key;
290
314
  }
291
315
  /**
292
- * Every (unprocessed) feature which might be used in this product. This is constant for a
293
- * product load. What features are actually used is controlled by rootFeatureRefs and what
316
+ * Every (unprocessed) feature that is currently loaded for this product. This can be extended
317
+ * by validation calls. What features are actually used is controlled by rootFeatureRefs and what
294
318
  * options are selected.
295
319
  */
296
- get allRawFeatures() {
297
- return this._internal.allRawFeatures;
320
+ get rawFeatures() {
321
+ return this._internal.accumulatedRawFeatures;
298
322
  }
299
323
  /** What features are used in the root of this. This can change with new validate calls. */
300
324
  get rootFeatureRefs() {
@@ -1,10 +1,12 @@
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[]];
1
+ import { Filters } from "@configura/web-utilities";
2
+ import { DtoCatalogueParams, DtoLevel, DtoProductRef } from "../CatalogueAPI.js";
3
+ export declare function applyCatalogueFilters<T extends DtoCatalogueParams>(filters: Filters<DtoCatalogueParams>, catalogues: T[]): [{
4
+ [K in keyof DtoCatalogueParams]: string[];
5
+ }, T[]];
4
6
  export interface ProductRefParams {
5
7
  partNr: string;
6
8
  }
7
- export declare function applyProductRefFilters(filters: Filters<ProductRefParams>, productRefs: DtoProductRef[]): [Matches<ProductRefParams>, DtoProductRef[]];
9
+ export declare function applyProductRefFilters(filters: Filters<ProductRefParams>, productRefs: DtoProductRef[]): [string[], DtoProductRef[]];
8
10
  /**
9
11
  * Clones the table of content levels tree.
10
12
  * Filters to only include passed products.
@@ -1,31 +1,102 @@
1
- import { match, pick } from "@configura/web-utilities";
1
+ import { shuffle } from "@configura/web-utilities";
2
+ function filterAndSort(propertyValuesToT, property, filtersByProperty, availablePropertyValuesByProperty) {
3
+ const availablePropertyValues = availablePropertyValuesByProperty[property];
4
+ for (const key of propertyValuesToT.keys()) {
5
+ availablePropertyValues.add(key);
6
+ }
7
+ let items;
8
+ const filter = filtersByProperty[property];
9
+ if (filter.mode === "exact") {
10
+ const { value: key } = filter;
11
+ if (key !== "-") {
12
+ const item = propertyValuesToT.get(key);
13
+ if (item === undefined) {
14
+ items = [];
15
+ }
16
+ else {
17
+ items = [item];
18
+ }
19
+ }
20
+ }
21
+ if (items === undefined) {
22
+ if (filter.mode === "random") {
23
+ items = shuffle(Array.from(propertyValuesToT.values()));
24
+ }
25
+ else {
26
+ items = Array.from(propertyValuesToT.entries())
27
+ .sort((l, r) => l[0].localeCompare(r[0], undefined, { sensitivity: "base" }))
28
+ .map((item) => item[1]);
29
+ }
30
+ if (filter.value > 0 && (filter.mode === "first" || filter.mode === "random")) {
31
+ items = items.slice(0, filter.value);
32
+ }
33
+ }
34
+ return items;
35
+ }
36
+ function stringSetToSortedArray(s) {
37
+ return Array.from(s).sort((l, r) => l.localeCompare(r, undefined, { sensitivity: "base" }));
38
+ }
2
39
  export function applyCatalogueFilters(filters, catalogues) {
3
- const enterprise = match("enterprise", filters.enterprise, catalogues);
4
- const prdCat = match("prdCat", filters.prdCat, enterprise.matching);
5
- const prdCatVersion = match("prdCatVersion", filters.prdCatVersion, prdCat.matching);
6
- const vendor = match("vendor", filters.vendor, prdCatVersion.matching);
7
- const priceList = match("priceList", filters.priceList, vendor.matching);
8
- let picked = pick(filters.enterprise, priceList.matching);
9
- picked = pick(filters.prdCat, picked);
10
- picked = pick(filters.prdCatVersion, picked);
11
- picked = pick(filters.vendor, picked);
12
- picked = pick(filters.priceList, picked);
13
- const matches = {
14
- enterprise,
15
- prdCat,
16
- prdCatVersion,
17
- priceList,
18
- vendor,
40
+ const cataloguesAsTree = new Map();
41
+ for (const catalogueParams of catalogues) {
42
+ const { enterprise, prdCat, prdCatVersion, vendor, priceList } = catalogueParams;
43
+ let byEnterprise = cataloguesAsTree.get(enterprise);
44
+ if (byEnterprise === undefined) {
45
+ byEnterprise = new Map();
46
+ cataloguesAsTree.set(enterprise, byEnterprise);
47
+ }
48
+ let byPrdCat = byEnterprise.get(prdCat);
49
+ if (byPrdCat === undefined) {
50
+ byPrdCat = new Map();
51
+ byEnterprise.set(prdCat, byPrdCat);
52
+ }
53
+ let byPrdCatVersion = byPrdCat.get(prdCatVersion);
54
+ if (byPrdCatVersion === undefined) {
55
+ byPrdCatVersion = new Map();
56
+ byPrdCat.set(prdCatVersion, byPrdCatVersion);
57
+ }
58
+ let byVendor = byPrdCatVersion.get(vendor);
59
+ if (byVendor === undefined) {
60
+ byVendor = new Map();
61
+ byPrdCatVersion.set(vendor, byVendor);
62
+ }
63
+ if (byVendor.has(priceList)) {
64
+ console.warn(`Duplicate catalogue ${enterprise} ${prdCat} ${prdCatVersion} ${vendor} ${priceList}`);
65
+ }
66
+ else {
67
+ byVendor.set(priceList, catalogueParams);
68
+ }
69
+ }
70
+ const filteredCatalogues = [];
71
+ const availablePropertyValues = {
72
+ enterprise: new Set(),
73
+ prdCat: new Set(),
74
+ prdCatVersion: new Set(),
75
+ vendor: new Set(),
76
+ priceList: new Set(),
19
77
  };
20
- return [matches, picked];
78
+ filterAndSort(cataloguesAsTree, "enterprise", filters, availablePropertyValues).map((item) => filterAndSort(item, "prdCat", filters, availablePropertyValues).map((item) => filterAndSort(item, "prdCatVersion", filters, availablePropertyValues).map((item) => filterAndSort(item, "vendor", filters, availablePropertyValues).map((item) => filterAndSort(item, "priceList", filters, availablePropertyValues).forEach((item) => {
79
+ filteredCatalogues.push(item);
80
+ })))));
81
+ const availablePropertyValuesAsSortedArrays = {
82
+ enterprise: stringSetToSortedArray(availablePropertyValues.enterprise),
83
+ prdCat: stringSetToSortedArray(availablePropertyValues.prdCat),
84
+ prdCatVersion: stringSetToSortedArray(availablePropertyValues.prdCatVersion),
85
+ vendor: stringSetToSortedArray(availablePropertyValues.vendor),
86
+ priceList: stringSetToSortedArray(availablePropertyValues.priceList),
87
+ };
88
+ return [availablePropertyValuesAsSortedArrays, filteredCatalogues];
21
89
  }
22
90
  export function applyProductRefFilters(filters, productRefs) {
23
- const partNr = match("partNr", filters.partNr, productRefs);
24
- const result = pick(filters.partNr, partNr.matching);
25
- const args = {
26
- partNr,
91
+ const map = new Map();
92
+ for (const productRef of productRefs) {
93
+ map.set(productRef.partNr, productRef);
94
+ }
95
+ const availablePropertyValues = {
96
+ partNr: new Set(),
27
97
  };
28
- return [args, result];
98
+ const filtered = filterAndSort(map, "partNr", filters, availablePropertyValues);
99
+ return [stringSetToSortedArray(availablePropertyValues.partNr), filtered];
29
100
  }
30
101
  /**
31
102
  * Clones the table of content levels tree.
@@ -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;
@@ -37,7 +37,7 @@ export function wrapWithGetProductCache(getProduct) {
37
37
  export function wrapWithPostValidateCache(postValidate) {
38
38
  const cache = new PromiseCache();
39
39
  return (params, body) => __awaiter(this, void 0, void 0, function* () {
40
- return cache.get(`${makeProductKey(params)}-${makeSelOptionsKey(body.selOptions)}`, () => postValidate(params, body));
40
+ return cache.get(`${makeProductKey(params)}-${makeSelOptionsKey(body.selOptions)}-${body.knownFeatureCodes.join(",")}`, () => postValidate(params, body));
41
41
  });
42
42
  }
43
43
  /** Does both wrapWithGetProductCache and wrapWithPostValidateCache. */
@@ -1,4 +1,5 @@
1
1
  import { AggregatedLoadingObservable } from "@configura/web-utilities";
2
+ import { DtoSyncGroupState } from "../CatalogueAPI.js";
2
3
  import { _CfgProductInternal } from "../CfgProduct.js";
3
4
  import { _CfgOptionInternal } from "../productConfiguration/CfgOption.js";
4
5
  import { ProductLoader } from "../productLoader.js";
@@ -18,13 +19,19 @@ export declare class SyncGroupsHandler {
18
19
  /**
19
20
  * @param verboseLogging Set to true to get verbose sync state changes logged to the console.
20
21
  */
21
- static make(updateMode?: SyncGroupsApplyMode, loadingObservable?: AggregatedLoadingObservable, verboseLogging?: boolean): SyncGroupsHandler;
22
+ static make(updateMode?: SyncGroupsApplyMode, loadingObservable?: AggregatedLoadingObservable, initial?: DtoSyncGroupState, verboseLogging?: boolean): SyncGroupsHandler;
22
23
  private constructor();
23
24
  /** Please note that clones will use the same loadingObservable as their source. */
24
25
  clone(): SyncGroupsHandler;
26
+ getCompactSyncGroupState(): DtoSyncGroupState;
27
+ /** Overwrites the sync state */
28
+ setCompactSyncGroupState(s: DtoSyncGroupState): void;
25
29
  get verboseLogging(): boolean;
26
30
  set verboseLogging(v: boolean);
27
- /** Used to initially apply the sync state onto a new product so that it is "in sync". */
31
+ /**
32
+ * Used to initially apply the sync state onto a new product so that it is "in sync"
33
+ * and to reapply the sync state when an optional additional product is selected.
34
+ */
28
35
  init(product: _CfgProductInternal, productLoader: ProductLoader): Promise<void>;
29
36
  /**
30
37
  * 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.
@@ -262,20 +263,30 @@ export class SyncGroupsHandler {
262
263
  /**
263
264
  * @param verboseLogging Set to true to get verbose sync state changes logged to the console.
264
265
  */
265
- static make(updateMode = SyncGroupsApplyMode.Strict, loadingObservable, verboseLogging = false) {
266
- return new SyncGroupsHandler(new SyncGroupsState(verboseLogging), updateMode, loadingObservable);
266
+ static make(updateMode = SyncGroupsApplyMode.Strict, loadingObservable, initial, verboseLogging = false) {
267
+ return new SyncGroupsHandler(new SyncGroupsState(verboseLogging, initial), updateMode, loadingObservable);
267
268
  }
268
269
  /** Please note that clones will use the same loadingObservable as their source. */
269
270
  clone() {
270
271
  return new SyncGroupsHandler(this._syncState.clone(), this.updateMode, this._loadingObservable);
271
272
  }
273
+ getCompactSyncGroupState() {
274
+ return this._syncState.getCompact();
275
+ }
276
+ /** Overwrites the sync state */
277
+ setCompactSyncGroupState(s) {
278
+ this._syncState.setCompact(s);
279
+ }
272
280
  get verboseLogging() {
273
281
  return this._syncState.verboseLogging;
274
282
  }
275
283
  set verboseLogging(v) {
276
284
  this._syncState.verboseLogging = v;
277
285
  }
278
- /** Used to initially apply the sync state onto a new product so that it is "in sync". */
286
+ /**
287
+ * Used to initially apply the sync state onto a new product so that it is "in sync"
288
+ * and to reapply the sync state when an optional additional product is selected.
289
+ */
279
290
  init(product, productLoader) {
280
291
  return __awaiter(this, void 0, void 0, function* () {
281
292
  const transaction = yield this.newTransaction(product, productLoader, true);
@@ -1,3 +1,4 @@
1
+ import { DtoSyncGroupState } from "../CatalogueAPI.js";
1
2
  import { OptionCode, SyncCode } from "./SyncGroupsHandler.js";
2
3
  /**
3
4
  * The SyncState is used to keep track of the current value of the SyncGroups.
@@ -12,7 +13,7 @@ export declare class SyncGroupsState {
12
13
  /**
13
14
  * @param verboseLogging Set to true to get verbose sync state changes logged to the console.
14
15
  */
15
- constructor(_verboseLogging: boolean);
16
+ constructor(_verboseLogging: boolean, initial: DtoSyncGroupState | undefined);
16
17
  get verboseLogging(): boolean;
17
18
  /**
18
19
  * Set to true to get verbose sync state changes logged to the console.
@@ -31,6 +32,9 @@ export declare class SyncGroupsState {
31
32
  setForSelectMany(syncCode: SyncCode, optionCode: OptionCode, selected: boolean): void;
32
33
  getForSelectOne(syncCode: SyncCode): OptionCode | undefined;
33
34
  getForSelectMany(syncCode: SyncCode, optionCode: OptionCode): boolean | undefined;
35
+ /** Overrides the current sync state */
36
+ setCompact(s: DtoSyncGroupState): void;
37
+ getCompact(): DtoSyncGroupState;
34
38
  log(syncCode?: SyncCode, optionCode?: OptionCode, selected?: boolean): void;
35
39
  }
36
40
  //# sourceMappingURL=SyncGroupsState.d.ts.map
@@ -8,10 +8,11 @@ export class SyncGroupsState {
8
8
  /**
9
9
  * @param verboseLogging Set to true to get verbose sync state changes logged to the console.
10
10
  */
11
- constructor(_verboseLogging) {
11
+ constructor(_verboseLogging, initial) {
12
12
  this._verboseLogging = _verboseLogging;
13
13
  this._selectOne = new Map();
14
14
  this._selectMany = new Map();
15
+ this.setCompact(initial !== null && initial !== void 0 ? initial : {});
15
16
  }
16
17
  get verboseLogging() {
17
18
  return this._verboseLogging;
@@ -26,7 +27,7 @@ export class SyncGroupsState {
26
27
  * @returns a deep copy of the SyncGroupState.
27
28
  */
28
29
  clone() {
29
- return new SyncGroupsState(this._verboseLogging).copyFrom(this);
30
+ return new SyncGroupsState(this._verboseLogging, this.getCompact());
30
31
  }
31
32
  /**
32
33
  * Replaces the internal state of this SyncGroupState with a deep copy of the one in source.
@@ -67,6 +68,47 @@ export class SyncGroupsState {
67
68
  var _a;
68
69
  return (_a = this._selectMany.get(syncCode)) === null || _a === void 0 ? void 0 : _a.get(optionCode);
69
70
  }
71
+ /** Overrides the current sync state */
72
+ setCompact(s) {
73
+ this._selectOne.clear();
74
+ this._selectMany.clear();
75
+ const { selectOne, selectMany } = s;
76
+ if (selectOne !== undefined) {
77
+ for (const { syncCode, optionCode } of selectOne) {
78
+ this._selectOne.set(syncCode, optionCode);
79
+ }
80
+ }
81
+ if (selectMany !== undefined) {
82
+ for (const { syncCode, optionCode, selected } of selectMany) {
83
+ let entry = this._selectMany.get(syncCode);
84
+ if (entry === undefined) {
85
+ entry = new Map();
86
+ this._selectMany.set(syncCode, entry);
87
+ }
88
+ entry.set(optionCode, selected);
89
+ }
90
+ }
91
+ }
92
+ getCompact() {
93
+ const result = {};
94
+ if (this._selectOne.size !== 0) {
95
+ const resultSelectOne = [];
96
+ for (const [syncCode, optionCode] of this._selectOne) {
97
+ resultSelectOne.push({ syncCode, optionCode });
98
+ }
99
+ result.selectOne = resultSelectOne;
100
+ }
101
+ if (this._selectMany.size !== 0) {
102
+ const resultSelectMany = [];
103
+ for (const [syncCode, optionCodeToSelected] of this._selectMany) {
104
+ for (const [optionCode, selected] of optionCodeToSelected) {
105
+ resultSelectMany.push({ syncCode, optionCode, selected });
106
+ }
107
+ }
108
+ result.selectMany = resultSelectMany;
109
+ }
110
+ return result;
111
+ }
70
112
  log(syncCode, optionCode, selected) {
71
113
  if (!this._verboseLogging) {
72
114
  return;