@configura/web-api 1.3.0-alpha.2 → 1.3.0-alpha.7

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 (31) hide show
  1. package/dist/CatalogueAPI.d.ts +55 -1
  2. package/dist/CfgMeasure.d.ts +33 -0
  3. package/dist/CfgMeasure.js +30 -0
  4. package/dist/CfgProduct.d.ts +106 -5
  5. package/dist/CfgProduct.js +117 -71
  6. package/dist/index.d.ts +1 -0
  7. package/dist/index.js +1 -0
  8. package/dist/material/CfgMaterialMapping.js +11 -6
  9. package/dist/material/CfgMtrlApplication.js +4 -4
  10. package/dist/productConfiguration/CfgFeature.d.ts +30 -4
  11. package/dist/productConfiguration/CfgFeature.js +162 -52
  12. package/dist/productConfiguration/CfgOption.d.ts +21 -5
  13. package/dist/productConfiguration/CfgOption.js +125 -24
  14. package/dist/productConfiguration/CfgProductConfiguration.d.ts +74 -4
  15. package/dist/productConfiguration/CfgProductConfiguration.js +158 -49
  16. package/dist/productConfiguration/utilitiesProductConfiguration.d.ts +9 -1
  17. package/dist/productConfiguration/utilitiesProductConfiguration.js +32 -13
  18. package/dist/productLoader.d.ts +22 -0
  19. package/dist/productLoader.js +22 -14
  20. package/dist/tests/testData/dummyProductForTest.js +1 -0
  21. package/dist/tests/testData/testDataAdditionalProductInAdditionalProductInProductForTest.js +13 -0
  22. package/dist/tests/testData/testDataCachedGetProduct.js +6 -0
  23. package/dist/tests/testData/testDataCachedPostValidate.js +6 -0
  24. package/dist/tests/testData/testDataNoAdditionalProductNoPropagateForTest.js +2 -0
  25. package/dist/tests/testData/testDataProductAggregatedPrice.js +6 -1
  26. package/dist/tests/testData/testDataUpcharge.js +4 -0
  27. package/dist/utilitiesCatalogueData.d.ts +5 -1
  28. package/dist/utilitiesCatalogueData.js +9 -1
  29. package/dist/utilitiesNumericValues.d.ts +25 -0
  30. package/dist/utilitiesNumericValues.js +109 -0
  31. package/package.json +3 -3
@@ -7,7 +7,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
7
7
  step((generator = generator.apply(thisArg, _arguments || [])).next());
8
8
  });
9
9
  };
10
- import { compareArrays, count, Observable, someMatch, } from "@configura/web-utilities";
10
+ import { compareArrays, convertLength, count, Observable, someMatch, toLengthUnit, } from "@configura/web-utilities";
11
11
  import { CfgProduct } from "../CfgProduct.js";
12
12
  import { CfgMtrlApplication } from "../material/CfgMtrlApplication.js";
13
13
  import { CfgMtrlApplicationSource } from "../material/CfgMtrlApplicationSource.js";
