@configura/web-api 2.1.0 → 2.2.0-alpha.1

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 (102) hide show
  1. package/.eslintrc.json +5 -5
  2. package/LICENSE +201 -201
  3. package/README.md +1 -1
  4. package/dist/CatalogueAPI.d.ts +633 -633
  5. package/dist/CatalogueAPI.js +312 -312
  6. package/dist/CfgMeasure.d.ts +32 -32
  7. package/dist/CfgMeasure.js +30 -30
  8. package/dist/CfgProduct.d.ts +344 -344
  9. package/dist/CfgProduct.js +992 -990
  10. package/dist/CfgReferencePathHelper.d.ts +26 -26
  11. package/dist/CfgReferencePathHelper.js +26 -26
  12. package/dist/index.d.ts +24 -24
  13. package/dist/index.js +24 -24
  14. package/dist/io/CfgHistoryManager.d.ts +83 -83
  15. package/dist/io/CfgHistoryManager.js +144 -144
  16. package/dist/io/CfgHistoryToProdConfConnector.d.ts +21 -21
  17. package/dist/io/CfgHistoryToProdConfConnector.js +50 -50
  18. package/dist/io/CfgIOManager.d.ts +53 -53
  19. package/dist/io/CfgIOManager.js +134 -134
  20. package/dist/io/CfgIOProdConfConnector.d.ts +54 -54
  21. package/dist/io/CfgIOProdConfConnector.js +139 -137
  22. package/dist/io/CfgIOWarningSupplier.d.ts +3 -3
  23. package/dist/io/CfgIOWarningSupplier.js +1 -1
  24. package/dist/io/CfgObservableStateManager.d.ts +25 -25
  25. package/dist/io/CfgObservableStateManager.js +69 -69
  26. package/dist/io/CfgObservableStateToProdConfConnector.d.ts +15 -15
  27. package/dist/io/CfgObservableStateToProdConfConnector.js +17 -17
  28. package/dist/io/CfgWindowEventManager.d.ts +21 -21
  29. package/dist/io/CfgWindowEventManager.js +38 -38
  30. package/dist/io/CfgWindowMessageManager.d.ts +40 -40
  31. package/dist/io/CfgWindowMessageManager.js +91 -91
  32. package/dist/io/CfgWindowMessageToProdConfConnector.d.ts +17 -17
  33. package/dist/io/CfgWindowMessageToProdConfConnector.js +19 -19
  34. package/dist/io/index.d.ts +8 -8
  35. package/dist/io/index.js +8 -8
  36. package/dist/material/CfgMaterialMapping.d.ts +7 -7
  37. package/dist/material/CfgMaterialMapping.js +181 -181
  38. package/dist/material/CfgMtrlApplication.d.ts +18 -18
  39. package/dist/material/CfgMtrlApplication.js +43 -43
  40. package/dist/material/CfgMtrlApplicationSource.d.ts +7 -7
  41. package/dist/material/CfgMtrlApplicationSource.js +8 -8
  42. package/dist/material/CfgMtrlSource.d.ts +19 -19
  43. package/dist/material/CfgMtrlSource.js +40 -40
  44. package/dist/material/CfgMtrlSourceWithMetaData.d.ts +7 -7
  45. package/dist/material/CfgMtrlSourceWithMetaData.js +1 -1
  46. package/dist/productConfiguration/CfgFeature.d.ts +199 -199
  47. package/dist/productConfiguration/CfgFeature.js +691 -691
  48. package/dist/productConfiguration/CfgOption.d.ts +160 -160
  49. package/dist/productConfiguration/CfgOption.js +464 -464
  50. package/dist/productConfiguration/CfgProductConfiguration.d.ts +129 -129
  51. package/dist/productConfiguration/CfgProductConfiguration.js +346 -346
  52. package/dist/productConfiguration/filters.d.ts +17 -17
  53. package/dist/productConfiguration/filters.js +141 -141
  54. package/dist/productConfiguration/productParamsGenerator.d.ts +15 -15
  55. package/dist/productConfiguration/productParamsGenerator.js +65 -64
  56. package/dist/productConfiguration/utilitiesProductConfiguration.d.ts +17 -17
  57. package/dist/productConfiguration/utilitiesProductConfiguration.js +87 -87
  58. package/dist/productLoader.d.ts +33 -33
  59. package/dist/productLoader.js +49 -49
  60. package/dist/syncGroups/SyncGroupsApplyMode.d.ts +20 -20
  61. package/dist/syncGroups/SyncGroupsApplyMode.js +21 -21
  62. package/dist/syncGroups/SyncGroupsHandler.d.ts +47 -47
  63. package/dist/syncGroups/SyncGroupsHandler.js +370 -370
  64. package/dist/syncGroups/SyncGroupsPathHelper.d.ts +26 -26
  65. package/dist/syncGroups/SyncGroupsPathHelper.js +90 -90
  66. package/dist/syncGroups/SyncGroupsState.d.ts +39 -39
  67. package/dist/syncGroups/SyncGroupsState.js +167 -167
  68. package/dist/syncGroups/SyncGroupsTransaction.d.ts +154 -154
  69. package/dist/syncGroups/SyncGroupsTransaction.js +589 -589
  70. package/dist/tasks/TaskHandler.d.ts +77 -77
  71. package/dist/tasks/TaskHandler.js +276 -276
  72. package/dist/tasks/formats.d.ts +4 -4
  73. package/dist/tasks/formats.js +7 -7
  74. package/dist/tests/testData/collectorForTest.d.ts +73 -73
  75. package/dist/tests/testData/collectorForTest.js +194 -194
  76. package/dist/tests/testData/dummyProductForTest.d.ts +4 -4
  77. package/dist/tests/testData/dummyProductForTest.js +32 -32
  78. package/dist/tests/testData/testDataAdditionalProductInAdditionalProductInProductForTest.d.ts +11 -11
  79. package/dist/tests/testData/testDataAdditionalProductInAdditionalProductInProductForTest.js +282 -282
  80. package/dist/tests/testData/testDataCachedGetProduct.d.ts +5 -5
  81. package/dist/tests/testData/testDataCachedGetProduct.js +187 -187
  82. package/dist/tests/testData/testDataCachedPostValidate.d.ts +7 -7
  83. package/dist/tests/testData/testDataCachedPostValidate.js +185 -185
  84. package/dist/tests/testData/testDataConstraints.d.ts +3 -3
  85. package/dist/tests/testData/testDataConstraints.js +174 -174
  86. package/dist/tests/testData/testDataNoAdditionalProductNoPropagateForTest.d.ts +3 -3
  87. package/dist/tests/testData/testDataNoAdditionalProductNoPropagateForTest.js +1099 -1099
  88. package/dist/tests/testData/testDataOptions.d.ts +12 -12
  89. package/dist/tests/testData/testDataOptions.js +60 -60
  90. package/dist/tests/testData/testDataProductAggregatedPrice.d.ts +6 -6
  91. package/dist/tests/testData/testDataProductAggregatedPrice.js +189 -189
  92. package/dist/tests/testData/testDataUpcharge.d.ts +8 -8
  93. package/dist/tests/testData/testDataUpcharge.js +121 -121
  94. package/dist/utilitiesCatalogueData.d.ts +47 -47
  95. package/dist/utilitiesCatalogueData.js +180 -180
  96. package/dist/utilitiesCataloguePermission.d.ts +38 -38
  97. package/dist/utilitiesCataloguePermission.js +79 -79
  98. package/dist/utilitiesConfiguration.d.ts +28 -28
  99. package/dist/utilitiesConfiguration.js +200 -200
  100. package/dist/utilitiesNumericValues.d.ts +24 -24
  101. package/dist/utilitiesNumericValues.js +114 -114
  102. package/package.json +4 -4
