@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.
- package/dist/CatalogueAPI.d.ts +55 -1
- package/dist/CfgMeasure.d.ts +33 -0
- package/dist/CfgMeasure.js +30 -0
- package/dist/CfgProduct.d.ts +106 -5
- package/dist/CfgProduct.js +117 -71
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/material/CfgMaterialMapping.js +11 -6
- package/dist/material/CfgMtrlApplication.js +4 -4
- package/dist/productConfiguration/CfgFeature.d.ts +30 -4
- package/dist/productConfiguration/CfgFeature.js +162 -52
- package/dist/productConfiguration/CfgOption.d.ts +21 -5
- package/dist/productConfiguration/CfgOption.js +125 -24
- package/dist/productConfiguration/CfgProductConfiguration.d.ts +74 -4
- package/dist/productConfiguration/CfgProductConfiguration.js +158 -49
- package/dist/productConfiguration/utilitiesProductConfiguration.d.ts +9 -1
- package/dist/productConfiguration/utilitiesProductConfiguration.js +32 -13
- package/dist/productLoader.d.ts +22 -0
- package/dist/productLoader.js +22 -14
- package/dist/tests/testData/dummyProductForTest.js +1 -0
- package/dist/tests/testData/testDataAdditionalProductInAdditionalProductInProductForTest.js +13 -0
- package/dist/tests/testData/testDataCachedGetProduct.js +6 -0
- package/dist/tests/testData/testDataCachedPostValidate.js +6 -0
- package/dist/tests/testData/testDataNoAdditionalProductNoPropagateForTest.js +2 -0
- package/dist/tests/testData/testDataProductAggregatedPrice.js +6 -1
- package/dist/tests/testData/testDataUpcharge.js +4 -0
- package/dist/utilitiesCatalogueData.d.ts +5 -1
- package/dist/utilitiesCatalogueData.js +9 -1
- package/dist/utilitiesNumericValues.d.ts +25 -0
- package/dist/utilitiesNumericValues.js +109 -0
- 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
|
|
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
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
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
|
-
|
|
232
|
-
(yield
|
|
233
|
-
|
|
248
|
+
else {
|
|
249
|
+
if (yield this.setNumericValue(otherNumericValue)) {
|
|
250
|
+
change = true;
|
|
251
|
+
}
|
|
234
252
|
}
|
|
235
|
-
|
|
236
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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,
|
|
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
|
|
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
|
|
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,
|
|
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
|
-
//
|
|
83
|
-
|
|
97
|
+
// selectOption takes care of the bubble
|
|
98
|
+
yield this.parent.selectOption(this, true, bubbleMode);
|
|
84
99
|
}
|
|
85
|
-
|
|
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
|
|
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
|
|
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
|
|
204
|
-
|
|
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
|
-
|
|
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);
|