@configura/web-api 2.2.0-alpha.1 → 2.2.0-alpha.3
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/.eslintrc.json +5 -5
- package/LICENSE +201 -201
- package/README.md +1 -1
- package/dist/CatalogueAPI.d.ts +633 -633
- package/dist/CatalogueAPI.js +312 -312
- package/dist/CfgMeasure.d.ts +32 -32
- package/dist/CfgMeasure.js +30 -30
- package/dist/CfgProduct.d.ts +359 -344
- package/dist/CfgProduct.js +1005 -992
- package/dist/CfgReferencePathHelper.d.ts +26 -26
- package/dist/CfgReferencePathHelper.js +26 -26
- package/dist/index.d.ts +24 -24
- package/dist/index.js +24 -24
- package/dist/io/CfgHistoryManager.d.ts +83 -83
- package/dist/io/CfgHistoryManager.js +144 -144
- package/dist/io/CfgHistoryToProdConfConnector.d.ts +21 -21
- package/dist/io/CfgHistoryToProdConfConnector.js +50 -50
- package/dist/io/CfgIOManager.d.ts +53 -53
- package/dist/io/CfgIOManager.js +134 -134
- package/dist/io/CfgIOProdConfConnector.d.ts +54 -54
- package/dist/io/CfgIOProdConfConnector.js +139 -139
- package/dist/io/CfgIOWarningSupplier.d.ts +3 -3
- package/dist/io/CfgIOWarningSupplier.js +1 -1
- package/dist/io/CfgObservableStateManager.d.ts +25 -25
- package/dist/io/CfgObservableStateManager.js +69 -69
- package/dist/io/CfgObservableStateToProdConfConnector.d.ts +15 -15
- package/dist/io/CfgObservableStateToProdConfConnector.js +17 -17
- package/dist/io/CfgWindowEventManager.d.ts +21 -21
- package/dist/io/CfgWindowEventManager.js +38 -38
- package/dist/io/CfgWindowMessageManager.d.ts +40 -40
- package/dist/io/CfgWindowMessageManager.js +91 -91
- package/dist/io/CfgWindowMessageToProdConfConnector.d.ts +17 -17
- package/dist/io/CfgWindowMessageToProdConfConnector.js +19 -19
- package/dist/io/index.d.ts +8 -8
- package/dist/io/index.js +8 -8
- package/dist/material/CfgMaterialMapping.d.ts +7 -7
- package/dist/material/CfgMaterialMapping.js +181 -181
- package/dist/material/CfgMtrlApplication.d.ts +18 -18
- package/dist/material/CfgMtrlApplication.js +43 -43
- package/dist/material/CfgMtrlApplicationSource.d.ts +7 -7
- package/dist/material/CfgMtrlApplicationSource.js +8 -8
- package/dist/material/CfgMtrlSource.d.ts +19 -19
- package/dist/material/CfgMtrlSource.js +40 -40
- package/dist/material/CfgMtrlSourceWithMetaData.d.ts +7 -7
- package/dist/material/CfgMtrlSourceWithMetaData.js +1 -1
- package/dist/productConfiguration/CfgFeature.d.ts +199 -199
- package/dist/productConfiguration/CfgFeature.js +691 -691
- package/dist/productConfiguration/CfgOption.d.ts +160 -160
- package/dist/productConfiguration/CfgOption.js +464 -464
- package/dist/productConfiguration/CfgProductConfiguration.d.ts +136 -129
- package/dist/productConfiguration/CfgProductConfiguration.js +355 -346
- package/dist/productConfiguration/filters.d.ts +17 -17
- package/dist/productConfiguration/filters.js +141 -141
- package/dist/productConfiguration/productParamsGenerator.d.ts +15 -15
- package/dist/productConfiguration/productParamsGenerator.js +65 -65
- package/dist/productConfiguration/utilitiesProductConfiguration.d.ts +17 -17
- package/dist/productConfiguration/utilitiesProductConfiguration.js +89 -87
- package/dist/productLoader.d.ts +33 -33
- package/dist/productLoader.js +49 -49
- package/dist/syncGroups/SyncGroupsApplyMode.d.ts +20 -20
- package/dist/syncGroups/SyncGroupsApplyMode.js +21 -21
- package/dist/syncGroups/SyncGroupsHandler.d.ts +47 -47
- package/dist/syncGroups/SyncGroupsHandler.js +370 -370
- package/dist/syncGroups/SyncGroupsPathHelper.d.ts +26 -26
- package/dist/syncGroups/SyncGroupsPathHelper.js +90 -90
- package/dist/syncGroups/SyncGroupsState.d.ts +39 -39
- package/dist/syncGroups/SyncGroupsState.js +167 -167
- package/dist/syncGroups/SyncGroupsTransaction.d.ts +154 -154
- package/dist/syncGroups/SyncGroupsTransaction.js +589 -589
- package/dist/tasks/TaskHandler.d.ts +77 -77
- package/dist/tasks/TaskHandler.js +276 -276
- package/dist/tasks/formats.d.ts +4 -4
- package/dist/tasks/formats.js +7 -7
- package/dist/tests/testData/collectorForTest.d.ts +73 -73
- package/dist/tests/testData/collectorForTest.js +194 -194
- package/dist/tests/testData/dummyProductForTest.d.ts +4 -4
- package/dist/tests/testData/dummyProductForTest.js +32 -32
- package/dist/tests/testData/testDataAdditionalProductInAdditionalProductInProductForTest.d.ts +11 -11
- package/dist/tests/testData/testDataAdditionalProductInAdditionalProductInProductForTest.js +282 -282
- package/dist/tests/testData/testDataCachedGetProduct.d.ts +5 -5
- package/dist/tests/testData/testDataCachedGetProduct.js +187 -187
- package/dist/tests/testData/testDataCachedPostValidate.d.ts +7 -7
- package/dist/tests/testData/testDataCachedPostValidate.js +185 -185
- package/dist/tests/testData/testDataConstraints.d.ts +3 -3
- package/dist/tests/testData/testDataConstraints.js +174 -174
- package/dist/tests/testData/testDataNoAdditionalProductNoPropagateForTest.d.ts +3 -3
- package/dist/tests/testData/testDataNoAdditionalProductNoPropagateForTest.js +1099 -1099
- package/dist/tests/testData/testDataOptions.d.ts +12 -12
- package/dist/tests/testData/testDataOptions.js +60 -60
- package/dist/tests/testData/testDataProductAggregatedPrice.d.ts +6 -6
- package/dist/tests/testData/testDataProductAggregatedPrice.js +189 -189
- package/dist/tests/testData/testDataUpcharge.d.ts +8 -8
- package/dist/tests/testData/testDataUpcharge.js +121 -121
- package/dist/utilitiesCatalogueData.d.ts +47 -47
- package/dist/utilitiesCatalogueData.js +180 -180
- package/dist/utilitiesCataloguePermission.d.ts +38 -38
- package/dist/utilitiesCataloguePermission.js +79 -79
- package/dist/utilitiesConfiguration.d.ts +28 -28
- package/dist/utilitiesConfiguration.js +200 -200
- package/dist/utilitiesNumericValues.d.ts +24 -24
- package/dist/utilitiesNumericValues.js +114 -114
- package/package.json +3 -3
|
@@ -1,464 +1,464 @@
|
|
|
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 { compareArrays, count, Observable, } from "@configura/web-utilities";
|
|
11
|
-
import { CfgProduct } from "../CfgProduct.js";
|
|
12
|
-
import { CfgMtrlApplication } from "../material/CfgMtrlApplication.js";
|
|
13
|
-
import { CfgMtrlApplicationSource } from "../material/CfgMtrlApplicationSource.js";
|
|
14
|
-
import { recursivelyGetPriceCodeValue } from "../utilitiesCatalogueData.js";
|
|
15
|
-
import { NumericValuesSelection } from "../utilitiesNumericValues.js";
|
|
16
|
-
import { CfgFeature, SelectionType } from "./CfgFeature.js";
|
|
17
|
-
import { getMtrlPreview, syncCfgFeatures } from "./utilitiesProductConfiguration.js";
|
|
18
|
-
export var ProductConfigurationBubbleMode;
|
|
19
|
-
(function (ProductConfigurationBubbleMode) {
|
|
20
|
-
/**
|
|
21
|
-
* If this is select it will turns on all ancestors all the way up.
|
|
22
|
-
*/
|
|
23
|
-
ProductConfigurationBubbleMode["BubbleSelected"] = "BubbleSelected";
|
|
24
|
-
/**
|
|
25
|
-
* Bubble to the closest CfgProduct, let it revalidate, then that will continue the bubble
|
|
26
|
-
* after validate.
|
|
27
|
-
*/
|
|
28
|
-
ProductConfigurationBubbleMode["Validate"] = "Validate";
|
|
29
|
-
/**
|
|
30
|
-
* Bubble to the closest CfgProduct, let it revalidate, then that will continue the bubble
|
|
31
|
-
* after validate. If this is select it will turn on all ancestors all the way up.
|
|
32
|
-
* So with this mode it is possible to select an option where its parents are not selected.
|
|
33
|
-
*/
|
|
34
|
-
ProductConfigurationBubbleMode["ValidateAndBubbleSelected"] = "ValidateAndBubbleSelected";
|
|
35
|
-
/**
|
|
36
|
-
* Like ValidateAndBubbleSelected, but SyncGroups are applied after ValidateAndBubbleSelected
|
|
37
|
-
* has been done
|
|
38
|
-
*/
|
|
39
|
-
ProductConfigurationBubbleMode["ValidateAndBubbleSelectedAndApplySyncGroups"] = "ValidateAndBubbleSelectedAndApplySyncGroups";
|
|
40
|
-
/**
|
|
41
|
-
* Stop bubbling
|
|
42
|
-
* This mode supports internal functionality and is not expected to be used by integrators.
|
|
43
|
-
*/
|
|
44
|
-
ProductConfigurationBubbleMode["Stop"] = "Stop";
|
|
45
|
-
/**
|
|
46
|
-
* Bubble to the next level up the tree. In features-options the next level is considered
|
|
47
|
-
* the next feature, so option levels are skipped over. The node we call from notifies its
|
|
48
|
-
* parent, and the parent switches out the reference to the node. Then bubbling stops.
|
|
49
|
-
* This mode supports internal functionality and is not expected to be used by integrators.
|
|
50
|
-
*/
|
|
51
|
-
ProductConfigurationBubbleMode["OneLevel"] = "OneLevel";
|
|
52
|
-
/**
|
|
53
|
-
* Bubble to the closest CfgProduct without doing any validation
|
|
54
|
-
* This mode supports internal functionality and is not expected to be used by integrators.
|
|
55
|
-
*/
|
|
56
|
-
ProductConfigurationBubbleMode["ToParentProduct"] = "ToParentProduct";
|
|
57
|
-
/**
|
|
58
|
-
* Bubble to the root CfgProduct
|
|
59
|
-
* This mode supports internal functionality and is not expected to be used by integrators.
|
|
60
|
-
*/
|
|
61
|
-
ProductConfigurationBubbleMode["ToRoot"] = "ToRoot";
|
|
62
|
-
})(ProductConfigurationBubbleMode || (ProductConfigurationBubbleMode = {}));
|
|
63
|
-
/** Overlap in one child also count */
|
|
64
|
-
function doesChildrenShareOptionsCode(features) {
|
|
65
|
-
const optionCodeSet = new Set();
|
|
66
|
-
for (const feature of features) {
|
|
67
|
-
for (const option of feature.options) {
|
|
68
|
-
const code = option.code;
|
|
69
|
-
if (optionCodeSet.has(code)) {
|
|
70
|
-
return true;
|
|
71
|
-
}
|
|
72
|
-
optionCodeSet.add(code);
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
return false;
|
|
76
|
-
}
|
|
77
|
-
/**
|
|
78
|
-
* This class is only meant to be used through CfgOption. It should never be instantiated on its
|
|
79
|
-
* own. Normally the internal state of this class should never be directly modified. CfgOption is
|
|
80
|
-
* the class that should be used and interacted with.
|
|
81
|
-
*/
|
|
82
|
-
export class _CfgOptionInternal {
|
|
83
|
-
constructor(rawOption, rawFeatures, siblingHasDuplicateDescription, parent, parentConfiguration, parentProduct, rootProduct) {
|
|
84
|
-
this.rawOption = rawOption;
|
|
85
|
-
this.rawFeatures = rawFeatures;
|
|
86
|
-
this.parent = parent;
|
|
87
|
-
this.parentConfiguration = parentConfiguration;
|
|
88
|
-
this.parentProduct = parentProduct;
|
|
89
|
-
this.rootProduct = rootProduct;
|
|
90
|
-
this.changeObservable = new Observable();
|
|
91
|
-
/** Called by child to tell its parent that it has changed. */
|
|
92
|
-
this._childHasChanged = (freshRef, bubbleMode, committed) => __awaiter(this, void 0, void 0, function* () {
|
|
93
|
-
const features = this._features;
|
|
94
|
-
if (features === undefined) {
|
|
95
|
-
throw Error("Child says it changed, but no children has actually been generated");
|
|
96
|
-
}
|
|
97
|
-
const i = features.findIndex((a) => a.isBackedBySame(freshRef));
|
|
98
|
-
if (i === -1) {
|
|
99
|
-
throw Error("Child feature not found");
|
|
100
|
-
}
|
|
101
|
-
features[i] = freshRef;
|
|
102
|
-
// In CfgOption we let stop bubble slip through. This is because CfgOption is sort of
|
|
103
|
-
// a semi level. Like Feature + Option together constitutes one level.
|
|
104
|
-
if (bubbleMode === ProductConfigurationBubbleMode.ValidateAndBubbleSelected ||
|
|
105
|
-
bubbleMode === ProductConfigurationBubbleMode.BubbleSelected) {
|
|
106
|
-
// selectOption takes care of the bubble
|
|
107
|
-
yield this.parent.selectOption(this, true, bubbleMode);
|
|
108
|
-
}
|
|
109
|
-
else {
|
|
110
|
-
yield this.parent._childHasChanged(this, bubbleMode, committed);
|
|
111
|
-
}
|
|
112
|
-
});
|
|
113
|
-
this.getDtoConf = (includeExtendedData) => {
|
|
114
|
-
const { features, isUseNumericValue, code, selected, numericValue } = this;
|
|
115
|
-
if (!selected) {
|
|
116
|
-
throw new Error("Currently only useable on selected options. Selected in the result is for future use.");
|
|
117
|
-
}
|
|
118
|
-
const result = {
|
|
119
|
-
code,
|
|
120
|
-
selected, // For future use
|
|
121
|
-
};
|
|
122
|
-
if (isUseNumericValue) {
|
|
123
|
-
if (numericValue === undefined) {
|
|
124
|
-
throw new Error("numeric Sku feature with option without numeric value");
|
|
125
|
-
}
|
|
126
|
-
else {
|
|
127
|
-
result.numericValue = { value: numericValue, unit: this.unit };
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
if (0 < features.length) {
|
|
131
|
-
result.features = features.map((f) => f._internal.getDtoConf(includeExtendedData));
|
|
132
|
-
}
|
|
133
|
-
return result;
|
|
134
|
-
};
|
|
135
|
-
// DtoOptionConf is the newer more easily readable format for configuration. As
|
|
136
|
-
// the API:s are still using the older format (and will for the forseeable future) we need
|
|
137
|
-
// to support both formats. The new format can be converted to the old, but not the other
|
|
138
|
-
// way around. For that reason the get-method above uses the new format, and the set-method
|
|
139
|
-
// below the old format. As these functions are meant to only be used internally this should't
|
|
140
|
-
// cause too much confusion.
|
|
141
|
-
this.setApiSelection = (apiOptionSelection, apiOptionConstraint) => __awaiter(this, void 0, void 0, function* () {
|
|
142
|
-
let change = false;
|
|
143
|
-
const upcharge = this._calculateUpcharge();
|
|
144
|
-
if (this._upcharge !== upcharge) {
|
|
145
|
-
change = true;
|
|
146
|
-
this._upcharge = upcharge;
|
|
147
|
-
}
|
|
148
|
-
let features;
|
|
149
|
-
if (apiOptionSelection === undefined) {
|
|
150
|
-
features = this._features || []; // All already generated children
|
|
151
|
-
}
|
|
152
|
-
else {
|
|
153
|
-
features = this.features; // This will generate all children
|
|
154
|
-
}
|
|
155
|
-
if ((yield Promise.all(features.map((f) => f._internal.setApiSelection(apiOptionSelection ? apiOptionSelection.next : undefined, apiOptionConstraint === null || apiOptionConstraint === void 0 ? void 0 : apiOptionConstraint.next)))).some((b) => b)) {
|
|
156
|
-
change = true;
|
|
157
|
-
}
|
|
158
|
-
return change;
|
|
159
|
-
});
|
|
160
|
-
this.getApiConstrained = (constrOptions) => {
|
|
161
|
-
var _a;
|
|
162
|
-
const next = {};
|
|
163
|
-
// use _features to avoid populating when the option is not selected
|
|
164
|
-
for (const feature of (_a = this._features) !== null && _a !== void 0 ? _a : []) {
|
|
165
|
-
feature._internal.addForApiConstrained(next);
|
|
166
|
-
}
|
|
167
|
-
return {
|
|
168
|
-
code: this.code,
|
|
169
|
-
feature: this.parent.code,
|
|
170
|
-
constrOptions: constrOptions.map((o) => o.code),
|
|
171
|
-
next,
|
|
172
|
-
};
|
|
173
|
-
};
|
|
174
|
-
this.structureCompare = (other, strictOrder = true, descriptionMatch = false) => this.keyMatch(other, descriptionMatch) &&
|
|
175
|
-
compareArrays(this.features, other.features, (l, r) => l._internal.structureCompare(r._internal, strictOrder, descriptionMatch), strictOrder);
|
|
176
|
-
this.tryMatchSelection = (other, descriptionMatch = false) => __awaiter(this, void 0, void 0, function* () {
|
|
177
|
-
return (yield Promise.all(other.features.map((otherF) => (() => __awaiter(this, void 0, void 0, function* () {
|
|
178
|
-
if (1 <
|
|
179
|
-
count(other.features, (item) => item._internal.keyMatch(otherF._internal, descriptionMatch))) {
|
|
180
|
-
console.warn("tryMatchSelection will ignore items with same key");
|
|
181
|
-
return false;
|
|
182
|
-
}
|
|
183
|
-
const toSetFeatures = this.features.filter((f) => f._internal.keyMatch(otherF._internal, descriptionMatch));
|
|
184
|
-
if (1 < toSetFeatures.length) {
|
|
185
|
-
console.warn("tryMatchSelection will ignore items with same key");
|
|
186
|
-
return false;
|
|
187
|
-
}
|
|
188
|
-
if (toSetFeatures.length === 0) {
|
|
189
|
-
return false;
|
|
190
|
-
}
|
|
191
|
-
return yield toSetFeatures[0]._internal.tryMatchSelection(otherF._internal, descriptionMatch);
|
|
192
|
-
}))()))).some((b) => b);
|
|
193
|
-
});
|
|
194
|
-
this.keyMatch = (other, descriptionMatch = false) => descriptionMatch
|
|
195
|
-
? this.description.toLowerCase() === other.description.toLowerCase()
|
|
196
|
-
: this.code === other.code;
|
|
197
|
-
this._getFeaturesWithCode = (code) => this.features.reduce((agg, feature) => {
|
|
198
|
-
agg.push(...feature._internal._getFeaturesWithCode(code));
|
|
199
|
-
return agg;
|
|
200
|
-
}, []);
|
|
201
|
-
// Description based key helps when switching between products with similar feature-options
|
|
202
|
-
// tree and trying to retain made selection.
|
|
203
|
-
this.key =
|
|
204
|
-
this.description +
|
|
205
|
-
(this.description === "" || siblingHasDuplicateDescription ? this.code : "");
|
|
206
|
-
this._upcharge = this._calculateUpcharge();
|
|
207
|
-
const rawRanges = rawOption.codeRanges;
|
|
208
|
-
if (rawRanges === undefined || rawRanges.length === 0) {
|
|
209
|
-
if (this.isUseNumericValue) {
|
|
210
|
-
throw new Error("Options in numeric sku Features must have at lease one code range");
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
else {
|
|
214
|
-
this.allowedNumericValues = new NumericValuesSelection(rawRanges);
|
|
215
|
-
this._numericValue = this.allowedNumericValues.first;
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
isAllowedNumericValue(val) {
|
|
219
|
-
const allowedNumericValues = this.allowedNumericValues;
|
|
220
|
-
if (allowedNumericValues === undefined) {
|
|
221
|
-
return false;
|
|
222
|
-
}
|
|
223
|
-
if (this.isUseNumericValue) {
|
|
224
|
-
return allowedNumericValues.includesValue(val);
|
|
225
|
-
}
|
|
226
|
-
// There is some limited support for numeric values on non numeric features.
|
|
227
|
-
// The first value is always used in these cases.
|
|
228
|
-
return allowedNumericValues.first === val;
|
|
229
|
-
}
|
|
230
|
-
get code() {
|
|
231
|
-
return this.rawOption.code;
|
|
232
|
-
}
|
|
233
|
-
get notes() {
|
|
234
|
-
var _a;
|
|
235
|
-
return this.parentProduct.getNotes((_a = this.rawOption.noteRefs) !== null && _a !== void 0 ? _a : []);
|
|
236
|
-
}
|
|
237
|
-
get miscFiles() {
|
|
238
|
-
var _a;
|
|
239
|
-
return (_a = this.rawOption.miscFiles) !== null && _a !== void 0 ? _a : [];
|
|
240
|
-
}
|
|
241
|
-
get isUseNumericValue() {
|
|
242
|
-
return this.parent.isUseNumericValue;
|
|
243
|
-
}
|
|
244
|
-
get numericValue() {
|
|
245
|
-
return this._numericValue;
|
|
246
|
-
}
|
|
247
|
-
setNumericValue(val, doSelectOption) {
|
|
248
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
249
|
-
if (!this.isAllowedNumericValue(val)) {
|
|
250
|
-
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.`);
|
|
251
|
-
}
|
|
252
|
-
let change = false;
|
|
253
|
-
if (this.isUseNumericValue) {
|
|
254
|
-
if (this._numericValue !== val) {
|
|
255
|
-
this._numericValue = val;
|
|
256
|
-
change = true;
|
|
257
|
-
}
|
|
258
|
-
// It could be that even though our value did not change some sibling value did, and
|
|
259
|
-
// this could make it needed to bubble later. Maybe. A bit uncertain about why I did
|
|
260
|
-
// not put this in the if-statement above //Linus
|
|
261
|
-
if (yield this.parent.pushStretch()) {
|
|
262
|
-
change = true;
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
if (doSelectOption) {
|
|
266
|
-
if (yield this.parent.selectOption(this, true, ProductConfigurationBubbleMode.Validate)) {
|
|
267
|
-
change = true;
|
|
268
|
-
}
|
|
269
|
-
}
|
|
270
|
-
else {
|
|
271
|
-
if (change) {
|
|
272
|
-
yield this.parent._childHasChanged(this, ProductConfigurationBubbleMode.Stop, false);
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
return change;
|
|
276
|
-
});
|
|
277
|
-
}
|
|
278
|
-
get unit() {
|
|
279
|
-
return this.parent.unit;
|
|
280
|
-
}
|
|
281
|
-
get description() {
|
|
282
|
-
return this.rawOption.description;
|
|
283
|
-
}
|
|
284
|
-
get selected() {
|
|
285
|
-
return this.parent.isSelected(this);
|
|
286
|
-
}
|
|
287
|
-
get disabled() {
|
|
288
|
-
return this.parent.isDisabled(this);
|
|
289
|
-
}
|
|
290
|
-
get selectedChangeInProgress() {
|
|
291
|
-
const syncGroupHandler = this.rootProduct.syncGroupHandler;
|
|
292
|
-
if (syncGroupHandler === undefined) {
|
|
293
|
-
return false;
|
|
294
|
-
}
|
|
295
|
-
const inProgressOption = syncGroupHandler.pending;
|
|
296
|
-
if (inProgressOption === this) {
|
|
297
|
-
return true;
|
|
298
|
-
}
|
|
299
|
-
if (!(this.selected && this.parent.selectionType === SelectionType.SelectOne)) {
|
|
300
|
-
return false;
|
|
301
|
-
}
|
|
302
|
-
return this.parent.options.some((o) => o._internal === inProgressOption);
|
|
303
|
-
}
|
|
304
|
-
get ancestorsSelected() {
|
|
305
|
-
return this.selected && this.parent.ancestorsSelected;
|
|
306
|
-
}
|
|
307
|
-
get mtrlApplications() {
|
|
308
|
-
if (this._mtrlApplications === undefined) {
|
|
309
|
-
this._mtrlApplications = (this.rawOption.mtrlApplications || []).map((m) => CfgMtrlApplication.fromMtrlLikeApplication(CfgMtrlApplicationSource.Option, m));
|
|
310
|
-
}
|
|
311
|
-
return this._mtrlApplications;
|
|
312
|
-
}
|
|
313
|
-
get thumbnail() {
|
|
314
|
-
return this.rawOption.material || getMtrlPreview(this.mtrlApplications);
|
|
315
|
-
}
|
|
316
|
-
_calculateUpcharge() {
|
|
317
|
-
let upcharge = this.rawOption.upcharge || 0;
|
|
318
|
-
const priceCodes = this.rawOption.priceCodes || [];
|
|
319
|
-
const prices = this.rootProduct.prices;
|
|
320
|
-
upcharge += recursivelyGetPriceCodeValue(priceCodes, prices) || 0;
|
|
321
|
-
return upcharge;
|
|
322
|
-
}
|
|
323
|
-
get upcharge() {
|
|
324
|
-
return this._upcharge;
|
|
325
|
-
}
|
|
326
|
-
get priceChangeAtSelectChange() {
|
|
327
|
-
if (!this.parent.hasUpcharge) {
|
|
328
|
-
return 0;
|
|
329
|
-
}
|
|
330
|
-
const upcharge = this._upcharge || 0;
|
|
331
|
-
const isSelected = this.parent.isSelected(this);
|
|
332
|
-
if (this.parent.selectionType === SelectionType.SelectMany) {
|
|
333
|
-
return isSelected ? -upcharge : upcharge;
|
|
334
|
-
}
|
|
335
|
-
if (this.parent.selectionType === SelectionType.SelectOne) {
|
|
336
|
-
if (isSelected) {
|
|
337
|
-
return 0;
|
|
338
|
-
}
|
|
339
|
-
const selectedOptions = this.parent.selectedOptions;
|
|
340
|
-
if (1 !== selectedOptions.length) {
|
|
341
|
-
console.warn("More or less than one selected in select one");
|
|
342
|
-
return upcharge;
|
|
343
|
-
}
|
|
344
|
-
return upcharge - (selectedOptions[0].upcharge || 0);
|
|
345
|
-
}
|
|
346
|
-
return 0;
|
|
347
|
-
}
|
|
348
|
-
get features() {
|
|
349
|
-
if (this._features === undefined) {
|
|
350
|
-
const allRefs = this.rawOption.featureRefs || [];
|
|
351
|
-
const features = syncCfgFeatures(allRefs, [], this.rawFeatures, this, this.parentConfiguration, this.parentProduct, this.rootProduct);
|
|
352
|
-
if (doesChildrenShareOptionsCode(features)) {
|
|
353
|
-
throw new Error("Stage does not yet properly support Options that has multiple sub-features with overlapping option codes.");
|
|
354
|
-
}
|
|
355
|
-
this._features = features;
|
|
356
|
-
}
|
|
357
|
-
return this._features;
|
|
358
|
-
}
|
|
359
|
-
}
|
|
360
|
-
export class CfgOption {
|
|
361
|
-
/**
|
|
362
|
-
* Private constructor and make-method because make new ref requires the constructor to
|
|
363
|
-
* take an internal and we don't want those who instantiate CfgOption to have to be aware
|
|
364
|
-
* of the internal.
|
|
365
|
-
*/
|
|
366
|
-
constructor(_internal) {
|
|
367
|
-
this._internal = _internal;
|
|
368
|
-
this.isBackedBySame = (other) => this._internal === other._internal;
|
|
369
|
-
this.setNumericValue = (val, doSelectOption) => __awaiter(this, void 0, void 0, function* () { return yield this._internal.setNumericValue(val, doSelectOption); });
|
|
370
|
-
this.isAllowedNumericValue = (val) => this._internal.isAllowedNumericValue(val);
|
|
371
|
-
/**
|
|
372
|
-
* Selects this Option.
|
|
373
|
-
* Only Options belonging to Features that are "select many" can be deselected.
|
|
374
|
-
* Calling this will cause a validation call to the server.
|
|
375
|
-
*/
|
|
376
|
-
this.setSelected = (on) => __awaiter(this, void 0, void 0, function* () {
|
|
377
|
-
return yield this._internal.parent.selectOption(this._internal, on, ProductConfigurationBubbleMode.ValidateAndBubbleSelectedAndApplySyncGroups);
|
|
378
|
-
});
|
|
379
|
-
this.listenForChange = (l) => this._internal.changeObservable.listen(l);
|
|
380
|
-
this.stopListenForChange = (l) => this._internal.changeObservable.stopListen(l);
|
|
381
|
-
}
|
|
382
|
-
static make(rawOption, rawFeatures, siblingHasDuplicateDescription, parent, parentConfiguration, parentProduct, rootProduct) {
|
|
383
|
-
return new this(new _CfgOptionInternal(rawOption, rawFeatures, siblingHasDuplicateDescription, parent, parentConfiguration, parentProduct, rootProduct));
|
|
384
|
-
}
|
|
385
|
-
/**
|
|
386
|
-
* Makes an object wrapping the passed object. This is not a clone method,
|
|
387
|
-
* it is a method to make a new outer reference. Like a shallow copy.
|
|
388
|
-
* We use this to help frameworks that are build around using equals to detect change.
|
|
389
|
-
*/
|
|
390
|
-
static _makeNewRefFrom(internal) {
|
|
391
|
-
return new this(internal);
|
|
392
|
-
}
|
|
393
|
-
get parentProduct() {
|
|
394
|
-
return CfgProduct._makeNewRefFrom(this._internal.parentProduct);
|
|
395
|
-
}
|
|
396
|
-
get parent() {
|
|
397
|
-
return CfgFeature._makeNewRefFrom(this._internal.parent);
|
|
398
|
-
}
|
|
399
|
-
get rootProduct() {
|
|
400
|
-
return CfgProduct._makeNewRefFrom(this._internal.rootProduct);
|
|
401
|
-
}
|
|
402
|
-
get rawOption() {
|
|
403
|
-
return this._internal.rawOption;
|
|
404
|
-
}
|
|
405
|
-
get key() {
|
|
406
|
-
return this._internal.key;
|
|
407
|
-
}
|
|
408
|
-
get code() {
|
|
409
|
-
return this._internal.code;
|
|
410
|
-
}
|
|
411
|
-
get notes() {
|
|
412
|
-
return this._internal.notes;
|
|
413
|
-
}
|
|
414
|
-
get miscFiles() {
|
|
415
|
-
return this._internal.miscFiles;
|
|
416
|
-
}
|
|
417
|
-
get isUseNumericValue() {
|
|
418
|
-
return this._internal.isUseNumericValue;
|
|
419
|
-
}
|
|
420
|
-
get numericValue() {
|
|
421
|
-
return this._internal.numericValue;
|
|
422
|
-
}
|
|
423
|
-
get allowedNumericValues() {
|
|
424
|
-
return this._internal.allowedNumericValues;
|
|
425
|
-
}
|
|
426
|
-
get unit() {
|
|
427
|
-
return this._internal.unit;
|
|
428
|
-
}
|
|
429
|
-
get description() {
|
|
430
|
-
return this._internal.description;
|
|
431
|
-
}
|
|
432
|
-
get selected() {
|
|
433
|
-
return this._internal.selected;
|
|
434
|
-
}
|
|
435
|
-
get disabled() {
|
|
436
|
-
return this._internal.disabled;
|
|
437
|
-
}
|
|
438
|
-
/**
|
|
439
|
-
* Selection state is in progress to be changed. This can be used in GUI
|
|
440
|
-
* to display the state as transitioning, or as already changed.
|
|
441
|
-
* If selectedChangeInProgress and:
|
|
442
|
-
* selected is true, it means that this is about to get unselected
|
|
443
|
-
* selected is false, it means that this is about to get selected
|
|
444
|
-
*/
|
|
445
|
-
get selectedChangeInProgress() {
|
|
446
|
-
return this._internal.selectedChangeInProgress;
|
|
447
|
-
}
|
|
448
|
-
/** Are all ancestors up to the CfgProductConfiguration selected? Includes self. */
|
|
449
|
-
get ancestorsSelected() {
|
|
450
|
-
return this._internal.ancestorsSelected;
|
|
451
|
-
}
|
|
452
|
-
get thumbnail() {
|
|
453
|
-
return this._internal.thumbnail;
|
|
454
|
-
}
|
|
455
|
-
get upcharge() {
|
|
456
|
-
return this._internal.upcharge;
|
|
457
|
-
}
|
|
458
|
-
get priceChangeAtSelectChange() {
|
|
459
|
-
return this._internal.priceChangeAtSelectChange;
|
|
460
|
-
}
|
|
461
|
-
get features() {
|
|
462
|
-
return this._internal.features;
|
|
463
|
-
}
|
|
464
|
-
}
|
|
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 { compareArrays, count, Observable, } from "@configura/web-utilities";
|
|
11
|
+
import { CfgProduct } from "../CfgProduct.js";
|
|
12
|
+
import { CfgMtrlApplication } from "../material/CfgMtrlApplication.js";
|
|
13
|
+
import { CfgMtrlApplicationSource } from "../material/CfgMtrlApplicationSource.js";
|
|
14
|
+
import { recursivelyGetPriceCodeValue } from "../utilitiesCatalogueData.js";
|
|
15
|
+
import { NumericValuesSelection } from "../utilitiesNumericValues.js";
|
|
16
|
+
import { CfgFeature, SelectionType } from "./CfgFeature.js";
|
|
17
|
+
import { getMtrlPreview, syncCfgFeatures } from "./utilitiesProductConfiguration.js";
|
|
18
|
+
export var ProductConfigurationBubbleMode;
|
|
19
|
+
(function (ProductConfigurationBubbleMode) {
|
|
20
|
+
/**
|
|
21
|
+
* If this is select it will turns on all ancestors all the way up.
|
|
22
|
+
*/
|
|
23
|
+
ProductConfigurationBubbleMode["BubbleSelected"] = "BubbleSelected";
|
|
24
|
+
/**
|
|
25
|
+
* Bubble to the closest CfgProduct, let it revalidate, then that will continue the bubble
|
|
26
|
+
* after validate.
|
|
27
|
+
*/
|
|
28
|
+
ProductConfigurationBubbleMode["Validate"] = "Validate";
|
|
29
|
+
/**
|
|
30
|
+
* Bubble to the closest CfgProduct, let it revalidate, then that will continue the bubble
|
|
31
|
+
* after validate. If this is select it will turn on all ancestors all the way up.
|
|
32
|
+
* So with this mode it is possible to select an option where its parents are not selected.
|
|
33
|
+
*/
|
|
34
|
+
ProductConfigurationBubbleMode["ValidateAndBubbleSelected"] = "ValidateAndBubbleSelected";
|
|
35
|
+
/**
|
|
36
|
+
* Like ValidateAndBubbleSelected, but SyncGroups are applied after ValidateAndBubbleSelected
|
|
37
|
+
* has been done
|
|
38
|
+
*/
|
|
39
|
+
ProductConfigurationBubbleMode["ValidateAndBubbleSelectedAndApplySyncGroups"] = "ValidateAndBubbleSelectedAndApplySyncGroups";
|
|
40
|
+
/**
|
|
41
|
+
* Stop bubbling
|
|
42
|
+
* This mode supports internal functionality and is not expected to be used by integrators.
|
|
43
|
+
*/
|
|
44
|
+
ProductConfigurationBubbleMode["Stop"] = "Stop";
|
|
45
|
+
/**
|
|
46
|
+
* Bubble to the next level up the tree. In features-options the next level is considered
|
|
47
|
+
* the next feature, so option levels are skipped over. The node we call from notifies its
|
|
48
|
+
* parent, and the parent switches out the reference to the node. Then bubbling stops.
|
|
49
|
+
* This mode supports internal functionality and is not expected to be used by integrators.
|
|
50
|
+
*/
|
|
51
|
+
ProductConfigurationBubbleMode["OneLevel"] = "OneLevel";
|
|
52
|
+
/**
|
|
53
|
+
* Bubble to the closest CfgProduct without doing any validation
|
|
54
|
+
* This mode supports internal functionality and is not expected to be used by integrators.
|
|
55
|
+
*/
|
|
56
|
+
ProductConfigurationBubbleMode["ToParentProduct"] = "ToParentProduct";
|
|
57
|
+
/**
|
|
58
|
+
* Bubble to the root CfgProduct
|
|
59
|
+
* This mode supports internal functionality and is not expected to be used by integrators.
|
|
60
|
+
*/
|
|
61
|
+
ProductConfigurationBubbleMode["ToRoot"] = "ToRoot";
|
|
62
|
+
})(ProductConfigurationBubbleMode || (ProductConfigurationBubbleMode = {}));
|
|
63
|
+
/** Overlap in one child also count */
|
|
64
|
+
function doesChildrenShareOptionsCode(features) {
|
|
65
|
+
const optionCodeSet = new Set();
|
|
66
|
+
for (const feature of features) {
|
|
67
|
+
for (const option of feature.options) {
|
|
68
|
+
const code = option.code;
|
|
69
|
+
if (optionCodeSet.has(code)) {
|
|
70
|
+
return true;
|
|
71
|
+
}
|
|
72
|
+
optionCodeSet.add(code);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* This class is only meant to be used through CfgOption. It should never be instantiated on its
|
|
79
|
+
* own. Normally the internal state of this class should never be directly modified. CfgOption is
|
|
80
|
+
* the class that should be used and interacted with.
|
|
81
|
+
*/
|
|
82
|
+
export class _CfgOptionInternal {
|
|
83
|
+
constructor(rawOption, rawFeatures, siblingHasDuplicateDescription, parent, parentConfiguration, parentProduct, rootProduct) {
|
|
84
|
+
this.rawOption = rawOption;
|
|
85
|
+
this.rawFeatures = rawFeatures;
|
|
86
|
+
this.parent = parent;
|
|
87
|
+
this.parentConfiguration = parentConfiguration;
|
|
88
|
+
this.parentProduct = parentProduct;
|
|
89
|
+
this.rootProduct = rootProduct;
|
|
90
|
+
this.changeObservable = new Observable();
|
|
91
|
+
/** Called by child to tell its parent that it has changed. */
|
|
92
|
+
this._childHasChanged = (freshRef, bubbleMode, committed) => __awaiter(this, void 0, void 0, function* () {
|
|
93
|
+
const features = this._features;
|
|
94
|
+
if (features === undefined) {
|
|
95
|
+
throw Error("Child says it changed, but no children has actually been generated");
|
|
96
|
+
}
|
|
97
|
+
const i = features.findIndex((a) => a.isBackedBySame(freshRef));
|
|
98
|
+
if (i === -1) {
|
|
99
|
+
throw Error("Child feature not found");
|
|
100
|
+
}
|
|
101
|
+
features[i] = freshRef;
|
|
102
|
+
// In CfgOption we let stop bubble slip through. This is because CfgOption is sort of
|
|
103
|
+
// a semi level. Like Feature + Option together constitutes one level.
|
|
104
|
+
if (bubbleMode === ProductConfigurationBubbleMode.ValidateAndBubbleSelected ||
|
|
105
|
+
bubbleMode === ProductConfigurationBubbleMode.BubbleSelected) {
|
|
106
|
+
// selectOption takes care of the bubble
|
|
107
|
+
yield this.parent.selectOption(this, true, bubbleMode);
|
|
108
|
+
}
|
|
109
|
+
else {
|
|
110
|
+
yield this.parent._childHasChanged(this, bubbleMode, committed);
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
this.getDtoConf = (includeExtendedData) => {
|
|
114
|
+
const { features, isUseNumericValue, code, selected, numericValue } = this;
|
|
115
|
+
if (!selected) {
|
|
116
|
+
throw new Error("Currently only useable on selected options. Selected in the result is for future use.");
|
|
117
|
+
}
|
|
118
|
+
const result = {
|
|
119
|
+
code,
|
|
120
|
+
selected, // For future use
|
|
121
|
+
};
|
|
122
|
+
if (isUseNumericValue) {
|
|
123
|
+
if (numericValue === undefined) {
|
|
124
|
+
throw new Error("numeric Sku feature with option without numeric value");
|
|
125
|
+
}
|
|
126
|
+
else {
|
|
127
|
+
result.numericValue = { value: numericValue, unit: this.unit };
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
if (0 < features.length) {
|
|
131
|
+
result.features = features.map((f) => f._internal.getDtoConf(includeExtendedData));
|
|
132
|
+
}
|
|
133
|
+
return result;
|
|
134
|
+
};
|
|
135
|
+
// DtoOptionConf is the newer more easily readable format for configuration. As
|
|
136
|
+
// the API:s are still using the older format (and will for the forseeable future) we need
|
|
137
|
+
// to support both formats. The new format can be converted to the old, but not the other
|
|
138
|
+
// way around. For that reason the get-method above uses the new format, and the set-method
|
|
139
|
+
// below the old format. As these functions are meant to only be used internally this should't
|
|
140
|
+
// cause too much confusion.
|
|
141
|
+
this.setApiSelection = (apiOptionSelection, apiOptionConstraint) => __awaiter(this, void 0, void 0, function* () {
|
|
142
|
+
let change = false;
|
|
143
|
+
const upcharge = this._calculateUpcharge();
|
|
144
|
+
if (this._upcharge !== upcharge) {
|
|
145
|
+
change = true;
|
|
146
|
+
this._upcharge = upcharge;
|
|
147
|
+
}
|
|
148
|
+
let features;
|
|
149
|
+
if (apiOptionSelection === undefined) {
|
|
150
|
+
features = this._features || []; // All already generated children
|
|
151
|
+
}
|
|
152
|
+
else {
|
|
153
|
+
features = this.features; // This will generate all children
|
|
154
|
+
}
|
|
155
|
+
if ((yield Promise.all(features.map((f) => f._internal.setApiSelection(apiOptionSelection ? apiOptionSelection.next : undefined, apiOptionConstraint === null || apiOptionConstraint === void 0 ? void 0 : apiOptionConstraint.next)))).some((b) => b)) {
|
|
156
|
+
change = true;
|
|
157
|
+
}
|
|
158
|
+
return change;
|
|
159
|
+
});
|
|
160
|
+
this.getApiConstrained = (constrOptions) => {
|
|
161
|
+
var _a;
|
|
162
|
+
const next = {};
|
|
163
|
+
// use _features to avoid populating when the option is not selected
|
|
164
|
+
for (const feature of (_a = this._features) !== null && _a !== void 0 ? _a : []) {
|
|
165
|
+
feature._internal.addForApiConstrained(next);
|
|
166
|
+
}
|
|
167
|
+
return {
|
|
168
|
+
code: this.code,
|
|
169
|
+
feature: this.parent.code,
|
|
170
|
+
constrOptions: constrOptions.map((o) => o.code),
|
|
171
|
+
next,
|
|
172
|
+
};
|
|
173
|
+
};
|
|
174
|
+
this.structureCompare = (other, strictOrder = true, descriptionMatch = false) => this.keyMatch(other, descriptionMatch) &&
|
|
175
|
+
compareArrays(this.features, other.features, (l, r) => l._internal.structureCompare(r._internal, strictOrder, descriptionMatch), strictOrder);
|
|
176
|
+
this.tryMatchSelection = (other, descriptionMatch = false) => __awaiter(this, void 0, void 0, function* () {
|
|
177
|
+
return (yield Promise.all(other.features.map((otherF) => (() => __awaiter(this, void 0, void 0, function* () {
|
|
178
|
+
if (1 <
|
|
179
|
+
count(other.features, (item) => item._internal.keyMatch(otherF._internal, descriptionMatch))) {
|
|
180
|
+
console.warn("tryMatchSelection will ignore items with same key");
|
|
181
|
+
return false;
|
|
182
|
+
}
|
|
183
|
+
const toSetFeatures = this.features.filter((f) => f._internal.keyMatch(otherF._internal, descriptionMatch));
|
|
184
|
+
if (1 < toSetFeatures.length) {
|
|
185
|
+
console.warn("tryMatchSelection will ignore items with same key");
|
|
186
|
+
return false;
|
|
187
|
+
}
|
|
188
|
+
if (toSetFeatures.length === 0) {
|
|
189
|
+
return false;
|
|
190
|
+
}
|
|
191
|
+
return yield toSetFeatures[0]._internal.tryMatchSelection(otherF._internal, descriptionMatch);
|
|
192
|
+
}))()))).some((b) => b);
|
|
193
|
+
});
|
|
194
|
+
this.keyMatch = (other, descriptionMatch = false) => descriptionMatch
|
|
195
|
+
? this.description.toLowerCase() === other.description.toLowerCase()
|
|
196
|
+
: this.code === other.code;
|
|
197
|
+
this._getFeaturesWithCode = (code) => this.features.reduce((agg, feature) => {
|
|
198
|
+
agg.push(...feature._internal._getFeaturesWithCode(code));
|
|
199
|
+
return agg;
|
|
200
|
+
}, []);
|
|
201
|
+
// Description based key helps when switching between products with similar feature-options
|
|
202
|
+
// tree and trying to retain made selection.
|
|
203
|
+
this.key =
|
|
204
|
+
this.description +
|
|
205
|
+
(this.description === "" || siblingHasDuplicateDescription ? this.code : "");
|
|
206
|
+
this._upcharge = this._calculateUpcharge();
|
|
207
|
+
const rawRanges = rawOption.codeRanges;
|
|
208
|
+
if (rawRanges === undefined || rawRanges.length === 0) {
|
|
209
|
+
if (this.isUseNumericValue) {
|
|
210
|
+
throw new Error("Options in numeric sku Features must have at lease one code range");
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
else {
|
|
214
|
+
this.allowedNumericValues = new NumericValuesSelection(rawRanges);
|
|
215
|
+
this._numericValue = this.allowedNumericValues.first;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
isAllowedNumericValue(val) {
|
|
219
|
+
const allowedNumericValues = this.allowedNumericValues;
|
|
220
|
+
if (allowedNumericValues === undefined) {
|
|
221
|
+
return false;
|
|
222
|
+
}
|
|
223
|
+
if (this.isUseNumericValue) {
|
|
224
|
+
return allowedNumericValues.includesValue(val);
|
|
225
|
+
}
|
|
226
|
+
// There is some limited support for numeric values on non numeric features.
|
|
227
|
+
// The first value is always used in these cases.
|
|
228
|
+
return allowedNumericValues.first === val;
|
|
229
|
+
}
|
|
230
|
+
get code() {
|
|
231
|
+
return this.rawOption.code;
|
|
232
|
+
}
|
|
233
|
+
get notes() {
|
|
234
|
+
var _a;
|
|
235
|
+
return this.parentProduct.getNotes((_a = this.rawOption.noteRefs) !== null && _a !== void 0 ? _a : []);
|
|
236
|
+
}
|
|
237
|
+
get miscFiles() {
|
|
238
|
+
var _a;
|
|
239
|
+
return (_a = this.rawOption.miscFiles) !== null && _a !== void 0 ? _a : [];
|
|
240
|
+
}
|
|
241
|
+
get isUseNumericValue() {
|
|
242
|
+
return this.parent.isUseNumericValue;
|
|
243
|
+
}
|
|
244
|
+
get numericValue() {
|
|
245
|
+
return this._numericValue;
|
|
246
|
+
}
|
|
247
|
+
setNumericValue(val, doSelectOption) {
|
|
248
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
249
|
+
if (!this.isAllowedNumericValue(val)) {
|
|
250
|
+
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.`);
|
|
251
|
+
}
|
|
252
|
+
let change = false;
|
|
253
|
+
if (this.isUseNumericValue) {
|
|
254
|
+
if (this._numericValue !== val) {
|
|
255
|
+
this._numericValue = val;
|
|
256
|
+
change = true;
|
|
257
|
+
}
|
|
258
|
+
// It could be that even though our value did not change some sibling value did, and
|
|
259
|
+
// this could make it needed to bubble later. Maybe. A bit uncertain about why I did
|
|
260
|
+
// not put this in the if-statement above //Linus
|
|
261
|
+
if (yield this.parent.pushStretch()) {
|
|
262
|
+
change = true;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
if (doSelectOption) {
|
|
266
|
+
if (yield this.parent.selectOption(this, true, ProductConfigurationBubbleMode.Validate)) {
|
|
267
|
+
change = true;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
else {
|
|
271
|
+
if (change) {
|
|
272
|
+
yield this.parent._childHasChanged(this, ProductConfigurationBubbleMode.Stop, false);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
return change;
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
get unit() {
|
|
279
|
+
return this.parent.unit;
|
|
280
|
+
}
|
|
281
|
+
get description() {
|
|
282
|
+
return this.rawOption.description;
|
|
283
|
+
}
|
|
284
|
+
get selected() {
|
|
285
|
+
return this.parent.isSelected(this);
|
|
286
|
+
}
|
|
287
|
+
get disabled() {
|
|
288
|
+
return this.parent.isDisabled(this);
|
|
289
|
+
}
|
|
290
|
+
get selectedChangeInProgress() {
|
|
291
|
+
const syncGroupHandler = this.rootProduct.syncGroupHandler;
|
|
292
|
+
if (syncGroupHandler === undefined) {
|
|
293
|
+
return false;
|
|
294
|
+
}
|
|
295
|
+
const inProgressOption = syncGroupHandler.pending;
|
|
296
|
+
if (inProgressOption === this) {
|
|
297
|
+
return true;
|
|
298
|
+
}
|
|
299
|
+
if (!(this.selected && this.parent.selectionType === SelectionType.SelectOne)) {
|
|
300
|
+
return false;
|
|
301
|
+
}
|
|
302
|
+
return this.parent.options.some((o) => o._internal === inProgressOption);
|
|
303
|
+
}
|
|
304
|
+
get ancestorsSelected() {
|
|
305
|
+
return this.selected && this.parent.ancestorsSelected;
|
|
306
|
+
}
|
|
307
|
+
get mtrlApplications() {
|
|
308
|
+
if (this._mtrlApplications === undefined) {
|
|
309
|
+
this._mtrlApplications = (this.rawOption.mtrlApplications || []).map((m) => CfgMtrlApplication.fromMtrlLikeApplication(CfgMtrlApplicationSource.Option, m));
|
|
310
|
+
}
|
|
311
|
+
return this._mtrlApplications;
|
|
312
|
+
}
|
|
313
|
+
get thumbnail() {
|
|
314
|
+
return this.rawOption.material || getMtrlPreview(this.mtrlApplications);
|
|
315
|
+
}
|
|
316
|
+
_calculateUpcharge() {
|
|
317
|
+
let upcharge = this.rawOption.upcharge || 0;
|
|
318
|
+
const priceCodes = this.rawOption.priceCodes || [];
|
|
319
|
+
const prices = this.rootProduct.prices;
|
|
320
|
+
upcharge += recursivelyGetPriceCodeValue(priceCodes, prices) || 0;
|
|
321
|
+
return upcharge;
|
|
322
|
+
}
|
|
323
|
+
get upcharge() {
|
|
324
|
+
return this._upcharge;
|
|
325
|
+
}
|
|
326
|
+
get priceChangeAtSelectChange() {
|
|
327
|
+
if (!this.parent.hasUpcharge) {
|
|
328
|
+
return 0;
|
|
329
|
+
}
|
|
330
|
+
const upcharge = this._upcharge || 0;
|
|
331
|
+
const isSelected = this.parent.isSelected(this);
|
|
332
|
+
if (this.parent.selectionType === SelectionType.SelectMany) {
|
|
333
|
+
return isSelected ? -upcharge : upcharge;
|
|
334
|
+
}
|
|
335
|
+
if (this.parent.selectionType === SelectionType.SelectOne) {
|
|
336
|
+
if (isSelected) {
|
|
337
|
+
return 0;
|
|
338
|
+
}
|
|
339
|
+
const selectedOptions = this.parent.selectedOptions;
|
|
340
|
+
if (1 !== selectedOptions.length) {
|
|
341
|
+
console.warn("More or less than one selected in select one");
|
|
342
|
+
return upcharge;
|
|
343
|
+
}
|
|
344
|
+
return upcharge - (selectedOptions[0].upcharge || 0);
|
|
345
|
+
}
|
|
346
|
+
return 0;
|
|
347
|
+
}
|
|
348
|
+
get features() {
|
|
349
|
+
if (this._features === undefined) {
|
|
350
|
+
const allRefs = this.rawOption.featureRefs || [];
|
|
351
|
+
const features = syncCfgFeatures(allRefs, [], this.rawFeatures, this, this.parentConfiguration, this.parentProduct, this.rootProduct);
|
|
352
|
+
if (doesChildrenShareOptionsCode(features)) {
|
|
353
|
+
throw new Error("Stage does not yet properly support Options that has multiple sub-features with overlapping option codes.");
|
|
354
|
+
}
|
|
355
|
+
this._features = features;
|
|
356
|
+
}
|
|
357
|
+
return this._features;
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
export class CfgOption {
|
|
361
|
+
/**
|
|
362
|
+
* Private constructor and make-method because make new ref requires the constructor to
|
|
363
|
+
* take an internal and we don't want those who instantiate CfgOption to have to be aware
|
|
364
|
+
* of the internal.
|
|
365
|
+
*/
|
|
366
|
+
constructor(_internal) {
|
|
367
|
+
this._internal = _internal;
|
|
368
|
+
this.isBackedBySame = (other) => this._internal === other._internal;
|
|
369
|
+
this.setNumericValue = (val, doSelectOption) => __awaiter(this, void 0, void 0, function* () { return yield this._internal.setNumericValue(val, doSelectOption); });
|
|
370
|
+
this.isAllowedNumericValue = (val) => this._internal.isAllowedNumericValue(val);
|
|
371
|
+
/**
|
|
372
|
+
* Selects this Option.
|
|
373
|
+
* Only Options belonging to Features that are "select many" can be deselected.
|
|
374
|
+
* Calling this will cause a validation call to the server.
|
|
375
|
+
*/
|
|
376
|
+
this.setSelected = (on) => __awaiter(this, void 0, void 0, function* () {
|
|
377
|
+
return yield this._internal.parent.selectOption(this._internal, on, ProductConfigurationBubbleMode.ValidateAndBubbleSelectedAndApplySyncGroups);
|
|
378
|
+
});
|
|
379
|
+
this.listenForChange = (l) => this._internal.changeObservable.listen(l);
|
|
380
|
+
this.stopListenForChange = (l) => this._internal.changeObservable.stopListen(l);
|
|
381
|
+
}
|
|
382
|
+
static make(rawOption, rawFeatures, siblingHasDuplicateDescription, parent, parentConfiguration, parentProduct, rootProduct) {
|
|
383
|
+
return new this(new _CfgOptionInternal(rawOption, rawFeatures, siblingHasDuplicateDescription, parent, parentConfiguration, parentProduct, rootProduct));
|
|
384
|
+
}
|
|
385
|
+
/**
|
|
386
|
+
* Makes an object wrapping the passed object. This is not a clone method,
|
|
387
|
+
* it is a method to make a new outer reference. Like a shallow copy.
|
|
388
|
+
* We use this to help frameworks that are build around using equals to detect change.
|
|
389
|
+
*/
|
|
390
|
+
static _makeNewRefFrom(internal) {
|
|
391
|
+
return new this(internal);
|
|
392
|
+
}
|
|
393
|
+
get parentProduct() {
|
|
394
|
+
return CfgProduct._makeNewRefFrom(this._internal.parentProduct);
|
|
395
|
+
}
|
|
396
|
+
get parent() {
|
|
397
|
+
return CfgFeature._makeNewRefFrom(this._internal.parent);
|
|
398
|
+
}
|
|
399
|
+
get rootProduct() {
|
|
400
|
+
return CfgProduct._makeNewRefFrom(this._internal.rootProduct);
|
|
401
|
+
}
|
|
402
|
+
get rawOption() {
|
|
403
|
+
return this._internal.rawOption;
|
|
404
|
+
}
|
|
405
|
+
get key() {
|
|
406
|
+
return this._internal.key;
|
|
407
|
+
}
|
|
408
|
+
get code() {
|
|
409
|
+
return this._internal.code;
|
|
410
|
+
}
|
|
411
|
+
get notes() {
|
|
412
|
+
return this._internal.notes;
|
|
413
|
+
}
|
|
414
|
+
get miscFiles() {
|
|
415
|
+
return this._internal.miscFiles;
|
|
416
|
+
}
|
|
417
|
+
get isUseNumericValue() {
|
|
418
|
+
return this._internal.isUseNumericValue;
|
|
419
|
+
}
|
|
420
|
+
get numericValue() {
|
|
421
|
+
return this._internal.numericValue;
|
|
422
|
+
}
|
|
423
|
+
get allowedNumericValues() {
|
|
424
|
+
return this._internal.allowedNumericValues;
|
|
425
|
+
}
|
|
426
|
+
get unit() {
|
|
427
|
+
return this._internal.unit;
|
|
428
|
+
}
|
|
429
|
+
get description() {
|
|
430
|
+
return this._internal.description;
|
|
431
|
+
}
|
|
432
|
+
get selected() {
|
|
433
|
+
return this._internal.selected;
|
|
434
|
+
}
|
|
435
|
+
get disabled() {
|
|
436
|
+
return this._internal.disabled;
|
|
437
|
+
}
|
|
438
|
+
/**
|
|
439
|
+
* Selection state is in progress to be changed. This can be used in GUI
|
|
440
|
+
* to display the state as transitioning, or as already changed.
|
|
441
|
+
* If selectedChangeInProgress and:
|
|
442
|
+
* selected is true, it means that this is about to get unselected
|
|
443
|
+
* selected is false, it means that this is about to get selected
|
|
444
|
+
*/
|
|
445
|
+
get selectedChangeInProgress() {
|
|
446
|
+
return this._internal.selectedChangeInProgress;
|
|
447
|
+
}
|
|
448
|
+
/** Are all ancestors up to the CfgProductConfiguration selected? Includes self. */
|
|
449
|
+
get ancestorsSelected() {
|
|
450
|
+
return this._internal.ancestorsSelected;
|
|
451
|
+
}
|
|
452
|
+
get thumbnail() {
|
|
453
|
+
return this._internal.thumbnail;
|
|
454
|
+
}
|
|
455
|
+
get upcharge() {
|
|
456
|
+
return this._internal.upcharge;
|
|
457
|
+
}
|
|
458
|
+
get priceChangeAtSelectChange() {
|
|
459
|
+
return this._internal.priceChangeAtSelectChange;
|
|
460
|
+
}
|
|
461
|
+
get features() {
|
|
462
|
+
return this._internal.features;
|
|
463
|
+
}
|
|
464
|
+
}
|