@@ -1,990 +1,992 @@
1
- var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2
- function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
3
- return new (P || (P = Promise))(function (resolve, reject) {
4
- function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
5
- function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6
- function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
7
- step((generator = generator.apply(thisArg, _arguments || [])).next());
8
- });
9
- };
10
- import { AggregatedLoadingObservable, assert, assertDefined, augmentErrorMessage, compareArrays, count, Observable, toLengthUnit, } from "@configura/web-utilities";
11
- import { CfgMeasureDefinition } from "./CfgMeasure.js";
12
- import { ProductConfigurationBubbleMode } from "./productConfiguration/CfgOption.js";
13
- import { CfgProductConfiguration } from "./productConfiguration/CfgProductConfiguration.js";
14
- import { collectAdditionalProductRefs } from "./productConfiguration/utilitiesProductConfiguration.js";
15
- import { wrapWithCache } from "./productLoader.js";
16
- import { SyncGroupsHandler } from "./syncGroups/SyncGroupsHandler.js";
17
- import { compareCfgProductData, correctDefaultsOnCatalogueParams, isSameCatalogueParams, isSameProductRef, makeProductKey, } from "./utilitiesCatalogueData.js";
18
- import { CfgProdConfParts, convertDtoProductConfToV1, isAdditionalProductConfiguration, isProductConf, } from "./utilitiesConfiguration.js";
19
- function completeSettings(incompleteSettings) {
20
- var _a;
21
- return {
22
- strictSelectOneSelectionCount: (_a = incompleteSettings === null || incompleteSettings === void 0 ? void 0 : incompleteSettings.strictSelectOneSelectionCount) !== null && _a !== void 0 ? _a : false,
23
- syncGroupsApplyMode: incompleteSettings === null || incompleteSettings === void 0 ? void 0 : incompleteSettings.syncGroupsApplyMode,
24
- };
25
- }
26
- /**
27
- * This enum is used internally in the SDK and is not expected by be used directly by integrators.
28
- */
29
- export var CfgProductBubbleMode;
30
- (function (CfgProductBubbleMode) {
31
- /** Stop bubbling. */
32
- CfgProductBubbleMode["Stop"] = "Stop";
33
- /**
34
- * Bubble to the parent CfgProduct up the tree.
35
- * This makes the CfgProduct we we call from notify that it has changed, and the CfgProduct
36
- * above switch out the reference to this.
37
- */
38
- CfgProductBubbleMode["OneLevel"] = "OneLevel";
39
- /** Bubble to the root CfgProduct. */
40
- CfgProductBubbleMode["ToRoot"] = "ToRoot";
41
- /** Bubble to the root CfgProduct and turn on all optional CfgProducts on the way up. */
42
- CfgProductBubbleMode["ToRootAndBubbleSelected"] = "ToRootAndBubbleSelected";
43
- })(CfgProductBubbleMode || (CfgProductBubbleMode = {}));
44
- function isDescriptionMatch(l, r) {
45
- const ld = l.description;
46
- const rd = r.description;
47
- return ld !== undefined && rd !== undefined && ld.toLowerCase() === rd.toLowerCase();
48
- }
49
- /**
50
- * This class is meant to only be used through CfgProduct. It should never be instantiated on its
51
- * own. Normally the internal state of this class should never be directly modified. CfgProduct is
52
- * the class that should be used and interacted with.
53
- */
54
- export class _CfgProductInternal {
55
- constructor(initSuccess, initFail, _productLoaderRaw, prodParams, settings, optional, selected, rootFeatureRefs, rawFeatures, notes, uuid, _rawUnit, _rawProductData, apiSelection, apiConstraints, loadingObservable, parent, root, _additionalProductRef, _syncGroupHandler) {
56
- var _a;
57
- this._productLoaderRaw = _productLoaderRaw;
58
- this.prodParams = prodParams;
59
- this.settings = settings;
60
- this.uuid = uuid;
61
- this._rawUnit = _rawUnit;
62
- this._rawProductData = _rawProductData;
63
- this.loadingObservable = loadingObservable;
64
- this.parent = parent;
65
- this._additionalProductRef = _additionalProductRef;
66
- this._syncGroupHandler = _syncGroupHandler;
67
- this._destroyed = false;
68
- this.additionalProducts = [];
69
- this._notes = new Map();
70
- this.changeObservable = new Observable();
71
- /** Mark this and its descendants as destroyed and remove all listeners */
72
- this.destroy = () => {
73
- var _a;
74
- this._destroyed = true;
75
- this.changeObservable.stopAllListen();
76
- this.configuration.stopAllListenForChange();
77
- for (const additionalProduct of (_a = this.additionalProducts) !== null && _a !== void 0 ? _a : []) {
78
- additionalProduct.destroy();
79
- }
80
- };
81
- /**
82
- * Reset will reset the product to its initial state
83
- */
84
- this.reset = () => __awaiter(this, void 0, void 0, function* () {
85
- if (this._initialClone !== undefined) {
86
- yield this.copyFrom(this._initialClone, true);
87
- }
88
- });
89
- this._notifyAllOfChange = (bubbleMode, committed) => __awaiter(this, void 0, void 0, function* () {
90
- if (bubbleMode === CfgProductBubbleMode.Stop) {
91
- return;
92
- }
93
- const parent = this.parent;
94
- const freshRef = CfgProduct._makeNewRefFrom(this);
95
- this.changeObservable.notifyAll({ freshRef, committed });
96
- if (parent !== undefined) {
97
- yield parent._additionalProductHasChanged(freshRef, bubbleMode === CfgProductBubbleMode.OneLevel
98
- ? CfgProductBubbleMode.Stop
99
- : bubbleMode, committed);
100
- }
101
- });
102
- /** Called when a child (additional product or the configuration) has changed. */
103
- this._childHasChanged = (bubbleMode, committed) => __awaiter(this, void 0, void 0, function* () {
104
- if (bubbleMode === CfgProductBubbleMode.ToRootAndBubbleSelected &&
105
- this.optional &&
106
- !this.selected) {
107
- yield this.setSelected(true, bubbleMode, false);
108
- return;
109
- }
110
- yield this._notifyAllOfChange(bubbleMode, committed);
111
- });
112
- /** Called by child to tell its parent that it has changed. */
113
- this._additionalProductHasChanged = (freshRef, bubbleMode, committed) => __awaiter(this, void 0, void 0, function* () {
114
- const i = this.additionalProducts.findIndex((a) => a.isBackedBySame(freshRef));
115
- if (i !== -1) {
116
- // Child additional product might not be found. This probably means that propagate
117
- // has chopped the branch your on. Let's say we have products A -> B -> C.
118
- // We change an option on C. This is propagated to product A. Product A now
119
- // changes its additional product so that B is no longer a child of A. Hence
120
- // C no longer is part of the tree. Odd, but fully permitted.
121
- this.additionalProducts[i] = freshRef;
122
- }
123
- yield this._childHasChanged(bubbleMode, committed);
124
- });
125
- /** Called by the configuration to tell its parent that it has changed. */
126
- this._configurationHasChanged = (freshRef, bubbleMode, committed) => __awaiter(this, void 0, void 0, function* () {
127
- this._configuration = freshRef;
128
- switch (bubbleMode) {
129
- case ProductConfigurationBubbleMode.ValidateAndBubbleSelected:
130
- // The revalidate call will continue the bubble
131
- yield this._revalidate(CfgProductBubbleMode.ToRootAndBubbleSelected, this._productLoaderRaw, committed);
132
- return;
133
- case ProductConfigurationBubbleMode.BubbleSelected:
134
- yield this._childHasChanged(CfgProductBubbleMode.ToRootAndBubbleSelected, committed);
135
- return;
136
- case ProductConfigurationBubbleMode.Validate:
137
- // The revalidate call will continue the bubble
138
- yield this._revalidate(CfgProductBubbleMode.ToRoot, this._productLoaderRaw, committed);
139
- return;
140
- case ProductConfigurationBubbleMode.ToParentProduct:
141
- // Do not continue bubble as we have reached the parent CfgProduct
142
- return;
143
- case ProductConfigurationBubbleMode.OneLevel:
144
- yield this._childHasChanged(CfgProductBubbleMode.OneLevel, committed);
145
- return;
146
- case ProductConfigurationBubbleMode.Stop:
147
- yield this._childHasChanged(CfgProductBubbleMode.Stop, committed);
148
- return;
149
- case ProductConfigurationBubbleMode.ToRoot:
150
- yield this._childHasChanged(CfgProductBubbleMode.ToRoot, committed);
151
- return;
152
- }
153
- });
154
- this.getDtoConf = (include) => {
155
- var _a;
156
- const conf = {};
157
- const features = this.configuration._internal.getDtoConf((include & CfgProdConfParts.ExtendedData) === CfgProdConfParts.ExtendedData);
158
- if (0 < features.length) {
159
- conf.features = features;
160
- }
161
- if ((include & CfgProdConfParts.ProdParams) === CfgProdConfParts.ProdParams) {
162
- conf.prodParams = this.prodParams;
163
- }
164
- // Only store the syncGroupState for the root product. The same state
165
- // is used for the entire product, including the additional product,
166
- // so no need to store it for additional products.
167
- if ((include & CfgProdConfParts.SyncGroupState) === CfgProdConfParts.SyncGroupState &&
168
- this.parent === undefined) {
169
- conf.syncGroupState = (_a = this.syncGroupHandler) === null || _a === void 0 ? void 0 : _a.getCompactSyncGroupState();
170
- }
171
- const additionalProducts = this.additionalProducts;
172
- if (0 < additionalProducts.length) {
173
- conf.additionalProducts = additionalProducts.map((p) => p._internal.getDtoConf(include));
174
- }
175
- if (this.isAdditionalProduct) {
176
- const refKey = this.refKey;
177
- if (refKey === undefined) {
178
- throw new Error("AdditionalProduct without refKey, this should not happen");
179
- }
180
- const confAddProd = conf;
181
- confAddProd.refKey = refKey;
182
- confAddProd.selected = this.selected;
183
- }
184
- return conf;
185
- };
186
- this.setDtoConf = (s, doValidate, productLoaderForGroupedLoad) => __awaiter(this, void 0, void 0, function* () {
187
- // The DtoProdConf format can contain a sync group state, but
188
- // not the DtoAdditionalProductConfiguration format. So we apply
189
- // any passed state here.
190
- if (this.root === this) {
191
- const syncGroupHandler = this.syncGroupHandler;
192
- const newSyncGroupState = s.syncGroupState;
193
- if (syncGroupHandler !== undefined && newSyncGroupState !== undefined) {
194
- syncGroupHandler.setCompactSyncGroupState(newSyncGroupState);
195
- }
196
- }
197
- return yield this.setApiSelection(convertDtoProductConfToV1(s), doValidate, productLoaderForGroupedLoad);
198
- });
199
- this.setApiSelection = (s, doValidate, productLoaderForGroupedLoad) => __awaiter(this, void 0, void 0, function* () {
200
- return yield this._setApiSelectionWithOtherProduct(s, doValidate, productLoaderForGroupedLoad, undefined);
201
- });
202
- this.copyFrom = (source, doValidate, productLoaderForGroupedLoad) => __awaiter(this, void 0, void 0, function* () {
203
- return yield this._setApiSelectionWithOtherProduct(convertDtoProductConfToV1(source.getDtoConf(CfgProdConfParts.NoExtra)), doValidate, productLoaderForGroupedLoad, source);
204
- });
205
- this._setApiSelectionWithOtherProduct = (productConfiguration, doValidate, productLoaderForGroupedLoad, sourceProduct) => __awaiter(this, void 0, void 0, function* () {
206
- // Wrap with cache will make getProduct for this function call use the same server call
207
- // for the same product with the same params. Used for getProduct (when a new additional
208
- // product is loaded) and postValidate.
209
- productLoaderForGroupedLoad =
210
- productLoaderForGroupedLoad || wrapWithCache(this._productLoaderRaw);
211
- let change = false;
212
- if (sourceProduct !== undefined) {
213
- if (!compareCfgProductData(this._rawProductData, sourceProduct.rawProductData)) {
214
- this._rawProductData = sourceProduct.rawProductData;
215
- change = true;
216
- }
217
- if (this.configuration._internal.addRawFeatures(sourceProduct.configuration.rawFeatures, false)) {
218
- change = true;
219
- }
220
- if (this.configuration._internal.populateFeatures(sourceProduct.configuration.rootFeatureRefs)) {
221
- change = true;
222
- }
223
- const targetNotes = this._notes;
224
- const sourceNotes = sourceProduct._notes;
225
- for (const targetKey of targetNotes.keys()) {
226
- if (!sourceNotes.has(targetKey)) {
227
- targetNotes.delete(targetKey);
228
- change = true;
229
- }
230
- }
231
- for (const [sourceKey, sourceNote] of sourceNotes) {
232
- if (!targetNotes.has(sourceKey)) {
233
- targetNotes.set(sourceKey, sourceNote);
234
- change = true;
235
- }
236
- }
237
- }
238
- const configurationChange = yield this.configuration._internal.setApiSelection(productConfiguration.selOptions, sourceProduct === null || sourceProduct === void 0 ? void 0 : sourceProduct._rawProductData.partsData.constrOptions, false);
239
- if (configurationChange) {
240
- change = true;
241
- }
242
- if (this.optional) {
243
- if (yield this.setSelected(productConfiguration.selected !== false, CfgProductBubbleMode.Stop, false)) {
244
- change = true;
245
- }
246
- }
247
- yield this._syncAndLoadAdditionalProducts(productLoaderForGroupedLoad, productConfiguration);
248
- const apiSelectionAdditionalProducts = productConfiguration.additionalProducts || [];
249
- const additionalProducts = this.additionalProducts.slice();
250
- const additionalProductsCount = additionalProducts.length;
251
- const apiSelectionAdditionalProductsCount = apiSelectionAdditionalProducts.length;
252
- if (apiSelectionAdditionalProductsCount !== additionalProductsCount) {
253
- throw new Error(`Additional products are not same length. This product: "${this.key}". This product additional products count: ${additionalProductsCount}. Passed apiSelection additional products count: ${apiSelectionAdditionalProductsCount}`);
254
- }
255
- const sourceProductAdditionalProducts = sourceProduct === null || sourceProduct === void 0 ? void 0 : sourceProduct.additionalProducts;
256
- assert(!sourceProductAdditionalProducts ||
257
- additionalProductsCount === sourceProductAdditionalProducts.length, `Passed sourceProduct does not have the same number of additional products as this.`);
258
- if ((yield Promise.all(apiSelectionAdditionalProducts.map((apiSelectionAdditionalProduct) => __awaiter(this, void 0, void 0, function* () {
259
- var _b;
260
- const refKey = apiSelectionAdditionalProduct.refKey;
261
- assertDefined(refKey, "Additional product api configurations must have refKey.");
262
- const i = additionalProducts.findIndex((a) => refKey === a.refKey);
263
- assert(i !== -1, `Additional product not found. This product: "${this.key}". refKey of not found additional product: "${refKey}"`);
264
- let sourceProductAdditionalProduct = undefined;
265
- if (sourceProductAdditionalProducts !== undefined) {
266
- sourceProductAdditionalProduct =
267
- (_b = sourceProductAdditionalProducts.find((a) => refKey === a.refKey)) === null || _b === void 0 ? void 0 : _b._internal;
268
- assertDefined(sourceProductAdditionalProduct, "Additional product not found in sourceProduct");
269
- }
270
- const additionalProduct = additionalProducts.splice(i, 1)[0]; // Splicing like this is okay because this is done synchronous. The setCon. is what is async.
271
- return yield additionalProduct._internal._setApiSelectionWithOtherProduct(apiSelectionAdditionalProduct, doValidate, productLoaderForGroupedLoad, sourceProductAdditionalProduct);
272
- })))).some((b) => b)) {
273
- change = true;
274
- }
275
- if (doValidate && configurationChange) {
276
- yield this._revalidate(CfgProductBubbleMode.ToRoot, productLoaderForGroupedLoad, true);
277
- }
278
- else if (change) {
279
- // As setApiSelection is done recursively each level takes care of its own notifications
280
- // so we only need to bubble one level to notify this and swap out the reference in the
281
- // parent.
282
- yield this._notifyAllOfChange(CfgProductBubbleMode.OneLevel, true);
283
- }
284
- return change;
285
- });
286
- this.structureCompare = (other, strictOrder = true, descriptionMatch = false) => {
287
- if (!this.configuration.structureCompare(other.configuration, strictOrder, descriptionMatch)) {
288
- return false;
289
- }
290
- return compareArrays(this.additionalProducts, other.additionalProducts, (l, r) => (!descriptionMatch || (descriptionMatch && isDescriptionMatch(l, r))) &&
291
- l.structureCompare(r, strictOrder, descriptionMatch), !descriptionMatch);
292
- };
293
- this.tryMatchSelection = (other, descriptionMatch = false, // Match on case insensitive description, not code
294
- productLoaderForGroupedLoad) => __awaiter(this, void 0, void 0, function* () {
295
- // wrap with cache will make getProduct for this function call use the same server call
296
- // for the same product with the same params
297
- productLoaderForGroupedLoad =
298
- productLoaderForGroupedLoad || wrapWithCache(this._productLoaderRaw);
299
- let change = false;
300
- if (this.optional && other.optional) {
301
- if (yield this.setSelected(other.selected, CfgProductBubbleMode.Stop, false)) {
302
- change = true;
303
- }
304
- }
305
- const configurationChange = yield this.configuration._internal.tryMatchSelection(other.configuration._internal, descriptionMatch, false);
306
- if (configurationChange) {
307
- change = true;
308
- yield this._revalidate(CfgProductBubbleMode.ToRootAndBubbleSelected, productLoaderForGroupedLoad, true);
309
- }
310
- else if (change) {
311
- yield this._notifyAllOfChange(CfgProductBubbleMode.ToRootAndBubbleSelected, true);
312
- }
313
- const thisAdditionalProducts = this.additionalProducts;
314
- const otherAdditionalProducts = other.additionalProducts;
315
- const promises = [];
316
- if (descriptionMatch) {
317
- for (const otherAdditionalProduct of otherAdditionalProducts) {
318
- if (1 <
319
- count(otherAdditionalProducts, (product) => isDescriptionMatch(product, otherAdditionalProduct))) {
320
- console.warn("tryMatchSelection will ignore products that have the same description");
321
- continue;
322
- }
323
- const toTryMatchProducts = thisAdditionalProducts.filter((product) => isDescriptionMatch(product, otherAdditionalProduct));
324
- if (1 < toTryMatchProducts.length) {
325
- console.warn("tryMatchSelection will ignore products that have the same description");
326
- continue;
327
- }
328
- if (toTryMatchProducts.length === 0) {
329
- continue;
330
- }
331
- promises.push(toTryMatchProducts[0]._internal.tryMatchSelection(otherAdditionalProduct._internal, descriptionMatch, productLoaderForGroupedLoad));
332
- }
333
- }
334
- else {
335
- if (thisAdditionalProducts.length !== otherAdditionalProducts.length) {
336
- console.warn("tryMatchSelection not same count, will not try to match further.");
337
- return change;
338
- }
339
- for (let i = 0; i < thisAdditionalProducts.length; i++) {
340
- promises.push(thisAdditionalProducts[i]._internal.tryMatchSelection(otherAdditionalProducts[i]._internal, descriptionMatch, productLoaderForGroupedLoad));
341
- }
342
- }
343
- if ((yield Promise.all(promises)).some((b) => b)) {
344
- change = true;
345
- }
346
- return change;
347
- });
348
- /** Only features in selected options and selected additional products. */
349
- this._getDescendantFeaturesWithCode = (code) => this.additionalProducts.reduce((agg, additionalProduct) => {
350
- if (additionalProduct.selected) {
351
- agg.push(...additionalProduct._internal._getDescendantFeaturesWithCode(code));
352
- }
353
- return agg;
354
- }, this._configuration._internal._getFeaturesWithCode(code));
355
- /**
356
- * Do a validate call for this product. It does not validate additional products, only this
357
- * product in isolation. The validation result is applied on the configuration. Then additional
358
- * products are synced (unloaded, loaded etc.) Finally the changes bubble up the tree.
359
- */
360
- this._revalidate = (bubbleMode, productLoader, committed) => __awaiter(this, void 0, void 0, function* () {
361
- const { _configuration: configuration } = this;
362
- const token = this.loadingObservable.startChildLoading();
363
- this._revalidateInProgressToken = token;
364
- try {
365
- const response = yield productLoader.postValidate(correctDefaultsOnCatalogueParams(this.prodParams), {
366
- selOptions: configuration._internal.getApiSelection(),
367
- knownFeatureCodes: configuration.rawFeatures.map((f) => f.code),
368
- });
369
- // The revalidateInProgressToken is used to know if some other revalidate
370
- // call has happened after this call, thereby making this call obsolete.
371
- // This is a bit crude in that it does not actually cancel previous validate
372
- // calls, but this is fine because 1. it is hard to cancel a call, especially
373
- // if you wanna have clear code 2. the actual calls are small anyhow 3. most
374
- // of all, the heavy work happens on the server, and that work will not be
375
- // cancelled even if we would cancel the call.
376
- if (this._revalidateInProgressToken !== token) {
377
- return false;
378
- }
379
- // After a successful validate-call we will always assume there
380
- // is a change. It would be possible to compare relevant parts
381
- // of productData, consider the result of setApiSelection and
382
- // syndAndLoad, however the code comparing productData would be fragile
383
- // and likely to break if new data-fields were added.
384
- if (this._destroyed) {
385
- return false;
386
- }
387
- const { productData, rootFeatureRefs, features, notes } = response;
388
- if (notes !== undefined) {
389
- this.addNotes(notes.values());
390
- }
391
- this._rawProductData = productData;
392
- configuration._internal.addRawFeatures(features, true);
393
- if (rootFeatureRefs !== undefined) {
394
- configuration._internal.populateFeatures(rootFeatureRefs);
395
- }
396
- yield configuration._internal.setApiSelection(productData.partsData.selOptions || [], productData.partsData.constrOptions, false);
397
- yield this._syncAndLoadAdditionalProducts(productLoader, undefined);
398
- if (this._destroyed) {
399
- return false;
400
- }
401
- yield this._notifyAllOfChange(bubbleMode, committed);
402
- return true;
403
- }
404
- catch (e) {
405
- throw augmentErrorMessage(e, `Validate product configuration request (${this.prodParams.partNumber}) failure`);
406
- }
407
- finally {
408
- this.loadingObservable.stopChildLoading(token);
409
- }
410
- });
411
- /**
412
- * Based on this configuration find what additional products should be shown and not, unload
413
- * (i.e. destroy) those that should no longer be shown, load the new ones.
414
- */
415
- this._syncAndLoadAdditionalProducts = (productLoaderForGroupedLoad, initialProductConfiguration) => __awaiter(this, void 0, void 0, function* () {
416
- const { _productLoaderRaw: productLoaderRaw, rawProductData: productData, configuration, additionalProducts: currentAdditionalProducts, } = this;
417
- const additionalProductRefs = [
418
- ...(productData.additionalProductRefs || []),
419
- ...collectAdditionalProductRefs(configuration),
420
- ]
421
- .reduce((a, c) => {
422
- if (a.every((p) => !isSameProductRef(p, c))) {
423
- a.push(c);
424
- }
425
- return a;
426
- }, [])
427
- .map((prodRef, originalIndex) => ({
428
- prodRef,
429
- originalIndex,
430
- }));
431
- let change = false;
432
- let i = currentAdditionalProducts.length;
433
- while (i--) {
434
- const currentAdditionalProduct = currentAdditionalProducts[i];
435
- const j = additionalProductRefs.findIndex((p) => {
436
- const prodRef = p.prodRef;
437
- return (prodRef.refKey === currentAdditionalProduct.refKey &&
438
- prodRef.partNumber === currentAdditionalProduct.partNumber &&
439
- isSameCatalogueParams(prodRef.catId, currentAdditionalProduct.catId));
440
- });
441
- if (j === -1) {
442
- currentAdditionalProduct.destroy();
443
- currentAdditionalProducts.splice(i, 1);
444
- change = true;
445
- }
446
- else {
447
- currentAdditionalProduct._internal._updateAdditionalProdRef(additionalProductRefs[j].prodRef);
448
- additionalProductRefs.splice(j, 1);
449
- }
450
- }
451
- const token = this.loadingObservable.startChildLoading();
452
- try {
453
- if (additionalProductRefs.length !== 0) {
454
- change = true;
455
- }
456
- const newAdditionalProducts = yield Promise.all(additionalProductRefs.map((p) => (() => __awaiter(this, void 0, void 0, function* () {
457
- var _c;
458
- const additionalProductRef = p.prodRef;
459
- const refKey = additionalProductRef.refKey;
460
- const additionalProductInitialProductConfiguration = (_c = initialProductConfiguration === null || initialProductConfiguration === void 0 ? void 0 : initialProductConfiguration.additionalProducts) === null || _c === void 0 ? void 0 : _c.find((aP) => refKey === aP.refKey);
461
- if (initialProductConfiguration !== undefined &&
462
- additionalProductInitialProductConfiguration === undefined) {
463
- console.warn(`Did not find initial product configuration for additional product with refKey ${refKey}`);
464
- }
465
- return {
466
- originalIndex: p.originalIndex,
467
- product: CfgProduct._makeNewRefFrom(yield _CfgProductInternal.make(productLoaderRaw, productLoaderForGroupedLoad, Object.assign(Object.assign({}, additionalProductRef.catId), { cid: this.prodParams.cid, lang: this.prodParams.lang, partNumber: additionalProductRef.partNumber }), this.settings, additionalProductRef.optional === true, this.loadingObservable, this, this.root, additionalProductRef, additionalProductInitialProductConfiguration)),
468
- };
469
- }))()));
470
- if (this._destroyed) {
471
- return change;
472
- }
473
- for (const newAdditionalProduct of newAdditionalProducts) {
474
- currentAdditionalProducts.splice(newAdditionalProduct.originalIndex, 0, newAdditionalProduct.product);
475
- }
476
- return change;
477
- }
478
- finally {
479
- this.loadingObservable.stopChildLoading(token);
480
- }
481
- });
482
- this.root = root !== null && root !== void 0 ? root : this;
483
- this.key = makeProductKey(Object.assign(Object.assign({}, prodParams), { partNumber: (_a = _additionalProductRef === null || _additionalProductRef === void 0 ? void 0 : _additionalProductRef.refKey) !== null && _a !== void 0 ? _a : prodParams.partNumber }));
484
- this._selected = optional ? selected : undefined;
485
- this.isAdditionalProduct = _additionalProductRef !== undefined;
486
- if (notes !== undefined) {
487
- this.addNotes(notes);
488
- }
489
- this._configuration = CfgProductConfiguration.make(initSuccess, initFail, rootFeatureRefs, rawFeatures, apiSelection, apiConstraints, this, this.root);
490
- }
491
- get selected() {
492
- return this._selected !== false;
493
- }
494
- /**
495
- * Please note that cloning an additional product will make the clone believe is is
496
- * an additional product, even if it has no parent and root.
497
- * Providing the parent and root of what you clone as arguments is unwise as it will
498
- * make changes you do on the clone be propagated up to the original non-clone root product.
499
- */
500
- clone(parent, root) {
501
- return __awaiter(this, void 0, void 0, function* () {
502
- const product = yield new Promise((initSuccess, initFail) => {
503
- var _a;
504
- const p = new _CfgProductInternal(() => {
505
- initSuccess(p);
506
- }, initFail, this._productLoaderRaw, this.prodParams, this.settings, this.optional, this.selected, this._configuration.rootFeatureRefs, this._configuration.rawFeatures, this._notes.values(), this.uuid, this._rawUnit, this._rawProductData, this.configuration._internal.getApiSelection(), this.configuration._internal.getApiConstrained(), new AggregatedLoadingObservable(), parent, root, this._additionalProductRef, (_a = this._syncGroupHandler) === null || _a === void 0 ? void 0 : _a.clone());
507
- });
508
- for (const additionalProduct of this.additionalProducts) {
509
- product.additionalProducts.push(CfgProduct._makeNewRefFrom(yield additionalProduct._internal.clone(product, root || product)));
510
- }
511
- return product;
512
- });
513
- }
514
- /**
515
- * Internal use. Used when this product is an additional product, and
516
- * changing a parent product has made the settings for this product
517
- * change.
518
- */
519
- _updateAdditionalProdRef(p) {
520
- this._additionalProductRef = p;
521
- if (p.optional !== this.optional) {
522
- this._selected = p.optional ? false : undefined;
523
- }
524
- }
525
- /**
526
- * Return a DtoNode using noteRef as a key.
527
- * Throws an error if no note is found.
528
- */
529
- getNote(noteRef) {
530
- const note = this._notes.get(noteRef);
531
- if (note === undefined) {
532
- throw new Error(`Note with noteRef ${noteRef} not found.`);
533
- }
534
- return note;
535
- }
536
- /**
537
- * noteRefs is a list of keys coming from CfgOption, CfgFeature or CfgProduct.
538
- * The keys are used to get a DtoNote[] from notes at CfgProduct.
539
- */
540
- getNotes(noteRefs) {
541
- return noteRefs.map((noteRef) => this.getNote(noteRef));
542
- }
543
- addNotes(notes) {
544
- for (const note of notes) {
545
- const code = note.code;
546
- if (!code) {
547
- throw new Error("Note with no code");
548
- }
549
- this._notes.set(code, note);
550
- }
551
- }
552
- get notes() {
553
- var _a;
554
- return this.getNotes((_a = this.rawProductData.noteRefs) !== null && _a !== void 0 ? _a : []);
555
- }
556
- get miscFiles() {
557
- var _a;
558
- return (_a = this._rawProductData.miscFiles) !== null && _a !== void 0 ? _a : [];
559
- }
560
- get hasRootFeaturesChanged() {
561
- return (this._configuration._internal.hasRootFeaturesChanged ||
562
- this.additionalProducts.some((p) => p._internal.hasRootFeaturesChanged));
563
- }
564
- get description() {
565
- var _a, _b;
566
- return (_b = (_a = this._additionalProductRef) === null || _a === void 0 ? void 0 : _a.refDescription) !== null && _b !== void 0 ? _b : this._rawProductData.description;
567
- }
568
- get rootNodeSources() {
569
- return this._rawProductData.models;
570
- }
571
- get mtrlApplications() {
572
- return this._rawProductData.mtrlApplications;
573
- }
574
- get currency() {
575
- return this._rawProductData.partsData.currency;
576
- }
577
- get fractionDigits() {
578
- return this._rawProductData.partsData.rounding || 0;
579
- }
580
- get prices() {
581
- return this._rawProductData.partsData.prices;
582
- }
583
- get measureDefinitions() {
584
- var _a;
585
- if (this._measureDefinitions === undefined) {
586
- this._measureDefinitions = ((_a = this._rawProductData.measurements) !== null && _a !== void 0 ? _a : [])
587
- .map((m) => CfgMeasureDefinition.make(m))
588
- .filter((m) => m !== undefined);
589
- }
590
- return this._measureDefinitions;
591
- }
592
- get refKey() {
593
- var _a;
594
- return (_a = this._additionalProductRef) === null || _a === void 0 ? void 0 : _a.refKey;
595
- }
596
- get transform() {
597
- var _a;
598
- return (_a = this._additionalProductRef) === null || _a === void 0 ? void 0 : _a.transform;
599
- }
600
- get anchor() {
601
- var _a;
602
- return (_a = this._additionalProductRef) === null || _a === void 0 ? void 0 : _a.anchor;
603
- }
604
- /** @throws an error if the actual unit sent by the server was not a LengthUnit */
605
- get unit() {
606
- if (this._unit === undefined) {
607
- this._unit = toLengthUnit(this._rawUnit);
608
- }
609
- return this._unit;
610
- }
611
- get aggregatedPrice() {
612
- const { currency, fractionDigits, rawProductData } = this;
613
- const { partsData } = rawProductData;
614
- let { listPrice, basePrice } = partsData;
615
- if (this._selected === false) {
616
- return { basePrice: 0, listPrice: 0, currency, fractionDigits };
617
- }
618
- for (const additionalProduct of this.additionalProducts || []) {
619
- const { basePrice: additionalBasePrice, listPrice: additionalListPrice, currency: additionalCurrency, fractionDigits: additionalFractionDigits, } = additionalProduct.aggregatedPrice;
620
- basePrice += additionalBasePrice;
621
- listPrice += additionalListPrice;
622
- if (currency !== additionalCurrency) {
623
- // This should not be possible
624
- // The server shouldn't return additional products with different currency from their parent
625
- throw new Error(`Currency mismatch between parent product and additional product. Parent product: "${this.key}" Additional product: "${additionalProduct.key}". Currency on parent product: ${currency}. Currency on additional product: ${additionalCurrency}`);
626
- }
627
- if (fractionDigits !== additionalFractionDigits) {
628
- // This should not be possible
629
- // The server shouldn't return additional products with different fraction digits from their parent
630
- throw new Error(`Fraction digits mismatch between parent product and additional product. Parent product: "${this.key}" Additional product: "${additionalProduct.key}". Fraction digits on parent product: ${fractionDigits}. Fraction digits on additional product: ${additionalFractionDigits}`);
631
- }
632
- }
633
- return { basePrice, listPrice, currency, fractionDigits };
634
- }
635
- get optional() {
636
- return this._selected !== undefined;
637
- }
638
- setSelected(selected, bubbleMode, interactive) {
639
- return __awaiter(this, void 0, void 0, function* () {
640
- if (!this.optional) {
641
- console.warn("This product is not optional. Nothing will happen");
642
- return false;
643
- }
644
- if (this._selected === selected) {
645
- return false;
646
- }
647
- // Vitally important that this happens before the call to reset,
648
- // so that the reset won't cause infinite loops
649
- this._selected = selected;
650
- if (interactive) {
651
- if (selected) {
652
- const syncGroupHandler = this.syncGroupHandler;
653
- if (syncGroupHandler !== undefined) {
654
- yield syncGroupHandler.init(this.root, wrapWithCache(this._productLoaderRaw));
655
- }
656
- }
657
- else {
658
- yield this.reset();
659
- // In case a passed initial configuration has made it reset to selected
660
- this._selected = selected;
661
- }
662
- }
663
- yield this._notifyAllOfChange(bubbleMode, true);
664
- return true;
665
- });
666
- }
667
- get configuration() {
668
- return this._configuration;
669
- }
670
- get rawProductData() {
671
- return this._rawProductData;
672
- }
673
- /**
674
- * Please note that this relates to the visibility in the Configuration tree.
675
- * It does not affect the visibility of anything in the 3D view at all.
676
- */
677
- get visibleIfAdditionalProduct() {
678
- return this.rawProductData.hideIfAdditionalProduct !== true;
679
- }
680
- /**
681
- * Please note that this relates to the visibility in the Configuration tree.
682
- * It does not affect the visibility of anything in the 3D view at all.
683
- */
684
- get visibleIfMainProduct() {
685
- return this.rawProductData.hideIfMainProduct !== true;
686
- }
687
- /**
688
- * Please note that this relates to the visibility in the Configuration tree.
689
- * It does not affect the visibility of anything in the 3D view at all.
690
- */
691
- get visible() {
692
- return this.isAdditionalProduct
693
- ? this.visibleIfAdditionalProduct
694
- : this.visibleIfMainProduct;
695
- }
696
- get syncGroupHandler() {
697
- return this.root._syncGroupHandler;
698
- }
699
- get syncGroupsVerboseLogging() {
700
- var _a, _b;
701
- return (_b = (_a = this.syncGroupHandler) === null || _a === void 0 ? void 0 : _a.verboseLogging) !== null && _b !== void 0 ? _b : false;
702
- }
703
- /**
704
- * Set to true to get verbose sync state changes logged to the console.
705
- */
706
- set syncGroupsVerboseLogging(v) {
707
- const syncGroupHandler = this.syncGroupHandler;
708
- if (syncGroupHandler === undefined) {
709
- throw new Error("No syncGroupHandler, so can not change log verbosity");
710
- }
711
- syncGroupHandler.verboseLogging = v;
712
- }
713
- }
714
- _CfgProductInternal.make = (productLoaderRaw, productLoaderForGroupedLoad, // Used when instantiating the current product
715
- prodParams, settings, optional, loadingObservable, parent, root, additionalProductRef, initialProductConfiguration) => __awaiter(void 0, void 0, void 0, function* () {
716
- // Wrap with cache will make getProduct for this function call use the same server call
717
- // for the same product with the same params. Not retained for future calls, only used
718
- // at this initial load.
719
- productLoaderForGroupedLoad =
720
- productLoaderForGroupedLoad || wrapWithCache(productLoaderRaw);
721
- let syncGroupHandler = undefined;
722
- // No root means we are root. We only create SyncGroupsHandler if this is the root product
723
- if (root === undefined) {
724
- let initialSyncGroupsState = undefined;
725
- // DtoProductConf supports containing the sync group state, but the old
726
- // DtoAdditionalProductConfiguration does not
727
- if (initialProductConfiguration !== undefined &&
728
- isProductConf(initialProductConfiguration)) {
729
- initialSyncGroupsState = initialProductConfiguration.syncGroupState;
730
- }
731
- syncGroupHandler = SyncGroupsHandler.make(settings.syncGroupsApplyMode, loadingObservable, initialSyncGroupsState);
732
- }
733
- try {
734
- const defaultCorrectedProdParams = correctDefaultsOnCatalogueParams(prodParams);
735
- let initialProductConfigurationV1 = undefined;
736
- if (initialProductConfiguration !== undefined) {
737
- if (isProductConf(initialProductConfiguration)) {
738
- initialProductConfigurationV1 = convertDtoProductConfToV1(initialProductConfiguration);
739
- }
740
- else if (isAdditionalProductConfiguration(initialProductConfiguration)) {
741
- initialProductConfigurationV1 = initialProductConfiguration;
742
- }
743
- }
744
- const productResponse = yield (initialProductConfigurationV1
745
- ? productLoaderForGroupedLoad.postValidate(defaultCorrectedProdParams, {
746
- knownFeatureCodes: [],
747
- selOptions: initialProductConfigurationV1.selOptions,
748
- })
749
- : productLoaderForGroupedLoad.getProduct(defaultCorrectedProdParams));
750
- const { productData, rootFeatureRefs, features: rawFeatures, notes, uuid, unit, } = productResponse;
751
- const initiallySelected = !optional || (initialProductConfigurationV1 === null || initialProductConfigurationV1 === void 0 ? void 0 : initialProductConfigurationV1.selected) === true;
752
- const product = yield new Promise((initSuccess, initFail) => {
753
- var _a;
754
- const p = new _CfgProductInternal(() => {
755
- // We absolutely do not want anyone to assign to this._configuration. So we want that field private.
756
- // But we can not set the api selection synchronously. And the product configuration needs "this". So we use this callback.
757
- // Feel free to find a nicer more readable solution :)
758
- initSuccess(p);
759
- }, initFail, productLoaderRaw, prodParams, settings, optional, initiallySelected, rootFeatureRefs !== null && rootFeatureRefs !== void 0 ? rootFeatureRefs : [], rawFeatures, notes === null || notes === void 0 ? void 0 : notes.values(), uuid, unit, productData, (_a = productData.partsData.selOptions) !== null && _a !== void 0 ? _a : [], productData.partsData.constrOptions, loadingObservable, parent, root, additionalProductRef, syncGroupHandler);
760
- });
761
- yield product._syncAndLoadAdditionalProducts(productLoaderForGroupedLoad, initialProductConfigurationV1);
762
- product._initialClone = yield product.clone();
763
- if (syncGroupHandler !== undefined && !initialProductConfiguration) {
764
- // As syncGroupHandler is only set for root product we know that we will init with root.
765
- // If we have an initialProductConfiguration we do not run init for the syncGroups.
766
- // The idea with an initial configuration is that the product should look as it did when
767
- // the configuration was saved. Not running init does that. Also, the DtoProductConf
768
- // object can contain a sync state which is then used to populate the sync state.
769
- yield syncGroupHandler.init(product, productLoaderForGroupedLoad);
770
- }
771
- return product;
772
- }
773
- catch (e) {
774
- throw augmentErrorMessage(e, `Load product request (${prodParams.partNumber}) failure`);
775
- }
776
- });
777
- export class CfgProduct {
778
- constructor(_internal) {
779
- this._internal = _internal;
780
- this.isBackedBySame = (other) => this._internal === other._internal;
781
- /**
782
- * Recursively marks this and descendants as destroyed so that late events are ignored
783
- * correctly. If you destroy one shallow copy of this you destroy all.
784
- */
785
- this.destroy = () => this._internal.destroy();
786
- /** Makes a clone of this. It is disconnected from the original. */
787
- this.clone = () => __awaiter(this, void 0, void 0, function* () { return CfgProduct._makeNewRefFrom(yield this._internal.clone()); });
788
- /**
789
- * Only applicable when this product is optional.
790
- * Setting this does not cause a validation call as toggling an optional additional product is
791
- * assumed to always be legal.
792
- */
793
- this.setSelected = (v) => __awaiter(this, void 0, void 0, function* () { return yield this._internal.setSelected(v, CfgProductBubbleMode.ToRootAndBubbleSelected, true); });
794
- /**
795
- * Experimental. Additional products lacks descriptions or keys that are suitably for structure
796
- * compare, so we use strict-order when trying to match the additional products. This makes
797
- * this method work nicely for different products having pretty much the same child products.
798
- */
799
- this.structureCompare = (other, strictOrder = true, descriptionMatch = false) => this._internal.structureCompare(other._internal, strictOrder, descriptionMatch);
800
- /**
801
- * Experimental. Additional products lacks descriptions or keys that are suitably for try
802
- * match, so we use strict-order when trying to match the additional products. This makes
803
- * this method work nicely for different products having pretty much the same child products.
804
- * This method does not propagate its selections.
805
- * This method will cause validation calls if something change.
806
- */
807
- this.tryMatchSelection = (other, descriptionMatch = false // Match on case insensitive description, not code
808
- ) => __awaiter(this, void 0, void 0, function* () { return yield this._internal.tryMatchSelection(other._internal, descriptionMatch); });
809
- /**
810
- * Gets what selections has been made on the product, recursively including product
811
- * configuration, optional products and additional products. Used when a full view of all
812
- * selections on a product is needed, such as when doing Render or Export.
813
- * @deprecated getDtoConf provides a newer format.
814
- * @see getDtoConf
815
- */
816
- this.getApiSelection = () => convertDtoProductConfToV1(this._internal.getDtoConf(CfgProdConfParts.NoExtra), true);
817
- /**
818
- * Applies the configuration (selections) in the passed object onto the product recursively
819
- * including product configuration, optional products and additional products.
820
- * @param doValidate Makes a server side validation call. These are necessary to ensure that
821
- * the right models are loaded.
822
- * @deprecated setDtoConf uses a newer format.
823
- */
824
- this.setApiSelection = (configuration, doValidate = true) => __awaiter(this, void 0, void 0, function* () { return yield this._internal.setApiSelection(configuration, doValidate); });
825
- /**
826
- * A newer alternative version of getApiSelection. This returns the configuration (selections)
827
- * on the product, recursively including product configuration, optional products and additional
828
- * products.
829
- * This version has the following advantages over getApiSelection:
830
- * - The format is clearer, designed to be readable
831
- * - Makes less assumptions about the structure in the Product being unchanging over time. In
832
- * particular, the Feature codes are included in the data, so that changes to what Features
833
- * are used in a Product is less likely to lead to unexpected results.
834
- * - You can request ExtendedData, ProductParams and/or SyncGroupState to be included in the
835
- * result. This extra data is ignored when passed back into the API, but it can be very useful
836
- * for external applications.
837
- * The other version (getApiSelection) has the advantage of using a format directly compatible with the API:s.
838
- * @param include Includes extra data which is not an actual part of the configuration
839
- */
840
- this.getDtoConf = (include = CfgProdConfParts.NoExtra) => this._internal.getDtoConf(include);
841
- /**
842
- * A newer alternative version of setApiSelection.
843
- * @param doValidate Makes a server side validation call. These are necessary to ensure that
844
- * the right models are loaded.
845
- */
846
- this.setDtoConf = (configuration, doValidate = true) => __awaiter(this, void 0, void 0, function* () { return yield this._internal.setDtoConf(configuration, doValidate); });
847
- this.listenForChange = (l) => this._internal.changeObservable.listen(l);
848
- this.stopListenForChange = (l) => this._internal.changeObservable.stopListen(l);
849
- this.stopAllListenForChange = () => this._internal.changeObservable.stopAllListen();
850
- this.listenForLoading = (l) => this._internal.loadingObservable.listen(l);
851
- this.stopListenForLoading = (l) => this._internal.loadingObservable.stopListen(l);
852
- this.stopAllListenForLoading = () => this._internal.loadingObservable.stopAllListen();
853
- }
854
- static make(productLoader, prodParams, settings, initialProductConfiguration) {
855
- return __awaiter(this, void 0, void 0, function* () {
856
- return this._makeNewRefFrom(yield _CfgProductInternal.make(productLoader, undefined, prodParams, completeSettings(settings), false, new AggregatedLoadingObservable(), undefined, undefined, undefined, initialProductConfiguration));
857
- });
858
- }
859
- /**
860
- * Makes an object wrapping the passed object. This is not a clone method, it is a method to
861
- * make a new outer reference. Like a shallow copy. We use this to help frameworks that are
862
- * build around using equals to detect change.
863
- */
864
- static _makeNewRefFrom(source) {
865
- return new this(source);
866
- }
867
- /**
868
- * A client side only key that should uniquely identify this product amongst other additional
869
- * products.
870
- */
871
- get key() {
872
- return this._internal.key;
873
- }
874
- /**
875
- * Only used when this product is in additional product.
876
- * As a product can have multiple instances of the same additional product this key exists.
877
- * It will be unique amongst child products, but not globally unique.
878
- */
879
- get refKey() {
880
- return this._internal.refKey;
881
- }
882
- get notes() {
883
- return this._internal.notes;
884
- }
885
- get miscFiles() {
886
- return this._internal.miscFiles;
887
- }
888
- get prodParams() {
889
- return this._internal.prodParams;
890
- }
891
- get lang() {
892
- return this._internal.prodParams.lang;
893
- }
894
- get catId() {
895
- const { cid, enterprise, prdCat, prdCatVersion, priceList, vendor } = this._internal.prodParams;
896
- return {
897
- cid,
898
- enterprise,
899
- prdCat,
900
- prdCatVersion,
901
- priceList,
902
- vendor,
903
- };
904
- }
905
- get partNumber() {
906
- return this._internal.prodParams.partNumber;
907
- }
908
- get isAdditionalProduct() {
909
- return this._internal.isAdditionalProduct;
910
- }
911
- /** Only used when this product is an additional product. Root products are never optional. */
912
- get optional() {
913
- return this._internal.optional;
914
- }
915
- /**
916
- * Only applicable when this product is optional. If this product is not optional this is
917
- * always true.
918
- */
919
- get selected() {
920
- return this._internal.selected;
921
- }
922
- /**
923
- * Please note that this relates to the visibility in the Configuration tree.
924
- * It does not affect the visibility of anything in the 3D view at all.
925
- * Visibility affects the Configuration for this Product, but any Additional Products
926
- * will not be affected.
927
- */
928
- get visible() {
929
- return this._internal.visible;
930
- }
931
- // A similar text to the one below exists in global-message-managers.md and should be kept in sync.
932
- /**
933
- * Functional selection is a Catalogues feature where selecting Options on Features result in that you
934
- * "jump" to another Product as a result of the Validate call. You normally do not notice that a functional
935
- * selection has occurred except for the styleNr changing. Functional selection can change which Features
936
- * from the original product call are used as root Features. This can in turn affect if serialized
937
- * configuration can be applied or not.
938
- *
939
- * The SDK can currently only apply serialized configuration if the list of root Features has not changed.
940
- * For this reason, when functional selection has happened, extracting data for external systems might work
941
- * well, but reapplying back into Stage will probably fail.
942
- */
943
- get hasRootFeaturesChanged() {
944
- return this._internal.hasRootFeaturesChanged;
945
- }
946
- get rawProductData() {
947
- return this._internal.rawProductData;
948
- }
949
- get uuid() {
950
- return this._internal.uuid;
951
- }
952
- get unit() {
953
- return this._internal.unit;
954
- }
955
- get sku() {
956
- return this._internal.rawProductData.sku;
957
- }
958
- get styleNr() {
959
- return this._internal.rawProductData.partsData.styleNr;
960
- }
961
- /** An URL. */
962
- get preview() {
963
- return this._internal.rawProductData.navImage;
964
- }
965
- get description() {
966
- return this._internal.description;
967
- }
968
- get additionalProducts() {
969
- return this._internal.additionalProducts;
970
- }
971
- get configuration() {
972
- return this._internal.configuration;
973
- }
974
- get transform() {
975
- return this._internal.transform;
976
- }
977
- get currency() {
978
- return this._internal.currency;
979
- }
980
- /**
981
- * If positive the number of fraction digits.
982
- * If negative rounding (essentially the number of zeros to the right)
983
- */
984
- get fractionDigits() {
985
- return this._internal.fractionDigits;
986
- }
987
- get aggregatedPrice() {
988
- return this._internal.aggregatedPrice;
989
- }
990
- }
1
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
3
+ return new (P || (P = Promise))(function (resolve, reject) {
4
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
5
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
7
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
8
+ });
9
+ };
10
+ var _a;
11
+ import { AggregatedLoadingObservable, assert, assertDefined, augmentErrorMessage, compareArrays, count, Observable, toLengthUnit, } from "@configura/web-utilities";
12
+ import { CfgMeasureDefinition } from "./CfgMeasure.js";
13
+ import { ProductConfigurationBubbleMode } from "./productConfiguration/CfgOption.js";
14
+ import { CfgProductConfiguration } from "./productConfiguration/CfgProductConfiguration.js";
15
+ import { collectAdditionalProductRefs } from "./productConfiguration/utilitiesProductConfiguration.js";
16
+ import { wrapWithCache } from "./productLoader.js";
17
+ import { SyncGroupsHandler } from "./syncGroups/SyncGroupsHandler.js";
18
+ import { compareCfgProductData, correctDefaultsOnCatalogueParams, isSameCatalogueParams, isSameProductRef, makeProductKey, } from "./utilitiesCatalogueData.js";
19
+ import { CfgProdConfParts, convertDtoProductConfToV1, isAdditionalProductConfiguration, isProductConf, } from "./utilitiesConfiguration.js";
20
+ function completeSettings(incompleteSettings) {
21
+ var _b;
22
+ return {
23
+ strictSelectOneSelectionCount: (_b = incompleteSettings === null || incompleteSettings === void 0 ? void 0 : incompleteSettings.strictSelectOneSelectionCount) !== null && _b !== void 0 ? _b : false,
24
+ syncGroupsApplyMode: incompleteSettings === null || incompleteSettings === void 0 ? void 0 : incompleteSettings.syncGroupsApplyMode,
25
+ };
26
+ }
27
+ /**
28
+ * This enum is used internally in the SDK and is not expected by be used directly by integrators.
29
+ */
30
+ export var CfgProductBubbleMode;
31
+ (function (CfgProductBubbleMode) {
32
+ /** Stop bubbling. */
33
+ CfgProductBubbleMode["Stop"] = "Stop";
34
+ /**
35
+ * Bubble to the parent CfgProduct up the tree.
36
+ * This makes the CfgProduct we we call from notify that it has changed, and the CfgProduct
37
+ * above switch out the reference to this.
38
+ */
39
+ CfgProductBubbleMode["OneLevel"] = "OneLevel";
40
+ /** Bubble to the root CfgProduct. */
41
+ CfgProductBubbleMode["ToRoot"] = "ToRoot";
42
+ /** Bubble to the root CfgProduct and turn on all optional CfgProducts on the way up. */
43
+ CfgProductBubbleMode["ToRootAndBubbleSelected"] = "ToRootAndBubbleSelected";
44
+ })(CfgProductBubbleMode || (CfgProductBubbleMode = {}));
45
+ function isDescriptionMatch(l, r) {
46
+ const ld = l.description;
47
+ const rd = r.description;
48
+ return ld !== undefined && rd !== undefined && ld.toLowerCase() === rd.toLowerCase();
49
+ }
50
+ /**
51
+ * This class is meant to only be used through CfgProduct. It should never be instantiated on its
52
+ * own. Normally the internal state of this class should never be directly modified. CfgProduct is
53
+ * the class that should be used and interacted with.
54
+ */
55
+ export class _CfgProductInternal {
56
+ constructor(initSuccess, initFail, _productLoaderRaw, prodParams, settings, optional, selected, rootFeatureRefs, rawFeatures, notes, uuid, _rawUnit, _rawProductData, apiSelection, apiConstraints, loadingObservable, parent, root, _additionalProductRef, _syncGroupHandler) {
57
+ var _b;
58
+ this._productLoaderRaw = _productLoaderRaw;
59
+ this.prodParams = prodParams;
60
+ this.settings = settings;
61
+ this.uuid = uuid;
62
+ this._rawUnit = _rawUnit;
63
+ this._rawProductData = _rawProductData;
64
+ this.loadingObservable = loadingObservable;
65
+ this.parent = parent;
66
+ this._additionalProductRef = _additionalProductRef;
67
+ this._syncGroupHandler = _syncGroupHandler;
68
+ this._destroyed = false;
69
+ this.additionalProducts = [];
70
+ this._notes = new Map();
71
+ this.changeObservable = new Observable();
72
+ /** Mark this and its descendants as destroyed and remove all listeners */
73
+ this.destroy = () => {
74
+ var _b;
75
+ this._destroyed = true;
76
+ this.changeObservable.stopAllListen();
77
+ this.configuration.stopAllListenForChange();
78
+ for (const additionalProduct of (_b = this.additionalProducts) !== null && _b !== void 0 ? _b : []) {
79
+ additionalProduct.destroy();
80
+ }
81
+ };
82
+ /**
83
+ * Reset will reset the product to its initial state
84
+ */
85
+ this.reset = () => __awaiter(this, void 0, void 0, function* () {
86
+ if (this._initialClone !== undefined) {
87
+ yield this.copyFrom(this._initialClone, true);
88
+ }
89
+ });
90
+ this._notifyAllOfChange = (bubbleMode, committed) => __awaiter(this, void 0, void 0, function* () {
91
+ if (bubbleMode === CfgProductBubbleMode.Stop) {
92
+ return;
93
+ }
94
+ const parent = this.parent;
95
+ const freshRef = CfgProduct._makeNewRefFrom(this);
96
+ this.changeObservable.notifyAll({ freshRef, committed });
97
+ if (parent !== undefined) {
98
+ yield parent._additionalProductHasChanged(freshRef, bubbleMode === CfgProductBubbleMode.OneLevel
99
+ ? CfgProductBubbleMode.Stop
100
+ : bubbleMode, committed);
101
+ }
102
+ });
103
+ /** Called when a child (additional product or the configuration) has changed. */
104
+ this._childHasChanged = (bubbleMode, committed) => __awaiter(this, void 0, void 0, function* () {
105
+ if (bubbleMode === CfgProductBubbleMode.ToRootAndBubbleSelected &&
106
+ this.optional &&
107
+ !this.selected) {
108
+ yield this.setSelected(true, bubbleMode, false);
109
+ return;
110
+ }
111
+ yield this._notifyAllOfChange(bubbleMode, committed);
112
+ });
113
+ /** Called by child to tell its parent that it has changed. */
114
+ this._additionalProductHasChanged = (freshRef, bubbleMode, committed) => __awaiter(this, void 0, void 0, function* () {
115
+ const i = this.additionalProducts.findIndex((a) => a.isBackedBySame(freshRef));
116
+ if (i !== -1) {
117
+ // Child additional product might not be found. This probably means that propagate
118
+ // has chopped the branch your on. Let's say we have products A -> B -> C.
119
+ // We change an option on C. This is propagated to product A. Product A now
120
+ // changes its additional product so that B is no longer a child of A. Hence
121
+ // C no longer is part of the tree. Odd, but fully permitted.
122
+ this.additionalProducts[i] = freshRef;
123
+ }
124
+ yield this._childHasChanged(bubbleMode, committed);
125
+ });
126
+ /** Called by the configuration to tell its parent that it has changed. */
127
+ this._configurationHasChanged = (freshRef, bubbleMode, committed) => __awaiter(this, void 0, void 0, function* () {
128
+ this._configuration = freshRef;
129
+ switch (bubbleMode) {
130
+ case ProductConfigurationBubbleMode.ValidateAndBubbleSelected:
131
+ // The revalidate call will continue the bubble
132
+ yield this._revalidate(CfgProductBubbleMode.ToRootAndBubbleSelected, this._productLoaderRaw, committed);
133
+ return;
134
+ case ProductConfigurationBubbleMode.BubbleSelected:
135
+ yield this._childHasChanged(CfgProductBubbleMode.ToRootAndBubbleSelected, committed);
136
+ return;
137
+ case ProductConfigurationBubbleMode.Validate:
138
+ // The revalidate call will continue the bubble
139
+ yield this._revalidate(CfgProductBubbleMode.ToRoot, this._productLoaderRaw, committed);
140
+ return;
141
+ case ProductConfigurationBubbleMode.ToParentProduct:
142
+ // Do not continue bubble as we have reached the parent CfgProduct
143
+ return;
144
+ case ProductConfigurationBubbleMode.OneLevel:
145
+ yield this._childHasChanged(CfgProductBubbleMode.OneLevel, committed);
146
+ return;
147
+ case ProductConfigurationBubbleMode.Stop:
148
+ yield this._childHasChanged(CfgProductBubbleMode.Stop, committed);
149
+ return;
150
+ case ProductConfigurationBubbleMode.ToRoot:
151
+ yield this._childHasChanged(CfgProductBubbleMode.ToRoot, committed);
152
+ return;
153
+ }
154
+ });
155
+ this.getDtoConf = (include) => {
156
+ var _b;
157
+ const conf = {};
158
+ const features = this.configuration._internal.getDtoConf((include & CfgProdConfParts.ExtendedData) === CfgProdConfParts.ExtendedData);
159
+ if (0 < features.length) {
160
+ conf.features = features;
161
+ }
162
+ if ((include & CfgProdConfParts.ProdParams) === CfgProdConfParts.ProdParams) {
163
+ conf.prodParams = this.prodParams;
164
+ }
165
+ // Only store the syncGroupState for the root product. The same state
166
+ // is used for the entire product, including the additional product,
167
+ // so no need to store it for additional products.
168
+ if ((include & CfgProdConfParts.SyncGroupState) === CfgProdConfParts.SyncGroupState &&
169
+ this.parent === undefined) {
170
+ conf.syncGroupState = (_b = this.syncGroupHandler) === null || _b === void 0 ? void 0 : _b.getCompactSyncGroupState();
171
+ }
172
+ const additionalProducts = this.additionalProducts;
173
+ if (0 < additionalProducts.length) {
174
+ conf.additionalProducts = additionalProducts.map((p) => p._internal.getDtoConf(include));
175
+ }
176
+ if (this.isAdditionalProduct) {
177
+ const refKey = this.refKey;
178
+ if (refKey === undefined) {
179
+ throw new Error("AdditionalProduct without refKey, this should not happen");
180
+ }
181
+ const confAddProd = conf;
182
+ confAddProd.refKey = refKey;
183
+ confAddProd.selected = this.selected;
184
+ }
185
+ return conf;
186
+ };
187
+ this.setDtoConf = (s, doValidate, productLoaderForGroupedLoad) => __awaiter(this, void 0, void 0, function* () {
188
+ // The DtoProdConf format can contain a sync group state, but
189
+ // not the DtoAdditionalProductConfiguration format. So we apply
190
+ // any passed state here.
191
+ if (this.root === this) {
192
+ const syncGroupHandler = this.syncGroupHandler;
193
+ const newSyncGroupState = s.syncGroupState;
194
+ if (syncGroupHandler !== undefined && newSyncGroupState !== undefined) {
195
+ syncGroupHandler.setCompactSyncGroupState(newSyncGroupState);
196
+ }
197
+ }
198
+ return yield this.setApiSelection(convertDtoProductConfToV1(s), doValidate, productLoaderForGroupedLoad);
199
+ });
200
+ this.setApiSelection = (s, doValidate, productLoaderForGroupedLoad) => __awaiter(this, void 0, void 0, function* () {
201
+ return yield this._setApiSelectionWithOtherProduct(s, doValidate, productLoaderForGroupedLoad, undefined);
202
+ });
203
+ this.copyFrom = (source, doValidate, productLoaderForGroupedLoad) => __awaiter(this, void 0, void 0, function* () {
204
+ return yield this._setApiSelectionWithOtherProduct(convertDtoProductConfToV1(source.getDtoConf(CfgProdConfParts.NoExtra)), doValidate, productLoaderForGroupedLoad, source);
205
+ });
206
+ this._setApiSelectionWithOtherProduct = (productConfiguration, doValidate, productLoaderForGroupedLoad, sourceProduct) => __awaiter(this, void 0, void 0, function* () {
207
+ // Wrap with cache will make getProduct for this function call use the same server call
208
+ // for the same product with the same params. Used for getProduct (when a new additional
209
+ // product is loaded) and postValidate.
210
+ productLoaderForGroupedLoad =
211
+ productLoaderForGroupedLoad || wrapWithCache(this._productLoaderRaw);
212
+ let change = false;
213
+ if (sourceProduct !== undefined) {
214
+ if (!compareCfgProductData(this._rawProductData, sourceProduct.rawProductData)) {
215
+ this._rawProductData = sourceProduct.rawProductData;
216
+ change = true;
217
+ }
218
+ if (this.configuration._internal.addRawFeatures(sourceProduct.configuration.rawFeatures, false)) {
219
+ change = true;
220
+ }
221
+ if (this.configuration._internal.populateFeatures(sourceProduct.configuration.rootFeatureRefs)) {
222
+ change = true;
223
+ }
224
+ const targetNotes = this._notes;
225
+ const sourceNotes = sourceProduct._notes;
226
+ for (const targetKey of targetNotes.keys()) {
227
+ if (!sourceNotes.has(targetKey)) {
228
+ targetNotes.delete(targetKey);
229
+ change = true;
230
+ }
231
+ }
232
+ for (const [sourceKey, sourceNote] of sourceNotes) {
233
+ if (!targetNotes.has(sourceKey)) {
234
+ targetNotes.set(sourceKey, sourceNote);
235
+ change = true;
236
+ }
237
+ }
238
+ }
239
+ const configurationChange = yield this.configuration._internal.setApiSelection(productConfiguration.selOptions, sourceProduct === null || sourceProduct === void 0 ? void 0 : sourceProduct._rawProductData.partsData.constrOptions, false);
240
+ if (configurationChange) {
241
+ change = true;
242
+ }
243
+ if (this.optional) {
244
+ if (yield this.setSelected(productConfiguration.selected !== false, CfgProductBubbleMode.Stop, false)) {
245
+ change = true;
246
+ }
247
+ }
248
+ yield this._syncAndLoadAdditionalProducts(productLoaderForGroupedLoad, productConfiguration);
249
+ const apiSelectionAdditionalProducts = productConfiguration.additionalProducts || [];
250
+ const additionalProducts = this.additionalProducts.slice();
251
+ const additionalProductsCount = additionalProducts.length;
252
+ const apiSelectionAdditionalProductsCount = apiSelectionAdditionalProducts.length;
253
+ if (apiSelectionAdditionalProductsCount !== additionalProductsCount) {
254
+ throw new Error(`Additional products are not same length. This product: "${this.key}". This product additional products count: ${additionalProductsCount}. Passed apiSelection additional products count: ${apiSelectionAdditionalProductsCount}`);
255
+ }
256
+ const sourceProductAdditionalProducts = sourceProduct === null || sourceProduct === void 0 ? void 0 : sourceProduct.additionalProducts;
257
+ assert(!sourceProductAdditionalProducts ||
258
+ additionalProductsCount === sourceProductAdditionalProducts.length, `Passed sourceProduct does not have the same number of additional products as this.`);
259
+ if ((yield Promise.all(apiSelectionAdditionalProducts.map((apiSelectionAdditionalProduct) => __awaiter(this, void 0, void 0, function* () {
260
+ var _c;
261
+ const refKey = apiSelectionAdditionalProduct.refKey;
262
+ assertDefined(refKey, "Additional product api configurations must have refKey.");
263
+ const i = additionalProducts.findIndex((a) => refKey === a.refKey);
264
+ assert(i !== -1, `Additional product not found. This product: "${this.key}". refKey of not found additional product: "${refKey}"`);
265
+ let sourceProductAdditionalProduct = undefined;
266
+ if (sourceProductAdditionalProducts !== undefined) {
267
+ sourceProductAdditionalProduct =
268
+ (_c = sourceProductAdditionalProducts.find((a) => refKey === a.refKey)) === null || _c === void 0 ? void 0 : _c._internal;
269
+ assertDefined(sourceProductAdditionalProduct, "Additional product not found in sourceProduct");
270
+ }
271
+ const additionalProduct = additionalProducts.splice(i, 1)[0]; // Splicing like this is okay because this is done synchronous. The setCon. is what is async.
272
+ return yield additionalProduct._internal._setApiSelectionWithOtherProduct(apiSelectionAdditionalProduct, doValidate, productLoaderForGroupedLoad, sourceProductAdditionalProduct);
273
+ })))).some((b) => b)) {
274
+ change = true;
275
+ }
276
+ if (doValidate && configurationChange) {
277
+ yield this._revalidate(CfgProductBubbleMode.ToRoot, productLoaderForGroupedLoad, true);
278
+ }
279
+ else if (change) {
280
+ // As setApiSelection is done recursively each level takes care of its own notifications
281
+ // so we only need to bubble one level to notify this and swap out the reference in the
282
+ // parent.
283
+ yield this._notifyAllOfChange(CfgProductBubbleMode.OneLevel, true);
284
+ }
285
+ return change;
286
+ });
287
+ this.structureCompare = (other, strictOrder = true, descriptionMatch = false) => {
288
+ if (!this.configuration.structureCompare(other.configuration, strictOrder, descriptionMatch)) {
289
+ return false;
290
+ }
291
+ return compareArrays(this.additionalProducts, other.additionalProducts, (l, r) => (!descriptionMatch || (descriptionMatch && isDescriptionMatch(l, r))) &&
292
+ l.structureCompare(r, strictOrder, descriptionMatch), !descriptionMatch);
293
+ };
294
+ this.tryMatchSelection = (other, descriptionMatch = false, // Match on case insensitive description, not code
295
+ productLoaderForGroupedLoad) => __awaiter(this, void 0, void 0, function* () {
296
+ // wrap with cache will make getProduct for this function call use the same server call
297
+ // for the same product with the same params
298
+ productLoaderForGroupedLoad =
299
+ productLoaderForGroupedLoad || wrapWithCache(this._productLoaderRaw);
300
+ let change = false;
301
+ if (this.optional && other.optional) {
302
+ if (yield this.setSelected(other.selected, CfgProductBubbleMode.Stop, false)) {
303
+ change = true;
304
+ }
305
+ }
306
+ const configurationChange = yield this.configuration._internal.tryMatchSelection(other.configuration._internal, descriptionMatch, false);
307
+ if (configurationChange) {
308
+ change = true;
309
+ yield this._revalidate(CfgProductBubbleMode.ToRootAndBubbleSelected, productLoaderForGroupedLoad, true);
310
+ }
311
+ else if (change) {
312
+ yield this._notifyAllOfChange(CfgProductBubbleMode.ToRootAndBubbleSelected, true);
313
+ }
314
+ const thisAdditionalProducts = this.additionalProducts;
315
+ const otherAdditionalProducts = other.additionalProducts;
316
+ const promises = [];
317
+ if (descriptionMatch) {
318
+ for (const otherAdditionalProduct of otherAdditionalProducts) {
319
+ if (1 <
320
+ count(otherAdditionalProducts, (product) => isDescriptionMatch(product, otherAdditionalProduct))) {
321
+ console.warn("tryMatchSelection will ignore products that have the same description");
322
+ continue;
323
+ }
324
+ const toTryMatchProducts = thisAdditionalProducts.filter((product) => isDescriptionMatch(product, otherAdditionalProduct));
325
+ if (1 < toTryMatchProducts.length) {
326
+ console.warn("tryMatchSelection will ignore products that have the same description");
327
+ continue;
328
+ }
329
+ if (toTryMatchProducts.length === 0) {
330
+ continue;
331
+ }
332
+ promises.push(toTryMatchProducts[0]._internal.tryMatchSelection(otherAdditionalProduct._internal, descriptionMatch, productLoaderForGroupedLoad));
333
+ }
334
+ }
335
+ else {
336
+ if (thisAdditionalProducts.length !== otherAdditionalProducts.length) {
337
+ console.warn("tryMatchSelection not same count, will not try to match further.");
338
+ return change;
339
+ }
340
+ for (let i = 0; i < thisAdditionalProducts.length; i++) {
341
+ promises.push(thisAdditionalProducts[i]._internal.tryMatchSelection(otherAdditionalProducts[i]._internal, descriptionMatch, productLoaderForGroupedLoad));
342
+ }
343
+ }
344
+ if ((yield Promise.all(promises)).some((b) => b)) {
345
+ change = true;
346
+ }
347
+ return change;
348
+ });
349
+ /** Only features in selected options and selected additional products. */
350
+ this._getDescendantFeaturesWithCode = (code) => this.additionalProducts.reduce((agg, additionalProduct) => {
351
+ if (additionalProduct.selected) {
352
+ agg.push(...additionalProduct._internal._getDescendantFeaturesWithCode(code));
353
+ }
354
+ return agg;
355
+ }, this._configuration._internal._getFeaturesWithCode(code));
356
+ /**
357
+ * Do a validate call for this product. It does not validate additional products, only this
358
+ * product in isolation. The validation result is applied on the configuration. Then additional
359
+ * products are synced (unloaded, loaded etc.) Finally the changes bubble up the tree.
360
+ */
361
+ this._revalidate = (bubbleMode, productLoader, committed) => __awaiter(this, void 0, void 0, function* () {
362
+ const { _configuration: configuration } = this;
363
+ const token = this.loadingObservable.startChildLoading();
364
+ this._revalidateInProgressToken = token;
365
+ try {
366
+ const response = yield productLoader.postValidate(correctDefaultsOnCatalogueParams(this.prodParams), {
367
+ selOptions: configuration._internal.getApiSelection(),
368
+ knownFeatureCodes: configuration.rawFeatures.map((f) => f.code),
369
+ });
370
+ // The revalidateInProgressToken is used to know if some other revalidate
371
+ // call has happened after this call, thereby making this call obsolete.
372
+ // This is a bit crude in that it does not actually cancel previous validate
373
+ // calls, but this is fine because 1. it is hard to cancel a call, especially
374
+ // if you wanna have clear code 2. the actual calls are small anyhow 3. most
375
+ // of all, the heavy work happens on the server, and that work will not be
376
+ // cancelled even if we would cancel the call.
377
+ if (this._revalidateInProgressToken !== token) {
378
+ return false;
379
+ }
380
+ // After a successful validate-call we will always assume there
381
+ // is a change. It would be possible to compare relevant parts
382
+ // of productData, consider the result of setApiSelection and
383
+ // syndAndLoad, however the code comparing productData would be fragile
384
+ // and likely to break if new data-fields were added.
385
+ if (this._destroyed) {
386
+ return false;
387
+ }
388
+ const { productData, rootFeatureRefs, features, notes } = response;
389
+ if (notes !== undefined) {
390
+ this.addNotes(notes.values());
391
+ }
392
+ this._rawProductData = productData;
393
+ configuration._internal.addRawFeatures(features, true);
394
+ if (rootFeatureRefs !== undefined) {
395
+ configuration._internal.populateFeatures(rootFeatureRefs);
396
+ }
397
+ yield configuration._internal.setApiSelection(productData.partsData.selOptions || [], productData.partsData.constrOptions, false);
398
+ yield this._syncAndLoadAdditionalProducts(productLoader, undefined);
399
+ if (this._destroyed) {
400
+ return false;
401
+ }
402
+ yield this._notifyAllOfChange(bubbleMode, committed);
403
+ return true;
404
+ }
405
+ catch (e) {
406
+ throw augmentErrorMessage(e, `Validate product configuration request (${this.prodParams.partNumber}) failure`);
407
+ }
408
+ finally {
409
+ this.loadingObservable.stopChildLoading(token);
410
+ }
411
+ });
412
+ /**
413
+ * Based on this configuration find what additional products should be shown and not, unload
414
+ * (i.e. destroy) those that should no longer be shown, load the new ones.
415
+ */
416
+ this._syncAndLoadAdditionalProducts = (productLoaderForGroupedLoad, initialProductConfiguration) => __awaiter(this, void 0, void 0, function* () {
417
+ const { _productLoaderRaw: productLoaderRaw, rawProductData: productData, configuration, additionalProducts: currentAdditionalProducts, } = this;
418
+ const additionalProductRefs = [
419
+ ...(productData.additionalProductRefs || []),
420
+ ...collectAdditionalProductRefs(configuration),
421
+ ]
422
+ .reduce((a, c) => {
423
+ if (a.every((p) => !isSameProductRef(p, c))) {
424
+ a.push(c);
425
+ }
426
+ return a;
427
+ }, [])
428
+ .map((prodRef, originalIndex) => ({
429
+ prodRef,
430
+ originalIndex,
431
+ }));
432
+ let change = false;
433
+ let i = currentAdditionalProducts.length;
434
+ while (i--) {
435
+ const currentAdditionalProduct = currentAdditionalProducts[i];
436
+ const j = additionalProductRefs.findIndex((p) => {
437
+ const prodRef = p.prodRef;
438
+ return (prodRef.refKey === currentAdditionalProduct.refKey &&
439
+ prodRef.partNumber === currentAdditionalProduct.partNumber &&
440
+ isSameCatalogueParams(prodRef.catId, currentAdditionalProduct.catId));
441
+ });
442
+ if (j === -1) {
443
+ currentAdditionalProduct.destroy();
444
+ currentAdditionalProducts.splice(i, 1);
445
+ change = true;
446
+ }
447
+ else {
448
+ currentAdditionalProduct._internal._updateAdditionalProdRef(additionalProductRefs[j].prodRef);
449
+ additionalProductRefs.splice(j, 1);
450
+ }
451
+ }
452
+ const token = this.loadingObservable.startChildLoading();
453
+ try {
454
+ if (additionalProductRefs.length !== 0) {
455
+ change = true;
456
+ }
457
+ const newAdditionalProducts = yield Promise.all(additionalProductRefs.map((p) => (() => __awaiter(this, void 0, void 0, function* () {
458
+ var _d;
459
+ const additionalProductRef = p.prodRef;
460
+ const refKey = additionalProductRef.refKey;
461
+ const additionalProductInitialProductConfiguration = (_d = initialProductConfiguration === null || initialProductConfiguration === void 0 ? void 0 : initialProductConfiguration.additionalProducts) === null || _d === void 0 ? void 0 : _d.find((aP) => refKey === aP.refKey);
462
+ if (initialProductConfiguration !== undefined &&
463
+ additionalProductInitialProductConfiguration === undefined) {
464
+ console.warn(`Did not find initial product configuration for additional product with refKey ${refKey}`);
465
+ }
466
+ return {
467
+ originalIndex: p.originalIndex,
468
+ product: CfgProduct._makeNewRefFrom(yield _CfgProductInternal.make(productLoaderRaw, productLoaderForGroupedLoad, Object.assign(Object.assign({}, additionalProductRef.catId), { cid: this.prodParams.cid, lang: this.prodParams.lang, partNumber: additionalProductRef.partNumber }), this.settings, additionalProductRef.optional === true, this.loadingObservable, this, this.root, additionalProductRef, additionalProductInitialProductConfiguration)),
469
+ };
470
+ }))()));
471
+ if (this._destroyed) {
472
+ return change;
473
+ }
474
+ for (const newAdditionalProduct of newAdditionalProducts) {
475
+ currentAdditionalProducts.splice(newAdditionalProduct.originalIndex, 0, newAdditionalProduct.product);
476
+ }
477
+ return change;
478
+ }
479
+ finally {
480
+ this.loadingObservable.stopChildLoading(token);
481
+ }
482
+ });
483
+ this.root = root !== null && root !== void 0 ? root : this;
484
+ this.key = makeProductKey(Object.assign(Object.assign({}, prodParams), { partNumber: (_b = _additionalProductRef === null || _additionalProductRef === void 0 ? void 0 : _additionalProductRef.refKey) !== null && _b !== void 0 ? _b : prodParams.partNumber }));
485
+ this._selected = optional ? selected : undefined;
486
+ this.isAdditionalProduct = _additionalProductRef !== undefined;
487
+ if (notes !== undefined) {
488
+ this.addNotes(notes);
489
+ }
490
+ this._configuration = CfgProductConfiguration.make(initSuccess, initFail, rootFeatureRefs, rawFeatures, apiSelection, apiConstraints, this, this.root);
491
+ }
492
+ get selected() {
493
+ return this._selected !== false;
494
+ }
495
+ /**
496
+ * Please note that cloning an additional product will make the clone believe is is
497
+ * an additional product, even if it has no parent and root.
498
+ * Providing the parent and root of what you clone as arguments is unwise as it will
499
+ * make changes you do on the clone be propagated up to the original non-clone root product.
500
+ */
501
+ clone(parent, root) {
502
+ return __awaiter(this, void 0, void 0, function* () {
503
+ const product = yield new Promise((initSuccess, initFail) => {
504
+ var _b;
505
+ const p = new _CfgProductInternal(() => {
506
+ initSuccess(p);
507
+ }, initFail, this._productLoaderRaw, this.prodParams, this.settings, this.optional, this.selected, this._configuration.rootFeatureRefs, this._configuration.rawFeatures, this._notes.values(), this.uuid, this._rawUnit, this._rawProductData, this.configuration._internal.getApiSelection(), this.configuration._internal.getApiConstrained(), new AggregatedLoadingObservable(), parent, root, this._additionalProductRef, (_b = this._syncGroupHandler) === null || _b === void 0 ? void 0 : _b.clone());
508
+ });
509
+ for (const additionalProduct of this.additionalProducts) {
510
+ product.additionalProducts.push(CfgProduct._makeNewRefFrom(yield additionalProduct._internal.clone(product, root || product)));
511
+ }
512
+ return product;
513
+ });
514
+ }
515
+ /**
516
+ * Internal use. Used when this product is an additional product, and
517
+ * changing a parent product has made the settings for this product
518
+ * change.
519
+ */
520
+ _updateAdditionalProdRef(p) {
521
+ this._additionalProductRef = p;
522
+ if (p.optional !== this.optional) {
523
+ this._selected = p.optional ? false : undefined;
524
+ }
525
+ }
526
+ /**
527
+ * Return a DtoNode using noteRef as a key.
528
+ * Throws an error if no note is found.
529
+ */
530
+ getNote(noteRef) {
531
+ const note = this._notes.get(noteRef);
532
+ if (note === undefined) {
533
+ throw new Error(`Note with noteRef ${noteRef} not found.`);
534
+ }
535
+ return note;
536
+ }
537
+ /**
538
+ * noteRefs is a list of keys coming from CfgOption, CfgFeature or CfgProduct.
539
+ * The keys are used to get a DtoNote[] from notes at CfgProduct.
540
+ */
541
+ getNotes(noteRefs) {
542
+ return noteRefs.map((noteRef) => this.getNote(noteRef));
543
+ }
544
+ addNotes(notes) {
545
+ for (const note of notes) {
546
+ const code = note.code;
547
+ if (!code) {
548
+ throw new Error("Note with no code");
549
+ }
550
+ this._notes.set(code, note);
551
+ }
552
+ }
553
+ get notes() {
554
+ var _b;
555
+ return this.getNotes((_b = this.rawProductData.noteRefs) !== null && _b !== void 0 ? _b : []);
556
+ }
557
+ get miscFiles() {
558
+ var _b;
559
+ return (_b = this._rawProductData.miscFiles) !== null && _b !== void 0 ? _b : [];
560
+ }
561
+ get hasRootFeaturesChanged() {
562
+ return (this._configuration._internal.hasRootFeaturesChanged ||
563
+ this.additionalProducts.some((p) => p._internal.hasRootFeaturesChanged));
564
+ }
565
+ get description() {
566
+ var _b, _c;
567
+ return (_c = (_b = this._additionalProductRef) === null || _b === void 0 ? void 0 : _b.refDescription) !== null && _c !== void 0 ? _c : this._rawProductData.description;
568
+ }
569
+ get rootNodeSources() {
570
+ return this._rawProductData.models;
571
+ }
572
+ get mtrlApplications() {
573
+ return this._rawProductData.mtrlApplications;
574
+ }
575
+ get currency() {
576
+ return this._rawProductData.partsData.currency;
577
+ }
578
+ get fractionDigits() {
579
+ return this._rawProductData.partsData.rounding || 0;
580
+ }
581
+ get prices() {
582
+ return this._rawProductData.partsData.prices;
583
+ }
584
+ get measureDefinitions() {
585
+ var _b;
586
+ if (this._measureDefinitions === undefined) {
587
+ this._measureDefinitions = ((_b = this._rawProductData.measurements) !== null && _b !== void 0 ? _b : [])
588
+ .map((m) => CfgMeasureDefinition.make(m))
589
+ .filter((m) => m !== undefined);
590
+ }
591
+ return this._measureDefinitions;
592
+ }
593
+ get refKey() {
594
+ var _b;
595
+ return (_b = this._additionalProductRef) === null || _b === void 0 ? void 0 : _b.refKey;
596
+ }
597
+ get transform() {
598
+ var _b;
599
+ return (_b = this._additionalProductRef) === null || _b === void 0 ? void 0 : _b.transform;
600
+ }
601
+ get anchor() {
602
+ var _b;
603
+ return (_b = this._additionalProductRef) === null || _b === void 0 ? void 0 : _b.anchor;
604
+ }
605
+ /** @throws an error if the actual unit sent by the server was not a LengthUnit */
606
+ get unit() {
607
+ if (this._unit === undefined) {
608
+ this._unit = toLengthUnit(this._rawUnit);
609
+ }
610
+ return this._unit;
611
+ }
612
+ get aggregatedPrice() {
613
+ const { currency, fractionDigits, rawProductData } = this;
614
+ const { partsData } = rawProductData;
615
+ let { listPrice, basePrice } = partsData;
616
+ if (this._selected === false) {
617
+ return { basePrice: 0, listPrice: 0, currency, fractionDigits };
618
+ }
619
+ for (const additionalProduct of this.additionalProducts || []) {
620
+ const { basePrice: additionalBasePrice, listPrice: additionalListPrice, currency: additionalCurrency, fractionDigits: additionalFractionDigits, } = additionalProduct.aggregatedPrice;
621
+ basePrice += additionalBasePrice;
622
+ listPrice += additionalListPrice;
623
+ if (currency !== additionalCurrency) {
624
+ // This should not be possible
625
+ // The server shouldn't return additional products with different currency from their parent
626
+ throw new Error(`Currency mismatch between parent product and additional product. Parent product: "${this.key}" Additional product: "${additionalProduct.key}". Currency on parent product: ${currency}. Currency on additional product: ${additionalCurrency}`);
627
+ }
628
+ if (fractionDigits !== additionalFractionDigits) {
629
+ // This should not be possible
630
+ // The server shouldn't return additional products with different fraction digits from their parent
631
+ throw new Error(`Fraction digits mismatch between parent product and additional product. Parent product: "${this.key}" Additional product: "${additionalProduct.key}". Fraction digits on parent product: ${fractionDigits}. Fraction digits on additional product: ${additionalFractionDigits}`);
632
+ }
633
+ }
634
+ return { basePrice, listPrice, currency, fractionDigits };
635
+ }
636
+ get optional() {
637
+ return this._selected !== undefined;
638
+ }
639
+ setSelected(selected, bubbleMode, interactive) {
640
+ return __awaiter(this, void 0, void 0, function* () {
641
+ if (!this.optional) {
642
+ console.warn("This product is not optional. Nothing will happen");
643
+ return false;
644
+ }
645
+ if (this._selected === selected) {
646
+ return false;
647
+ }
648
+ // Vitally important that this happens before the call to reset,
649
+ // so that the reset won't cause infinite loops
650
+ this._selected = selected;
651
+ if (interactive) {
652
+ if (selected) {
653
+ const syncGroupHandler = this.syncGroupHandler;
654
+ if (syncGroupHandler !== undefined) {
655
+ yield syncGroupHandler.init(this.root, wrapWithCache(this._productLoaderRaw));
656
+ }
657
+ }
658
+ else {
659
+ yield this.reset();
660
+ // In case a passed initial configuration has made it reset to selected
661
+ this._selected = selected;
662
+ }
663
+ }
664
+ yield this._notifyAllOfChange(bubbleMode, true);
665
+ return true;
666
+ });
667
+ }
668
+ get configuration() {
669
+ return this._configuration;
670
+ }
671
+ get rawProductData() {
672
+ return this._rawProductData;
673
+ }
674
+ /**
675
+ * Please note that this relates to the visibility in the Configuration tree.
676
+ * It does not affect the visibility of anything in the 3D view at all.
677
+ */
678
+ get visibleIfAdditionalProduct() {
679
+ return this.rawProductData.hideIfAdditionalProduct !== true;
680
+ }
681
+ /**
682
+ * Please note that this relates to the visibility in the Configuration tree.
683
+ * It does not affect the visibility of anything in the 3D view at all.
684
+ */
685
+ get visibleIfMainProduct() {
686
+ return this.rawProductData.hideIfMainProduct !== true;
687
+ }
688
+ /**
689
+ * Please note that this relates to the visibility in the Configuration tree.
690
+ * It does not affect the visibility of anything in the 3D view at all.
691
+ */
692
+ get visible() {
693
+ return this.isAdditionalProduct
694
+ ? this.visibleIfAdditionalProduct
695
+ : this.visibleIfMainProduct;
696
+ }
697
+ get syncGroupHandler() {
698
+ return this.root._syncGroupHandler;
699
+ }
700
+ get syncGroupsVerboseLogging() {
701
+ var _b, _c;
702
+ return (_c = (_b = this.syncGroupHandler) === null || _b === void 0 ? void 0 : _b.verboseLogging) !== null && _c !== void 0 ? _c : false;
703
+ }
704
+ /**
705
+ * Set to true to get verbose sync state changes logged to the console.
706
+ */
707
+ set syncGroupsVerboseLogging(v) {
708
+ const syncGroupHandler = this.syncGroupHandler;
709
+ if (syncGroupHandler === undefined) {
710
+ throw new Error("No syncGroupHandler, so can not change log verbosity");
711
+ }
712
+ syncGroupHandler.verboseLogging = v;
713
+ }
714
+ }
715
+ _a = _CfgProductInternal;
716
+ _CfgProductInternal.make = (productLoaderRaw, productLoaderForGroupedLoad, // Used when instantiating the current product
717
+ prodParams, settings, optional, loadingObservable, parent, root, additionalProductRef, initialProductConfiguration) => __awaiter(void 0, void 0, void 0, function* () {
718
+ // Wrap with cache will make getProduct for this function call use the same server call
719
+ // for the same product with the same params. Not retained for future calls, only used
720
+ // at this initial load.
721
+ productLoaderForGroupedLoad =
722
+ productLoaderForGroupedLoad || wrapWithCache(productLoaderRaw);
723
+ let syncGroupHandler = undefined;
724
+ // No root means we are root. We only create SyncGroupsHandler if this is the root product
725
+ if (root === undefined) {
726
+ let initialSyncGroupsState = undefined;
727
+ // DtoProductConf supports containing the sync group state, but the old
728
+ // DtoAdditionalProductConfiguration does not
729
+ if (initialProductConfiguration !== undefined &&
730
+ isProductConf(initialProductConfiguration)) {
731
+ initialSyncGroupsState = initialProductConfiguration.syncGroupState;
732
+ }
733
+ syncGroupHandler = SyncGroupsHandler.make(settings.syncGroupsApplyMode, loadingObservable, initialSyncGroupsState);
734
+ }
735
+ try {
736
+ const defaultCorrectedProdParams = correctDefaultsOnCatalogueParams(prodParams);
737
+ let initialProductConfigurationV1 = undefined;
738
+ if (initialProductConfiguration !== undefined) {
739
+ if (isProductConf(initialProductConfiguration)) {
740
+ initialProductConfigurationV1 = convertDtoProductConfToV1(initialProductConfiguration);
741
+ }
742
+ else if (isAdditionalProductConfiguration(initialProductConfiguration)) {
743
+ initialProductConfigurationV1 = initialProductConfiguration;
744
+ }
745
+ }
746
+ const productResponse = yield (initialProductConfigurationV1
747
+ ? productLoaderForGroupedLoad.postValidate(defaultCorrectedProdParams, {
748
+ knownFeatureCodes: [],
749
+ selOptions: initialProductConfigurationV1.selOptions,
750
+ })
751
+ : productLoaderForGroupedLoad.getProduct(defaultCorrectedProdParams));
752
+ const { productData, rootFeatureRefs, features: rawFeatures, notes, uuid, unit, } = productResponse;
753
+ const initiallySelected = !optional || (initialProductConfigurationV1 === null || initialProductConfigurationV1 === void 0 ? void 0 : initialProductConfigurationV1.selected) === true;
754
+ const product = yield new Promise((initSuccess, initFail) => {
755
+ var _b;
756
+ const p = new _CfgProductInternal(() => {
757
+ // We absolutely do not want anyone to assign to this._configuration. So we want that field private.
758
+ // But we can not set the api selection synchronously. And the product configuration needs "this". So we use this callback.
759
+ // Feel free to find a nicer more readable solution :)
760
+ initSuccess(p);
761
+ }, initFail, productLoaderRaw, prodParams, settings, optional, initiallySelected, rootFeatureRefs !== null && rootFeatureRefs !== void 0 ? rootFeatureRefs : [], rawFeatures, notes === null || notes === void 0 ? void 0 : notes.values(), uuid, unit, productData, (_b = productData.partsData.selOptions) !== null && _b !== void 0 ? _b : [], productData.partsData.constrOptions, loadingObservable, parent, root, additionalProductRef, syncGroupHandler);
762
+ });
763
+ yield product._syncAndLoadAdditionalProducts(productLoaderForGroupedLoad, initialProductConfigurationV1);
764
+ product._initialClone = yield product.clone();
765
+ if (syncGroupHandler !== undefined && !initialProductConfiguration) {
766
+ // As syncGroupHandler is only set for root product we know that we will init with root.
767
+ // If we have an initialProductConfiguration we do not run init for the syncGroups.
768
+ // The idea with an initial configuration is that the product should look as it did when
769
+ // the configuration was saved. Not running init does that. Also, the DtoProductConf
770
+ // object can contain a sync state which is then used to populate the sync state.
771
+ yield syncGroupHandler.init(product, productLoaderForGroupedLoad);
772
+ }
773
+ return product;
774
+ }
775
+ catch (e) {
776
+ throw augmentErrorMessage(e, `Load product request (${prodParams.partNumber}) failure`);
777
+ }
778
+ });
779
+ export class CfgProduct {
780
+ constructor(_internal) {
781
+ this._internal = _internal;
782
+ this.isBackedBySame = (other) => this._internal === other._internal;
783
+ /**
784
+ * Recursively marks this and descendants as destroyed so that late events are ignored
785
+ * correctly. If you destroy one shallow copy of this you destroy all.
786
+ */
787
+ this.destroy = () => this._internal.destroy();
788
+ /** Makes a clone of this. It is disconnected from the original. */
789
+ this.clone = () => __awaiter(this, void 0, void 0, function* () { return CfgProduct._makeNewRefFrom(yield this._internal.clone()); });
790
+ /**
791
+ * Only applicable when this product is optional.
792
+ * Setting this does not cause a validation call as toggling an optional additional product is
793
+ * assumed to always be legal.
794
+ */
795
+ this.setSelected = (v) => __awaiter(this, void 0, void 0, function* () { return yield this._internal.setSelected(v, CfgProductBubbleMode.ToRootAndBubbleSelected, true); });
796
+ /**
797
+ * Experimental. Additional products lacks descriptions or keys that are suitably for structure
798
+ * compare, so we use strict-order when trying to match the additional products. This makes
799
+ * this method work nicely for different products having pretty much the same child products.
800
+ */
801
+ this.structureCompare = (other, strictOrder = true, descriptionMatch = false) => this._internal.structureCompare(other._internal, strictOrder, descriptionMatch);
802
+ /**
803
+ * Experimental. Additional products lacks descriptions or keys that are suitably for try
804
+ * match, so we use strict-order when trying to match the additional products. This makes
805
+ * this method work nicely for different products having pretty much the same child products.
806
+ * This method does not propagate its selections.
807
+ * This method will cause validation calls if something change.
808
+ */
809
+ this.tryMatchSelection = (other, descriptionMatch = false // Match on case insensitive description, not code
810
+ ) => __awaiter(this, void 0, void 0, function* () { return yield this._internal.tryMatchSelection(other._internal, descriptionMatch); });
811
+ /**
812
+ * Gets what selections has been made on the product, recursively including product
813
+ * configuration, optional products and additional products. Used when a full view of all
814
+ * selections on a product is needed, such as when doing Render or Export.
815
+ * @deprecated getDtoConf provides a newer format.
816
+ * @see getDtoConf
817
+ */
818
+ this.getApiSelection = () => convertDtoProductConfToV1(this._internal.getDtoConf(CfgProdConfParts.NoExtra), true);
819
+ /**
820
+ * Applies the configuration (selections) in the passed object onto the product recursively
821
+ * including product configuration, optional products and additional products.
822
+ * @param doValidate Makes a server side validation call. These are necessary to ensure that
823
+ * the right models are loaded.
824
+ * @deprecated setDtoConf uses a newer format.
825
+ */
826
+ this.setApiSelection = (configuration, doValidate = true) => __awaiter(this, void 0, void 0, function* () { return yield this._internal.setApiSelection(configuration, doValidate); });
827
+ /**
828
+ * A newer alternative version of getApiSelection. This returns the configuration (selections)
829
+ * on the product, recursively including product configuration, optional products and additional
830
+ * products.
831
+ * This version has the following advantages over getApiSelection:
832
+ * - The format is clearer, designed to be readable
833
+ * - Makes less assumptions about the structure in the Product being unchanging over time. In
834
+ * particular, the Feature codes are included in the data, so that changes to what Features
835
+ * are used in a Product is less likely to lead to unexpected results.
836
+ * - You can request ExtendedData, ProductParams and/or SyncGroupState to be included in the
837
+ * result. This extra data is ignored when passed back into the API, but it can be very useful
838
+ * for external applications.
839
+ * The other version (getApiSelection) has the advantage of using a format directly compatible with the API:s.
840
+ * @param include Includes extra data which is not an actual part of the configuration
841
+ */
842
+ this.getDtoConf = (include = CfgProdConfParts.NoExtra) => this._internal.getDtoConf(include);
843
+ /**
844
+ * A newer alternative version of setApiSelection.
845
+ * @param doValidate Makes a server side validation call. These are necessary to ensure that
846
+ * the right models are loaded.
847
+ */
848
+ this.setDtoConf = (configuration, doValidate = true) => __awaiter(this, void 0, void 0, function* () { return yield this._internal.setDtoConf(configuration, doValidate); });
849
+ this.listenForChange = (l) => this._internal.changeObservable.listen(l);
850
+ this.stopListenForChange = (l) => this._internal.changeObservable.stopListen(l);
851
+ this.stopAllListenForChange = () => this._internal.changeObservable.stopAllListen();
852
+ this.listenForLoading = (l) => this._internal.loadingObservable.listen(l);
853
+ this.stopListenForLoading = (l) => this._internal.loadingObservable.stopListen(l);
854
+ this.stopAllListenForLoading = () => this._internal.loadingObservable.stopAllListen();
855
+ }
856
+ static make(productLoader, prodParams, settings, initialProductConfiguration) {
857
+ return __awaiter(this, void 0, void 0, function* () {
858
+ return this._makeNewRefFrom(yield _CfgProductInternal.make(productLoader, undefined, prodParams, completeSettings(settings), false, new AggregatedLoadingObservable(), undefined, undefined, undefined, initialProductConfiguration));
859
+ });
860
+ }
861
+ /**
862
+ * Makes an object wrapping the passed object. This is not a clone method, it is a method to
863
+ * make a new outer reference. Like a shallow copy. We use this to help frameworks that are
864
+ * build around using equals to detect change.
865
+ */
866
+ static _makeNewRefFrom(source) {
867
+ return new this(source);
868
+ }
869
+ /**
870
+ * A client side only key that should uniquely identify this product amongst other additional
871
+ * products.
872
+ */
873
+ get key() {
874
+ return this._internal.key;
875
+ }
876
+ /**
877
+ * Only used when this product is in additional product.
878
+ * As a product can have multiple instances of the same additional product this key exists.
879
+ * It will be unique amongst child products, but not globally unique.
880
+ */
881
+ get refKey() {
882
+ return this._internal.refKey;
883
+ }
884
+ get notes() {
885
+ return this._internal.notes;
886
+ }
887
+ get miscFiles() {
888
+ return this._internal.miscFiles;
889
+ }
890
+ get prodParams() {
891
+ return this._internal.prodParams;
892
+ }
893
+ get lang() {
894
+ return this._internal.prodParams.lang;
895
+ }
896
+ get catId() {
897
+ const { cid, enterprise, prdCat, prdCatVersion, priceList, vendor } = this._internal.prodParams;
898
+ return {
899
+ cid,
900
+ enterprise,
901
+ prdCat,
902
+ prdCatVersion,
903
+ priceList,
904
+ vendor,
905
+ };
906
+ }
907
+ get partNumber() {
908
+ return this._internal.prodParams.partNumber;
909
+ }
910
+ get isAdditionalProduct() {
911
+ return this._internal.isAdditionalProduct;
912
+ }
913
+ /** Only used when this product is an additional product. Root products are never optional. */
914
+ get optional() {
915
+ return this._internal.optional;
916
+ }
917
+ /**
918
+ * Only applicable when this product is optional. If this product is not optional this is
919
+ * always true.
920
+ */
921
+ get selected() {
922
+ return this._internal.selected;
923
+ }
924
+ /**
925
+ * Please note that this relates to the visibility in the Configuration tree.
926
+ * It does not affect the visibility of anything in the 3D view at all.
927
+ * Visibility affects the Configuration for this Product, but any Additional Products
928
+ * will not be affected.
929
+ */
930
+ get visible() {
931
+ return this._internal.visible;
932
+ }
933
+ // A similar text to the one below exists in global-message-managers.md and should be kept in sync.
934
+ /**
935
+ * Functional selection is a Catalogues feature where selecting Options on Features result in that you
936
+ * "jump" to another Product as a result of the Validate call. You normally do not notice that a functional
937
+ * selection has occurred except for the styleNr changing. Functional selection can change which Features
938
+ * from the original product call are used as root Features. This can in turn affect if serialized
939
+ * configuration can be applied or not.
940
+ *
941
+ * The SDK can currently only apply serialized configuration if the list of root Features has not changed.
942
+ * For this reason, when functional selection has happened, extracting data for external systems might work
943
+ * well, but reapplying back into Stage will probably fail.
944
+ */
945
+ get hasRootFeaturesChanged() {
946
+ return this._internal.hasRootFeaturesChanged;
947
+ }
948
+ get rawProductData() {
949
+ return this._internal.rawProductData;
950
+ }
951
+ get uuid() {
952
+ return this._internal.uuid;
953
+ }
954
+ get unit() {
955
+ return this._internal.unit;
956
+ }
957
+ get sku() {
958
+ return this._internal.rawProductData.sku;
959
+ }
960
+ get styleNr() {
961
+ return this._internal.rawProductData.partsData.styleNr;
962
+ }
963
+ /** An URL. */
964
+ get preview() {
965
+ return this._internal.rawProductData.navImage;
966
+ }
967
+ get description() {
968
+ return this._internal.description;
969
+ }
970
+ get additionalProducts() {
971
+ return this._internal.additionalProducts;
972
+ }
973
+ get configuration() {
974
+ return this._internal.configuration;
975
+ }
976
+ get transform() {
977
+ return this._internal.transform;
978
+ }
979
+ get currency() {
980
+ return this._internal.currency;
981
+ }
982
+ /**
983
+ * If positive the number of fraction digits.
984
+ * If negative rounding (essentially the number of zeros to the right)
985
+ */
986
+ get fractionDigits() {
987
+ return this._internal.fractionDigits;
988
+ }
989
+ get aggregatedPrice() {
990
+ return this._internal.aggregatedPrice;
991
+ }
992
+ }