@@ -62,15 +62,26 @@ function doFreshRefOption(options, optionInternal, beforeNotify) {
62
62
  */
63
63
  export class _CfgFeatureInternal {
64
64
  constructor(rawFeature, allRawFeatures, key, // Unique amongst siblings
65
- parent, parentProduct, rootProduct) {
65
+ parent, parentConfiguration, parentProduct, rootProduct) {
66
66
  this.rawFeature = rawFeature;
67
67
  this.allRawFeatures = allRawFeatures;
68
68
  this.key = key;
69
69
  this.parent = parent;
70
+ this.parentConfiguration = parentConfiguration;
70
71
  this.parentProduct = parentProduct;
71
72
  this.rootProduct = rootProduct;
72
73
  this._selectedOptions = [];
73
74
  this.changeObservable = new Observable();
75
+ this.setNumericValue = (val) => __awaiter(this, void 0, void 0, function* () {
76
+ if (this.selectionType !== SelectionType.SelectOne) {
77
+ throw new Error("Only SelectOne Features can have numeric values");
78
+ }
79
+ const option = this.options.find((option) => option.isAllowedNumericValue(val));
80
+ if (option === undefined) {
81
+ throw new Error("No Option allows the value you tried to set");
82
+ }
83
+ return yield option.setNumericValue(val, true);
84
+ });
74
85
  this._notifyAllOfChange = (bubbleMode) => __awaiter(this, void 0, void 0, function* () {
75
86
  if (bubbleMode === ProductConfigurationBubbleMode.Stop) {
76
87
  return;
@@ -129,6 +140,20 @@ export class _CfgFeatureInternal {
129
140
  const selectedIndex = this._selectedOptions.findIndex(getOptionFilter(option._internal));
130
141
  const isOn = isGroup || apiOptionSelection;
131
142
  if (isOn) {
143
+ if (isSelectOne) {
144
+ if (this.isUseNumericValue) {
145
+ const { numericValue } = apiOptionSelection;
146
+ if (numericValue === undefined) {
147
+ throw new Error("No numeric value for numeric feature");
148
+ }
149
+ const { value, unit } = numericValue;
150
+ if (yield option.setNumericValue(unit === undefined
151
+ ? value
152
+ : convertLength(value, toLengthUnit(unit), this.unit), false)) {
153
+ change = true;
154
+ }
155
+ }
156
+ }
132
157
  if (selectedIndex === -1) {
133
158
  if (isAllOptionsAffectedByAnySelection) {
134
159
  this._selectedOptions.push(option);
@@ -180,6 +205,9 @@ export class _CfgFeatureInternal {
180
205
  if (isAllOptionsAffectedByAnySelection) {
181
206
  this._freshRefAllOptions();
182
207
  }
208
+ if (selectionType === SelectionType.SelectOne) {
209
+ this.pushStretch();
210
+ }
183
211
  // setApiSelection works its way top down and handles notifications on
184
212
  // each level by looking at change returned from its children. That way
185
213
  // we do not get multiple notifications for the same ancestral node even
@@ -190,50 +218,77 @@ export class _CfgFeatureInternal {
190
218
  }
191
219
  return change;
192
220
  });
221
+ /** Pushes to refresh stretch. Does not cause validation. */
222
+ this.pushStretch = () => __awaiter(this, void 0, void 0, function* () {
223
+ if (this.selectionType !== SelectionType.SelectOne) {
224
+ throw new Error("Can only push stretch for SelectOne");
225
+ }
226
+ const value = this.numericValue;
227
+ return (yield Promise.all((this.measureParamCodes || []).map((measureParamCode) => this.parentConfiguration.setStretchReferenceLength(measureParamCode, value, this.unit)))).some((change) => change);
228
+ });
193
229
  this.structureCompare = (other, strictOrder = true, descriptionMatch = false) => this.keyMatch(other, descriptionMatch) &&
194
230
  compareArrays(this.options, other.options, (l, r) => l._internal.structureCompare(r._internal, strictOrder, descriptionMatch), strictOrder);
195
231
  this.tryMatchSelection = (other, descriptionMatch = false) => __awaiter(this, void 0, void 0, function* () {
196
- const selectionType = this.selectionType;
232
+ const { selectionType, isUseNumericValue } = this;
197
233
  if (selectionType !== other.selectionType) {
198
234
  // Will not try if the selection type is different
199
235
  return false;
200
236
  }
201
- const thisOptions = this.options;
202
- const otherOptions = other.options;
203
- const change = (yield Promise.all(otherOptions.map((otherO) => (() => __awaiter(this, void 0, void 0, function* () {
204
- const otherOSelected = otherO.selected;
205
- if (selectionType === SelectionType.SelectOne && !otherOSelected) {
206
- return false;
207
- }
208
- if (1 <
209
- count(otherOptions, (item) => item._internal.keyMatch(otherO._internal, descriptionMatch))) {
210
- console.warn("tryMatchSelection will ignore options that have the same key");
211
- return false;
212
- }
213
- const toTryChangeOptions = thisOptions.filter((o) => otherO._internal.keyMatch(o._internal, descriptionMatch));
214
- if (1 < toTryChangeOptions.length) {
215
- console.warn("tryMatchSelection will ignore options that have the same key");
216
- return false;
217
- }
218
- if (toTryChangeOptions.length === 0) {
219
- return false;
220
- }
221
- const toTryChangeOption = toTryChangeOptions[0];
222
- let change = false;
223
- if (selectionType === SelectionType.SelectMany ||
224
- selectionType === SelectionType.SelectOne) {
225
- // The setSelected will only affect ourselves,
226
- // so we do not need to bubble the notification.
227
- // Instead we use the change variable to know
228
- // when to notify for this feature.
229
- change = yield toTryChangeOption._internal.parent.selectOption(toTryChangeOption._internal, otherOSelected, ProductConfigurationBubbleMode.Stop);
237
+ if (isUseNumericValue !== other.isUseNumericValue) {
238
+ return false;
239
+ }
240
+ let change = false;
241
+ if (isUseNumericValue) {
242
+ // For numeric options we do not try to go any further down. It's becoming to
243
+ // strange to make informed guesses on how to progress down the tree.
244
+ const otherNumericValue = other.numericValue;
245
+ if (otherNumericValue === undefined) {
246
+ throw new Error("Numeric feature without numeric value, this should never happen");
230
247
  }
231
- if (otherOSelected &&
232
- (yield toTryChangeOption._internal.tryMatchSelection(otherO, descriptionMatch))) {
233
- change = true;
248
+ else {
249
+ if (yield this.setNumericValue(otherNumericValue)) {
250
+ change = true;
251
+ }
234
252
  }
235
- return change;
236
- }))()))).some((b) => b);
253
+ }
254
+ else {
255
+ const thisOptions = this.options;
256
+ const otherOptions = other.options;
257
+ change = (yield Promise.all(otherOptions.map((otherO) => (() => __awaiter(this, void 0, void 0, function* () {
258
+ const otherOSelected = otherO.selected;
259
+ if (selectionType === SelectionType.SelectOne && !otherOSelected) {
260
+ return false;
261
+ }
262
+ if (1 <
263
+ count(otherOptions, (item) => item._internal.keyMatch(otherO._internal, descriptionMatch))) {
264
+ console.warn("tryMatchSelection will ignore options that have the same key");
265
+ return false;
266
+ }
267
+ const toTryChangeOptions = thisOptions.filter((o) => otherO._internal.keyMatch(o._internal, descriptionMatch));
268
+ if (1 < toTryChangeOptions.length) {
269
+ console.warn("tryMatchSelection will ignore options that have the same key");
270
+ return false;
271
+ }
272
+ if (toTryChangeOptions.length === 0) {
273
+ return false;
274
+ }
275
+ const toTryChangeOption = toTryChangeOptions[0];
276
+ let change = false;
277
+ if (selectionType === SelectionType.SelectMany ||
278
+ selectionType === SelectionType.SelectOne) {
279
+ // The setSelected will only affect ourselves,
280
+ // so we do not need to bubble the notification.
281
+ // Instead we use the change variable to know
282
+ // when to notify for this feature.
283
+ change = yield toTryChangeOption._internal.parent.selectOption(toTryChangeOption._internal, otherOSelected, ProductConfigurationBubbleMode.Stop);
284
+ }
285
+ if (otherOSelected &&
286
+ (yield toTryChangeOption._internal.tryMatchSelection(otherO, descriptionMatch))) {
287
+ change = true;
288
+ }
289
+ return change;
290
+ }))()))).some((b) => b);
291
+ }
237
292
  if (change) {
238
293
  // tryMatchSelection works its way top down and handles notifications on
239
294
  // each level by looking at change returned from its children. That way
@@ -256,17 +311,13 @@ export class _CfgFeatureInternal {
256
311
  throw new Error(`Multiple features are always selected and are not user selectable. Feature key: "${this.key}".`);
257
312
  }
258
313
  if (this.selectionType === SelectionType.SelectOne) {
259
- // Select one can never be deselected. However, propagate can
260
- // make calls with deselect ending up here. We simply ignore them.
261
- return false;
314
+ throw new Error(`Select one can never be deselected. Feature key: "${this.key}".`);
262
315
  }
263
316
  }
264
317
  const selectedOptions = this._selectedOptions;
265
318
  const isAllOptionsAffectedByAnySelection = this.isAllOptionsAffectedByAnySelection;
266
319
  const index = selectedOptions.findIndex(getOptionFilter(optionInternal));
267
- if ((index !== -1) === on) {
268
- return false;
269
- }
320
+ const isActualChange = (index === -1) === on;
270
321
  if (on) {
271
322
  // Calling this.options will populate (generate) all Options if they have not yet
272
323
  // been generated. As we are selecting an option we need all Options to be generated.
@@ -286,6 +337,9 @@ export class _CfgFeatureInternal {
286
337
  else {
287
338
  doFreshRefOption(options, optionInternal, (freshRef) => selectedOptions.push(freshRef));
288
339
  }
340
+ if (this.selectionType === SelectionType.SelectOne) {
341
+ yield this.pushStretch();
342
+ }
289
343
  }
290
344
  else {
291
345
  selectedOptions.splice(index, 1);
@@ -308,7 +362,7 @@ export class _CfgFeatureInternal {
308
362
  yield this._notifyAllOfChange(!on && bubbleMode === ProductConfigurationBubbleMode.ValidateAndBubbleSelected
309
363
  ? ProductConfigurationBubbleMode.Validate
310
364
  : bubbleMode);
311
- return true;
365
+ return isActualChange;
312
366
  });
313
367
  this.isSelected = (option) => this.selectionType === SelectionType.Group ||
314
368
  this._selectedOptions.some(getOptionFilter(option));
@@ -338,9 +392,42 @@ export class _CfgFeatureInternal {
338
392
  get groupCode() {
339
393
  return this.rawFeature.groupCode;
340
394
  }
395
+ get isUseNumericValue() {
396
+ return this.rawFeature.numericOrder;
397
+ }
398
+ get numericValue() {
399
+ if (this.selectionType !== SelectionType.SelectOne) {
400
+ throw new Error("Only SelectOne Features can have numeric values");
401
+ }
402
+ const { selectedOptions } = this;
403
+ if (selectedOptions.length === 0) {
404
+ return undefined;
405
+ }
406
+ const value = selectedOptions[0].numericValue;
407
+ if (value === undefined) {
408
+ if (this.isUseNumericValue) {
409
+ throw new Error("No numericValue set on option with numeric sku feature");
410
+ }
411
+ return undefined;
412
+ }
413
+ return value;
414
+ }
341
415
  get description() {
342
416
  return this.rawFeature.description;
343
417
  }
418
+ /**
419
+ * The MeasureParam class is re-used for different purposes. In Features it is used
420
+ * to indicate which stretch measures inside Models shall be affected by this state
421
+ * of this Feature. Hence only the code property is used.
422
+ */
423
+ get measureParamCodes() {
424
+ var _a;
425
+ return (_a = this.rawFeature.measureParams) === null || _a === void 0 ? void 0 : _a.map((measureParam) => measureParam.code);
426
+ }
427
+ get unit() {
428
+ const unit = this.rawFeature.unit;
429
+ return unit === undefined ? this.parentProduct.unit : toLengthUnit(unit);
430
+ }
344
431
  get mtrlApplications() {
345
432
  if (this._mtrlApplications === undefined) {
346
433
  this._mtrlApplications = (this.rawFeature.mtrlApplications || []).map((m) => CfgMtrlApplication.fromMtrlLikeApplication(CfgMtrlApplicationSource.Feature, m));
@@ -379,7 +466,7 @@ export class _CfgFeatureInternal {
379
466
  const hasDuplicateDescription = someMatch(this.rawFeature.options, (l, r) => {
380
467
  return l.description.toLowerCase() === r.description.toLowerCase();
381
468
  });
382
- this._options = this.rawFeature.options.map((o) => CfgOption.make(o, this, this.allRawFeatures, hasDuplicateDescription, this.parentProduct, this.rootProduct));
469
+ this._options = this.rawFeature.options.map((o) => CfgOption.make(o, this.allRawFeatures, hasDuplicateDescription, this, this.parentConfiguration, this.parentProduct, this.rootProduct));
383
470
  }
384
471
  return this._options;
385
472
  }
@@ -421,6 +508,11 @@ export class CfgFeature {
421
508
  constructor(_internal) {
422
509
  this._internal = _internal;
423
510
  this.isBackedBySame = (other) => this._internal === other._internal;
511
+ /**
512
+ * This will find the first option allowing the value, set the value on it and select it.
513
+ * This is an implicit option-select.
514
+ */
515
+ this.setNumericValue = (val) => __awaiter(this, void 0, void 0, function* () { return yield this._internal.setNumericValue(val); });
424
516
  /**
425
517
  * Selects the passed Option.
426
518
  * Only Options belonging to Features that are "select many" can be deselected.
@@ -433,6 +525,17 @@ export class CfgFeature {
433
525
  this.listenForChange = (l) => this._internal.changeObservable.listen(l);
434
526
  this.stopListenForChange = (l) => this._internal.changeObservable.stopListen(l);
435
527
  }
528
+ static make(rawFeature, allRawFeatures, key, parent, parentConfiguration, parentProduct, rootProduct) {
529
+ return new this(new _CfgFeatureInternal(rawFeature, allRawFeatures, key, parent, parentConfiguration, parentProduct, rootProduct));
530
+ }
531
+ /**
532
+ * Makes an object wrapping the passed object. This is not a clone method,
533
+ * it is a method to make a new outer reference. Like a shallow copy.
534
+ * We use this to help frameworks that are built around using equals to detect change.
535
+ */
536
+ static _makeNewRefFrom(internal) {
537
+ return new this(internal);
538
+ }
436
539
  get parentProduct() {
437
540
  return CfgProduct._makeNewRefFrom(this._internal.parentProduct);
438
541
  }
@@ -451,6 +554,20 @@ export class CfgFeature {
451
554
  get groupCode() {
452
555
  return this._internal.groupCode;
453
556
  }
557
+ /**
558
+ * If true the options in the feature is selected by both sending its code and numeric value
559
+ * when selecting.
560
+ */
561
+ get isUseNumericValue() {
562
+ return this._internal.isUseNumericValue;
563
+ }
564
+ /** This will read the numeric value of the selected option. */
565
+ get numericValue() {
566
+ return this._internal.numericValue;
567
+ }
568
+ get unit() {
569
+ return this._internal.unit;
570
+ }
454
571
  get description() {
455
572
  return this._internal.description;
456
573
  }
@@ -474,10 +591,3 @@ export class CfgFeature {
474
591
  return this._internal.visible;
475
592
  }
476
593
  }
477
- CfgFeature.make = (rawFeature, allRawFeatures, key, parent, parentProduct, rootProduct) => new CfgFeature(new _CfgFeatureInternal(rawFeature, allRawFeatures, key, parent, parentProduct, rootProduct));
478
- /**
479
- * Makes an object wrapping the passed object. This is not a clone method,
480
- * it is a method to make a new outer reference. Like a shallow copy.
481
- * We use this to help frameworks that are built around using equals to detect change.
482
- */
483
- CfgFeature._makeNewRefFrom = (internal) => new CfgFeature(internal);
@@ -1,8 +1,10 @@
1
- import { Observable, SingleArgCallback } from "@configura/web-utilities";
1
+ import { LengthUnit, Observable, SingleArgCallback } from "@configura/web-utilities";
2
2
  import { Feature, Option, SelectedOption } from "../CatalogueAPI.js";
3
3
  import { CfgProduct, _CfgProductInternal } from "../CfgProduct.js";
4
4
  import { CfgMtrlApplication } from "../material/CfgMtrlApplication.js";
5
+ import { NumericValuesSelection } from "../utilitiesNumericValues.js";
5
6
  import { CfgFeature, _CfgFeatureInternal } from "./CfgFeature.js";
7
+ import { _CfgProductConfigurationInternal } from "./CfgProductConfiguration.js";
6
8
  export declare type OptionChangeNotification = {
7
9
  freshRef: CfgOption;
8
10
  };
@@ -49,16 +51,24 @@ export declare enum ProductConfigurationBubbleMode {
49
51
  */
50
52
  export declare class _CfgOptionInternal {
51
53
  readonly rawOption: Option;
52
- readonly parent: _CfgFeatureInternal;
53
54
  private readonly allRawFeatures;
55
+ readonly parent: _CfgFeatureInternal;
56
+ readonly parentConfiguration: _CfgProductConfigurationInternal;
54
57
  readonly parentProduct: _CfgProductInternal;
55
58
  readonly rootProduct: _CfgProductInternal;
56
- constructor(rawOption: Option, parent: _CfgFeatureInternal, allRawFeatures: Feature[], siblingHasDuplicateDescription: boolean, parentProduct: _CfgProductInternal, rootProduct: _CfgProductInternal);
59
+ constructor(rawOption: Option, allRawFeatures: Feature[], siblingHasDuplicateDescription: boolean, parent: _CfgFeatureInternal, parentConfiguration: _CfgProductConfigurationInternal, parentProduct: _CfgProductInternal, rootProduct: _CfgProductInternal);
57
60
  private _features;
58
61
  private _mtrlApplications;
59
62
  readonly key: string;
63
+ private _numericValue;
64
+ readonly allowedNumericValues: NumericValuesSelection | undefined;
65
+ isAllowedNumericValue(val: number): boolean;
60
66
  readonly changeObservable: Observable<OptionChangeNotification>;
61
67
  get code(): string;
68
+ get isUseNumericValue(): boolean;
69
+ get numericValue(): number | undefined;
70
+ setNumericValue(val: number, doSelectOption: boolean): Promise<boolean>;
71
+ get unit(): LengthUnit;
62
72
  get description(): string;
63
73
  get selected(): boolean;
64
74
  get ancestorsSelected(): boolean;
@@ -79,13 +89,13 @@ export declare class _CfgOptionInternal {
79
89
  }
80
90
  export declare class CfgOption {
81
91
  readonly _internal: _CfgOptionInternal;
82
- static make: (rawOption: Option, parent: _CfgFeatureInternal, allRawFeatures: Feature[], siblingHasDuplicateDescription: boolean, parentProduct: _CfgProductInternal, rootProduct: _CfgProductInternal) => CfgOption;
92
+ static make(rawOption: Option, allRawFeatures: Feature[], siblingHasDuplicateDescription: boolean, parent: _CfgFeatureInternal, parentConfiguration: _CfgProductConfigurationInternal, parentProduct: _CfgProductInternal, rootProduct: _CfgProductInternal): CfgOption;
83
93
  /**
84
94
  * Makes an object wrapping the passed object. This is not a clone method,
85
95
  * it is a method to make a new outer reference. Like a shallow copy.
86
96
  * We use this to help frameworks that are build around using equals to detect change.
87
97
  */
88
- static _makeNewRefFrom: (internal: _CfgOptionInternal) => CfgOption;
98
+ static _makeNewRefFrom(internal: _CfgOptionInternal): CfgOption;
89
99
  /**
90
100
  * Private constructor and make-method because make new ref requires the constructor to
91
101
  * take an internal and we don't want those who instantiate CfgOption to have to be aware
@@ -98,6 +108,12 @@ export declare class CfgOption {
98
108
  get rawOption(): Option;
99
109
  get key(): string;
100
110
  get code(): string;
111
+ get isUseNumericValue(): boolean;
112
+ get numericValue(): number | undefined;
113
+ setNumericValue: (val: number, doSelectOption: boolean) => Promise<boolean>;
114
+ get allowedNumericValues(): NumericValuesSelection | undefined;
115
+ isAllowedNumericValue: (val: number) => boolean;
116
+ get unit(): LengthUnit;
101
117
  get description(): string;
102
118
  get selected(): boolean;
103
119
  /** Are all ancestors up to the CfgProductConfiguration selected? Includes self. */
@@ -7,11 +7,12 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
7
7
  step((generator = generator.apply(thisArg, _arguments || [])).next());
8
8
  });
9
9
  };
10
- import { compareArrays, count, Observable } from "@configura/web-utilities";
10
+ import { compareArrays, count, Observable, } from "@configura/web-utilities";
11
11
  import { CfgProduct } from "../CfgProduct.js";
12
12
  import { CfgMtrlApplication } from "../material/CfgMtrlApplication.js";
13
13
  import { CfgMtrlApplicationSource } from "../material/CfgMtrlApplicationSource.js";
14
14
  import { recursivelyGetPriceCodeValue } from "../utilitiesCatalogueData.js";
15
+ import { NumericValuesSelection } from "../utilitiesNumericValues.js";
15
16
  import { CfgFeature, SelectionType } from "./CfgFeature.js";
16
17
  import { getMtrlPreview, syncCfgFeatures } from "./utilitiesProductConfiguration.js";
17
18
  export var ProductConfigurationBubbleMode;
@@ -50,6 +51,20 @@ export var ProductConfigurationBubbleMode;
50
51
  */
51
52
  ProductConfigurationBubbleMode["ToRoot"] = "ToRoot";
52
53
  })(ProductConfigurationBubbleMode || (ProductConfigurationBubbleMode = {}));
54
+ /** Overlap in one child also count */
55
+ function doesChildrenShareOptionsCode(features) {
56
+ const optionCodeSet = new Set();
57
+ for (const feature of features) {
58
+ for (const option of feature.options) {
59
+ const code = option.code;
60
+ if (optionCodeSet.has(code)) {
61
+ return true;
62
+ }
63
+ optionCodeSet.add(code);
64
+ }
65
+ }
66
+ return false;
67
+ }
53
68
  /**
54
69
  * This class is meant to only be used through CfgOption. It should
55
70
  * never be instantiated on its own. Normally the internal state of this class
@@ -57,10 +72,11 @@ export var ProductConfigurationBubbleMode;
57
72
  * should be used and interacted with.
58
73
  */
59
74
  export class _CfgOptionInternal {
60
- constructor(rawOption, parent, allRawFeatures, siblingHasDuplicateDescription, parentProduct, rootProduct) {
75
+ constructor(rawOption, allRawFeatures, siblingHasDuplicateDescription, parent, parentConfiguration, parentProduct, rootProduct) {
61
76
  this.rawOption = rawOption;
62
- this.parent = parent;
63
77
  this.allRawFeatures = allRawFeatures;
78
+ this.parent = parent;
79
+ this.parentConfiguration = parentConfiguration;
64
80
  this.parentProduct = parentProduct;
65
81
  this.rootProduct = rootProduct;
66
82
  this.changeObservable = new Observable();
@@ -77,19 +93,16 @@ export class _CfgOptionInternal {
77
93
  features[i] = freshRef;
78
94
  // In CfgOption we let stop bubble slip through. This is because CfgOption is sort of
79
95
  // a semi level. Like Feature + Option together constitutes one level.
80
- let change = false;
81
96
  if (bubbleMode === ProductConfigurationBubbleMode.ValidateAndBubbleSelected) {
82
- // If there is a change selectOption will take care of the bubble
83
- change = yield this.parent.selectOption(this, true, bubbleMode);
97
+ // selectOption takes care of the bubble
98
+ yield this.parent.selectOption(this, true, bubbleMode);
84
99
  }
85
- if (!change) {
86
- // There was no change, or we did not try validate, so the normal childHasChanged
87
- // takes over.
100
+ else {
88
101
  yield this.parent._childHasChanged(this, bubbleMode);
89
102
  }
90
103
  });
91
104
  this.getApiSelection = () => {
92
- const { features, code } = this;
105
+ const { features, isUseNumericValue, code, numericValue } = this;
93
106
  const selectionTrees = features.map((f) => f._internal.getApiSelection());
94
107
  const mergedSelectionTree = {};
95
108
  let anyItems = false;
@@ -106,8 +119,14 @@ export class _CfgOptionInternal {
106
119
  anyItems = true;
107
120
  }
108
121
  }
122
+ if (isUseNumericValue && numericValue === undefined) {
123
+ throw new Error("numeric Sku feature with option without numeric value");
124
+ }
109
125
  const selectedOption = {
110
- code: code,
126
+ code,
127
+ numericValue: isUseNumericValue && numericValue !== undefined
128
+ ? { value: numericValue, unit: this.unit }
129
+ : undefined,
111
130
  };
112
131
  if (anyItems) {
113
132
  selectedOption.next = mergedSelectionTree;
@@ -153,14 +172,76 @@ export class _CfgOptionInternal {
153
172
  }, []);
154
173
  // Description based key helps when switching between
155
174
  // products with similar feature-options tree and trying
156
- // to retain made selections
175
+ // to retain made selection
157
176
  this.key =
158
177
  this.description +
159
178
  (this.description === "" || siblingHasDuplicateDescription ? this.code : "");
179
+ const rawRanges = rawOption.codeRanges;
180
+ if (rawRanges === undefined || rawRanges.length === 0) {
181
+ if (this.isUseNumericValue) {
182
+ throw new Error("Options in numeric sku Features must have at lease one code range");
183
+ }
184
+ }
185
+ else {
186
+ this.allowedNumericValues = new NumericValuesSelection(rawRanges);
187
+ this._numericValue = this.allowedNumericValues.first;
188
+ }
189
+ }
190
+ isAllowedNumericValue(val) {
191
+ const allowedNumericValues = this.allowedNumericValues;
192
+ if (allowedNumericValues === undefined) {
193
+ return false;
194
+ }
195
+ if (this.isUseNumericValue) {
196
+ return allowedNumericValues.includesValue(val);
197
+ }
198
+ // There is some limited support for numeric values on non numeric features.
199
+ // The first value is always used in these cases.
200
+ return allowedNumericValues.first === val;
160
201
  }
161
202
  get code() {
162
203
  return this.rawOption.code;
163
204
  }
205
+ get isUseNumericValue() {
206
+ return this.parent.isUseNumericValue;
207
+ }
208
+ get numericValue() {
209
+ return this._numericValue;
210
+ }
211
+ setNumericValue(val, doSelectOption) {
212
+ return __awaiter(this, void 0, void 0, function* () {
213
+ if (!this.isAllowedNumericValue(val)) {
214
+ throw new Error(`The value ${val} is not allowed. This could be because: 1. There are no allowed ranges defined 2. The Feature is numeric selection and the value does not fit in the allowed values 3. The Feature is not numeric selection and the value is not the first in the allowed ranges.`);
215
+ }
216
+ let change = false;
217
+ if (this.isUseNumericValue) {
218
+ if (this._numericValue !== val) {
219
+ this._numericValue = val;
220
+ change = true;
221
+ }
222
+ // It could be that even though our value did not change some sibling value did, and this
223
+ // could make it needed to bubble later. Maybe. A bit uncertain about why I did not put
224
+ // this in the if-statement above //Linus
225
+ if (yield this.parent.pushStretch()) {
226
+ change = true;
227
+ }
228
+ }
229
+ if (doSelectOption) {
230
+ if (yield this.parent.selectOption(this, true, ProductConfigurationBubbleMode.Validate)) {
231
+ change = true;
232
+ }
233
+ }
234
+ else {
235
+ if (change) {
236
+ yield this.parent._childHasChanged(this, ProductConfigurationBubbleMode.OneLevel);
237
+ }
238
+ }
239
+ return change;
240
+ });
241
+ }
242
+ get unit() {
243
+ return this.parent.unit;
244
+ }
164
245
  get description() {
165
246
  return this.rawOption.description;
166
247
  }
@@ -200,10 +281,8 @@ export class _CfgOptionInternal {
200
281
  return 0;
201
282
  }
202
283
  const selectedOptions = this.parent.selectedOptions;
203
- if (1 < selectedOptions.length) {
204
- throw new Error(`More than one selected in select one. Option key: "${this.key}"`);
205
- }
206
- if (selectedOptions.length === 0) {
284
+ if (1 !== selectedOptions.length) {
285
+ console.warn("More or less than one selected in select one");
207
286
  return upcharge;
208
287
  }
209
288
  return upcharge - (selectedOptions[0].upcharge || 0);
@@ -213,7 +292,11 @@ export class _CfgOptionInternal {
213
292
  get features() {
214
293
  if (this._features === undefined) {
215
294
  const allRefs = this.rawOption.featureRefs || [];
216
- this._features = syncCfgFeatures(allRefs, [], this.allRawFeatures, this, this.parentProduct, this.rootProduct);
295
+ const features = syncCfgFeatures(allRefs, [], this.allRawFeatures, this, this.parentConfiguration, this.parentProduct, this.rootProduct);
296
+ if (doesChildrenShareOptionsCode(features)) {
297
+ throw new Error("Stage does not yet properly support Options that has multiple sub-features with overlapping option codes.");
298
+ }
299
+ this._features = features;
217
300
  }
218
301
  return this._features;
219
302
  }
@@ -235,6 +318,8 @@ export class CfgOption {
235
318
  constructor(_internal) {
236
319
  this._internal = _internal;
237
320
  this.isBackedBySame = (other) => this._internal === other._internal;
321
+ this.setNumericValue = (val, doSelectOption) => __awaiter(this, void 0, void 0, function* () { return yield this._internal.setNumericValue(val, doSelectOption); });
322
+ this.isAllowedNumericValue = (val) => this._internal.isAllowedNumericValue(val);
238
323
  /*
239
324
  * Selects this Option.
240
325
  * Only Options belonging to Features that are "select many" can be deselected.
@@ -246,6 +331,17 @@ export class CfgOption {
246
331
  this.listenForChange = (l) => this._internal.changeObservable.listen(l);
247
332
  this.stopListenForChange = (l) => this._internal.changeObservable.stopListen(l);
248
333
  }
334
+ static make(rawOption, allRawFeatures, siblingHasDuplicateDescription, parent, parentConfiguration, parentProduct, rootProduct) {
335
+ return new this(new _CfgOptionInternal(rawOption, allRawFeatures, siblingHasDuplicateDescription, parent, parentConfiguration, parentProduct, rootProduct));
336
+ }
337
+ /**
338
+ * Makes an object wrapping the passed object. This is not a clone method,
339
+ * it is a method to make a new outer reference. Like a shallow copy.
340
+ * We use this to help frameworks that are build around using equals to detect change.
341
+ */
342
+ static _makeNewRefFrom(internal) {
343
+ return new this(internal);
344
+ }
249
345
  get parentProduct() {
250
346
  return CfgProduct._makeNewRefFrom(this._internal.parentProduct);
251
347
  }
@@ -261,6 +357,18 @@ export class CfgOption {
261
357
  get code() {
262
358
  return this._internal.code;
263
359
  }
360
+ get isUseNumericValue() {
361
+ return this._internal.isUseNumericValue;
362
+ }
363
+ get numericValue() {
364
+ return this._internal.numericValue;
365
+ }
366
+ get allowedNumericValues() {
367
+ return this._internal.allowedNumericValues;
368
+ }
369
+ get unit() {
370
+ return this._internal.unit;
371
+ }
264
372
  get description() {
265
373
  return this._internal.description;
266
374
  }
@@ -284,10 +392,3 @@ export class CfgOption {
284
392
  return this._internal.features;
285
393
  }
286
394
  }
287
- CfgOption.make = (rawOption, parent, allRawFeatures, siblingHasDuplicateDescription, parentProduct, rootProduct) => new CfgOption(new _CfgOptionInternal(rawOption, parent, allRawFeatures, siblingHasDuplicateDescription, parentProduct, rootProduct));
288
- /**
289
- * Makes an object wrapping the passed object. This is not a clone method,
290
- * it is a method to make a new outer reference. Like a shallow copy.
291
- * We use this to help frameworks that are build around using equals to detect change.
292
- */
293
- CfgOption._makeNewRefFrom = (internal) => new CfgOption(internal);