@nuskin/ns-product-lib 1.5.0-test-only.1 → 2.0.0-cx24-2382.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,9 +1,29 @@
1
- # [1.5.0-test-only.1](https://code.tls.nuskin.io/ns-am/product/js-libs/ns-product-lib/compare/v1.4.5...v1.5.0-test-only.1) (2022-08-11)
1
+ # [2.0.0-cx24-2382.1](https://code.tls.nuskin.io/ns-am/product/js-libs/ns-product-lib/compare/v1.6.0-cx24-2382.1...v2.0.0-cx24-2382.1) (2022-09-06)
2
+
3
+
4
+ ### Breaking
5
+
6
+ * replace runConfig and marketConfig parameters of getProductData with locale and market (CX24-2382) ([713bbb6](https://code.tls.nuskin.io/ns-am/product/js-libs/ns-product-lib/commit/713bbb6b52e20727ce846d4df5c84d837fb931d5))
7
+
8
+ ### New
9
+
10
+ * add equinox-enabled market validation (CX24-2382) ([6633eb5](https://code.tls.nuskin.io/ns-am/product/js-libs/ns-product-lib/commit/6633eb5824ec621a7316aff401d29876761ffc38))
11
+
12
+ # [1.6.0-cx24-2382.1](https://code.tls.nuskin.io/ns-am/product/js-libs/ns-product-lib/compare/v1.5.0...v1.6.0-cx24-2382.1) (2022-09-01)
13
+
14
+
15
+ ### New
16
+
17
+ * query equinox-enabled markets (CX24-2382) ([1fcd191](https://code.tls.nuskin.io/ns-am/product/js-libs/ns-product-lib/commit/1fcd191d654b5ad39603140e80dddd06ab7d0056))
18
+
19
+ # [1.5.0](https://code.tls.nuskin.io/ns-am/product/js-libs/ns-product-lib/compare/v1.4.5...v1.5.0) (2022-08-25)
2
20
 
3
21
 
4
22
  ### New
5
23
 
6
- * Get Product Data from Equinox (#CX24-1810) ([cdd8d7e](https://code.tls.nuskin.io/ns-am/product/js-libs/ns-product-lib/commit/cdd8d7e8eeaab866ee0cbbdf11a29215ca40ce02)), closes [#CX24-1810](https://code.tls.nuskin.io/ns-am/product/js-libs/ns-product-lib/issues/CX24-1810)
24
+ * get Product data from equinox (#CX24) ([9e4a93f](https://code.tls.nuskin.io/ns-am/product/js-libs/ns-product-lib/commit/9e4a93fd0cc24069a9f0fa81f309e606b2123523)), closes [#CX24](https://code.tls.nuskin.io/ns-am/product/js-libs/ns-product-lib/issues/CX24)
25
+ * get Product data from equinox (#CX24) ([2679c94](https://code.tls.nuskin.io/ns-am/product/js-libs/ns-product-lib/commit/2679c941d1bf59eb0ad506f0bf83e9c7f1a3d10e)), closes [#CX24](https://code.tls.nuskin.io/ns-am/product/js-libs/ns-product-lib/issues/CX24)
26
+ * Get Product Data from Equinox (CX24-1810, CX24-2121) ([50f0bcc](https://code.tls.nuskin.io/ns-am/product/js-libs/ns-product-lib/commit/50f0bcc7131fae0302abd9932c8d05bc1879ae11))
7
27
 
8
28
  ## [1.4.5](https://code.tls.nuskin.io/ns-am/product/js-libs/ns-product-lib/compare/v1.4.4...v1.4.5) (2022-07-28)
9
29
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nuskin/ns-product-lib",
3
- "version": "1.5.0-test-only.1",
3
+ "version": "2.0.0-cx24-2382.1",
4
4
  "description": "This project contains shared Product models and code between the backend and frontend.",
5
5
  "main": "src/index.js",
6
6
  "scripts": {
@@ -30,7 +30,7 @@
30
30
  },
31
31
  "dependencies": {
32
32
  "@nuskin/ns-common-lib": "1.3.0",
33
- "@nuskin/ns-util": "3.104.0-CX24.1",
33
+ "@nuskin/ns-util": "3.104.0",
34
34
  "axios": "0.27.2",
35
35
  "qs": "6.11.0"
36
36
  },
@@ -0,0 +1,121 @@
1
+ // This module can be bundled into a package for better reusability.
2
+ const axios = require('axios');
3
+ const { getEnvironmentFromUrl } = require('@nuskin/ns-common-lib');
4
+
5
+ /**
6
+ * getAEMConfig fetches the market config from contentstack.
7
+ *
8
+ * @typedef MarketConfig
9
+ * @property {string} aws_url
10
+ * @property {string} checkout_client_id
11
+ * @property {string} checkout_client_secret
12
+ *
13
+ * @export
14
+ * @return {Promise<null|MarketConfig>}
15
+ */
16
+ async function getAEMConfig() {
17
+ try {
18
+ const env = getEnvironmentFromUrl('test.nuskin.com');
19
+ const marketConfig = `market_config_${env}`;
20
+ const variables = { uid: 'blt52b0b83a89763791' };
21
+ const query = `query EquinoxMarketsByUid($uid: String!) {
22
+ aem_config(uid: $uid) {
23
+ title
24
+ ${marketConfig} {
25
+ checkout_client_secret
26
+ checkout_client_id
27
+ aws_url
28
+ }
29
+ }
30
+ }`;
31
+
32
+ const { data } = await contentstackService({
33
+ method: 'post',
34
+ data: { variables, query }
35
+ });
36
+
37
+ return data.aem_config[marketConfig];
38
+ } catch (error) {
39
+ console.error(error);
40
+ return null;
41
+ }
42
+ }
43
+
44
+ /**
45
+ * getEquinoxMarkets will get the list of Equinox-enabled markets from contentstack
46
+ *
47
+ * @typedef EquinoxEnabledMarket
48
+ * @property {string} market_name
49
+ * @property {string} country_code
50
+ * @property {string} store_id
51
+ *
52
+ * @typedef EquinoxEnabledMarkets
53
+ * @property {EquinoxEnabledMarket[]} [dev_markets]
54
+ * @property {EquinoxEnabledMarket[]} [test_markets]
55
+ * @property {EquinoxEnabledMarket[]} [staging_markets]
56
+ * @property {EquinoxEnabledMarket[]} [prod_markets]
57
+ *
58
+ * @export
59
+ * @return {Promise<EquinoxEnabledMarkets>}
60
+ */
61
+ async function getEquinoxMarkets() {
62
+ try {
63
+ const env = getEnvironmentFromUrl('test.nuskin.com');
64
+ const marketConfig = `${env}_markets`;
65
+ const variables = { uid: 'blt5633ff9e560f14ef' };
66
+ const query = `query EquinoxMarketsByUid($uid: String!) {
67
+ equinox_markets(uid: $uid) {
68
+ ${marketConfig} {
69
+ market_name
70
+ country_code
71
+ store_id
72
+ }
73
+ }
74
+ }`;
75
+
76
+ const { data } = await contentstackService({
77
+ method: 'post',
78
+ data: { variables, query }
79
+ });
80
+
81
+ return data.equinox_markets[marketConfig];
82
+ } catch (error) {
83
+ console.error(error);
84
+ return [];
85
+ }
86
+ }
87
+
88
+
89
+
90
+ /**
91
+ * Contentstack HTTP - axios wrapper for contentstack request
92
+ *
93
+ * @param {Object} config - required param
94
+ * @return {Promise<import('axios').AxiosResponse>} Promise that resolves to market_list_eq object
95
+ */
96
+ async function contentstackService(config) {
97
+ const baseURL = 'https://graphql.contentstack.com';
98
+ const deliveryToken = 'cs74599c107573ea7f6c7a1413';
99
+ const apiKey = 'blt7d4c4f4a1bf5a819';
100
+ const env = 'public-dev';
101
+ const defaultUrl = `${baseURL}/stacks/${apiKey}?environment=${env}`;
102
+
103
+ config.method = config.method || 'get'
104
+ config.url = config.url || defaultUrl
105
+ config.headers = {
106
+ 'access_token': `${deliveryToken}`,
107
+ 'Content-Type': 'application/json'
108
+ }
109
+
110
+ try {
111
+ const response = await axios(config);
112
+ return response.data
113
+ } catch (error) {
114
+ console.error(error.response.data.errors);
115
+ }
116
+ }
117
+
118
+ module.exports = {
119
+ getAEMConfig,
120
+ getEquinoxMarkets
121
+ }
package/src/index.js CHANGED
@@ -13,7 +13,6 @@ const ProductStatus = require("./productStatus.js");
13
13
  const ProductStatusMapper = require("./productStatusMapper.js");
14
14
  const ProductUtils = require("./productUtils.js");
15
15
  const ProductData = require("./productData.js");
16
- const EqProduct = require("./eqProduct.js");
17
16
 
18
17
  module.exports = {
19
18
  Agelocme,
@@ -23,6 +22,5 @@ module.exports = {
23
22
  ProductStatus,
24
23
  ProductStatusMapper,
25
24
  ProductUtils,
26
- ProductData,
27
- EqProduct
25
+ ProductData
28
26
  };
@@ -1,58 +1,104 @@
1
1
  "use strict";
2
- const axios = require("axios");
3
- const product = require("./product")
2
+ const axios = require("axios");
3
+ const contentstack = require('./contentstack/contentstack');
4
+ const product = require("./product");
4
5
 
6
+ /** @type {*} */
5
7
  const ProductData = {
6
- getProductData: async function (skus, runConfig, marketConfig, isEqEnabled) {
7
- let productResponse = {};
8
- let locale = `${runConfig.language}_${runConfig.country}`;
9
- if (isEqEnabled == true) {
10
- let filter = '{"filters":[{"field":"index_key_skuId","operation":"IN","value":"' +
11
- skus.toString() +
12
- '"}]}'
13
- axios.defaults.withCredentials = true;
14
- let productDataResponse = await axios.request({
15
- method: 'get',
16
- url: `https://storefront.api.wts.nuskin.io/orchestrationservices/storefront/catalogs/search/`,
17
- params: {
18
- locale: locale,
19
- storeId: '406',
20
- filter: filter
21
- },
22
- headers: this.getProductDataRequestHeaders(marketConfig),
23
- responseType: "json"
24
- });
25
- productResponse = this.eqProductMapper(productDataResponse.data.product)
26
-
27
- } else {
28
- let lambdaUrl = `${marketConfig.awsUrl}/productData/v1`;
29
- const payload = {
30
- locale: locale,
31
- skus: skus
32
- };
33
- productResponse = await axios.request({
34
- method: "post",
35
- url: lambdaUrl,
36
- data: payload,
37
- headers: this.getProductDataRequestHeaders(marketConfig),
38
- responseType: "json"
39
- });
8
+ /**
9
+ * @param {*} skus
10
+ * @param {*} runConfig
11
+ * @param {*} marketConfig
12
+ * @param {boolean} isEquinoxEnabled
13
+ */
14
+ getProductData: async function (skus, locale, market, isEquinoxEnabled = false) {
15
+ const localeMarket = `${locale}_${market}`;
16
+ const enabledMarkets = await contentstack.getEquinoxMarkets();
17
+ const isMarketEnabled = enabledMarkets
18
+ .map(m => m.country_code.toLowerCase())
19
+ .includes(market.toLowerCase());
20
+
21
+ if (isEquinoxEnabled && isMarketEnabled) {
22
+ return await this.getProductFromEquinox(skus, localeMarket, 406);
23
+ }
24
+
25
+ return await this.getProductFromLegacy(skus, localeMarket);
26
+ },
27
+
28
+ getProductFromEquinox: async function (skus, locale, storeID) {
29
+ const filter = '{"filters":[{"field":"index_key_skuId","operation":"IN","value":"' + skus.toString() + '"}]}';
30
+ axios.defaults.withCredentials = true;
31
+ const url = `https://storefront.api.wde.nuskin.io/orchestrationservices/storefront/catalogs/search/`;
32
+ const href = `${url}?filter=${encodeURI(filter)}`;
33
+ const response = await axios.request({
34
+ method: 'get',
35
+ url: href,
36
+ params: {
37
+ locale,
38
+ storeId: storeID
39
+ },
40
+ headers: this.getEquinoxRequestHeaders(),
41
+ responseType: 'json'
42
+ });
43
+
44
+ if (response.data.product) {
45
+ return this.eqProductMapper(response.data.product);
46
+ }
47
+
48
+ return {
49
+ status: 400,
50
+ messages: [response.data.message],
51
+ data: { count: 0, products: [] }
40
52
  }
53
+ },
41
54
 
42
- return productResponse;
55
+ getEquinoxRequestHeaders() {
56
+ return {
57
+ authorization: `Bearer ${localStorage.getItem('equinox-okta-token')}`,
58
+ 'Content-Type': 'application/json',
59
+ 'x-sk-session-id': localStorage.getItem('x-sk-session-id')
60
+ };
43
61
  },
44
62
 
45
- /* eqProductMapper: function(productDataResponse) {
46
- return productDataResponse;
47
- }, */
63
+ getProductFromLegacy: async function (skus, locale) {
64
+ const marketConfig = await contentstack.getAEMConfig();
65
+
66
+ if (marketConfig === null) {
67
+ return null;
68
+ }
69
+
70
+ const lambdaUrl = `${marketConfig.aws_url}/productData/v1`;
71
+ const payload = { locale, skus };
72
+
73
+ return await axios.request({
74
+ method: 'post',
75
+ url: lambdaUrl,
76
+ data: payload,
77
+ headers: this.getLegacyRequestHeaders(marketConfig),
78
+ responseType: 'json'
79
+ });
80
+ },
81
+
82
+ /**
83
+ * @param {contentstack.MarketConfig} marketConfig
84
+ */
85
+ getLegacyRequestHeaders: function(marketConfig) {
86
+ return {
87
+ 'Content-Type': 'application/json',
88
+ client_id: marketConfig.checkout_client_id,
89
+ client_secret: marketConfig.checkout_client_secret
90
+ };
91
+ },
48
92
 
49
93
  eqProductMapper: function (productDataResponse) {
50
94
  let prodArr = [];
51
95
  let prod = [];
52
96
  let count = 0;
97
+
98
+ // we can use Array.map for this one
53
99
  productDataResponse.forEach((data, index) => {
54
100
  count++
55
-
101
+
56
102
  prod = {
57
103
  "sku": data.sku[0].identifier,
58
104
  "globalProductID": data.identifier,
@@ -175,15 +221,6 @@ const ProductData = {
175
221
  "messages": [],
176
222
  "data": data
177
223
  }
178
- }
179
-
180
- },
181
-
182
- getProductDataRequestHeaders: function (marketConfig) {
183
- return {
184
- "Content-Type": "application/json",
185
- client_id: marketConfig.checkout.clientId,
186
- client_secret: marketConfig.checkout.clientSecret
187
224
  };
188
225
  }
189
226
  }
package/src/eqProduct.js DELETED
@@ -1,812 +0,0 @@
1
- /* eslint-disable no-invalid-this */
2
- "use strict";
3
-
4
- const { isTrue } = require("@nuskin/ns-common-lib");
5
-
6
- const ProductUtils = require("./productUtils.js");
7
-
8
- const Agelocme = require("./agelocme.js");
9
- const PriceType = require("./priceType.js");
10
- const ProductStatus = require("./productStatus.js");
11
-
12
- const eqProduct = function(productData) {
13
- this.priceMap = {};
14
- this.pvMap = {};
15
- this.cvMap = {};
16
- this.orderTypes = {};
17
- this.childSkus = [];
18
- this.custTypes = [];
19
- this.addOns = [];
20
- this.restrictedMarkets = [];
21
- this.productCarouselImages = [];
22
- this.locallyProduced = false;
23
-
24
- //class variables to be used instead of getters/setters
25
- this.globalProductID = "";
26
- this.sku = "";
27
- this.title = "";
28
- this.country = "";
29
- this.lang = "";
30
- this.shortDescr = "";
31
- this.longDescr = "";
32
- this.sizeWeight = "";
33
- this.nettoWeight = "";
34
- this.productLabels = {};
35
- this.count = "";
36
- this.flavor = "";
37
- this.shade = "";
38
- this.size = "";
39
- this.variantType = "";
40
- this.variantDropdownLabel = "";
41
- this.variantDropdownPlaceholder = "";
42
- this.variantsLabel = "";
43
- this.ingredients = "";
44
- this.benefits = "";
45
- this.usage = "";
46
- this.resources = "";
47
- this.contentSection = "";
48
- this.videos = "";
49
- this.personalOffer = false;
50
- this.groupOffer = false;
51
- this.scanQualified;
52
- this.status = ProductStatus.ReleasedForSale;
53
- this.availableQuantity = 0; // total available quantity
54
- this.maxQuantity = 999; // max quantity allowed for purchase
55
- this.priceType = null;
56
- this.division = "";
57
- this.backOrderDate = null;
58
- this.baseSku = "";
59
- this.variants = {};
60
- this.eventName = null; // set if pricing is based off of an event
61
- // Used to remember an event that was used for pricing in case ADR checkbox was clicked where
62
- // event pricing is not available. If the event was active when the product was added then
63
- // then the event can be used for the duration of the cart.
64
- this.savedEventName = null;
65
- this.salesLabel = "";
66
- this.searchScore = 0;
67
- this.isExclusive = false;
68
- this.movie = "";
69
- this.youtube = "";
70
- this.marketAttributes = {};
71
- this.salesEventText = "";
72
-
73
- // agelocme stuff
74
- this.agelocme = null; // object containing agelocme information (like code, label, name)
75
-
76
- this.setMarketAttributes = function(productStatus) {
77
- if (productStatus.marketAttributes) {
78
- this.marketAttributes = productStatus.marketAttributes;
79
- }
80
- };
81
-
82
- this.getAgelocmeKeyPart = function() {
83
- let keyPart = null;
84
- if (this.agelocme) {
85
- keyPart =
86
- this.agelocme.code +
87
- "-" +
88
- this.agelocme.label +
89
- "-" +
90
- this.agelocme.name;
91
- }
92
-
93
- return keyPart;
94
- };
95
-
96
- this.getHighlightedSku = function() {
97
- let rv = this.sku;
98
- if (this.highlightedSku) rv = this.highlightedSku;
99
- return rv;
100
- };
101
-
102
- this.getProductSearchTitle = function() {
103
- if (this.agelocme && this.agelocme.label) {
104
- return this.agelocme.label;
105
- }
106
- return this.title;
107
- };
108
-
109
- this.getProductSearchSku = function() {
110
- if (this.agelocme && this.agelocme.code) {
111
- return this.agelocme.code;
112
- }
113
- return this.getHighlightedSku();
114
- };
115
-
116
- this.getDescription = function() {
117
- let description = this.longDescr;
118
- if (!description || description.length === 0) {
119
- return this.shortDescr;
120
- }
121
- return description;
122
- };
123
-
124
- this.setFullImage = function(fullImage) {
125
- if (fullImage) {
126
- this.fullImage = fullImage.replace("http:", "https:");
127
- } else {
128
- this.fullImage = "";
129
- }
130
- };
131
-
132
- this.getFullImage = function() {
133
- return this.fullImage ? this.fullImage.replace("http:", "https:") : "";
134
- };
135
-
136
- this.setProductCarouselImages = function(productCarouselImages) {
137
- this.productCarouselImages = productCarouselImages;
138
- };
139
-
140
- this.getProductCarouselImages = function() {
141
- return this.productCarouselImages;
142
- };
143
-
144
- this.setImageAltText = function(altText) {
145
- if (altText) {
146
- this.imageAltText = altText;
147
- } else {
148
- this.imageAltText = this.title;
149
- }
150
- };
151
-
152
- this.getImageAltText = function() {
153
- return this.imageAltText || this.title;
154
- };
155
-
156
- this.setThumbnail = function(thumbnail) {
157
- if (thumbnail) {
158
- this.thumbnail = thumbnail.replace("http:", "https:");
159
- } else {
160
- this.thumbnail = "";
161
- }
162
- };
163
-
164
- this.setThumbnailFromSku = function(sku) {
165
- // WARNING: Base URL will need to be handled in the client (call setBaseUrl)
166
- // eslint-disable-next-line max-len
167
- // this.thumbnail = `${RunConfigService.getBaseUrl()}/content/products/${ProductUtils.getTokenizedSku(sku)}/jcr:content/fullImage.img.100.100.png`;
168
-
169
- this.thumbnail = `/content/products/${ProductUtils.getTokenizedSku(
170
- sku
171
- )}/jcr:content/fullImage.img.100.100.png`;
172
- };
173
-
174
- this.getThumbnail = function() {
175
- return this.thumbnail ? this.thumbnail.replace("http:", "https:") : "";
176
- };
177
-
178
- this.hasAvailableQuantity = function(qty = 1) {
179
- return Math.min(this.maxQuantity, this.availableQuantity) >= qty;
180
- };
181
-
182
- this.setPoints = function(points) {
183
- this.points = ensureFloatVal(points) || "";
184
- };
185
-
186
- this.getPointsFixed = function() {
187
- return parseFloat(this.points).toFixed(2);
188
- };
189
-
190
- this.setCv = function(cv) {
191
- this.cv = ensureFloatVal(cv) || "";
192
- };
193
-
194
- this.getCvFixed = function() {
195
- return parseFloat(this.cv).toFixed(2);
196
- };
197
-
198
- this.setPv = function(pv) {
199
- this.pv = ensureFloatVal(pv) || "";
200
- };
201
-
202
- this.getPvFixed = function() {
203
- return parseFloat(this.pv || 0).toFixed(2);
204
- };
205
-
206
- this.setPrice = function(price) {
207
- this.price = ensureFloatVal(price, true);
208
- };
209
-
210
- this.setFormattedPrice = function(formattedPrice) {
211
- this.formattedPrice = formattedPrice;
212
- };
213
-
214
- /**
215
- * Use to save the eventName if one exists. Added originally for when a product is
216
- * added to the cart the event pricing needs to be remembered if toggling between
217
- * pricetypes with event pricing and pricetype without event pricing
218
- */
219
- this.lockEventName = function() {
220
- if (!this.savedEventName && this.eventName) {
221
- this.savedEventName = this.eventName;
222
- }
223
- };
224
-
225
- this.setPriceAndPvFromType = function(_priceType, _activeEvents = null) {
226
- // WARNING: priceType needs to be handled client-side
227
- // const priceType = ConfigService.getMarketConfig().showWholeSalePricing && !AccountManager.isLoggedIn()
228
- // ? PriceType.WWHL
229
- // : (_priceType ? _priceType : this.priceType),
230
- const priceType = _priceType ? _priceType : this.priceType;
231
- const eventPricing = getEventPricing(this, priceType, _activeEvents);
232
-
233
- let changed = false;
234
- if (
235
- !this.price ||
236
- priceType !== this.priceType ||
237
- this.eventName !== eventPricing.eventName
238
- ) {
239
- this.setPrice(eventPricing.price || this.getPricing(priceType));
240
- this.setCv(eventPricing.cv || this.getCvWithType(priceType));
241
- this.setPv(eventPricing.pv || this.getPvWithType(priceType));
242
- this.priceType = priceType;
243
- this.eventName = eventPricing.eventName;
244
- changed = true;
245
- }
246
-
247
- this.variantSkus().forEach((variantSku) => {
248
- if (
249
- this.variants[variantSku].setPriceAndPvFromType(
250
- priceType,
251
- _activeEvents
252
- )
253
- ) {
254
- changed = true;
255
- }
256
- }, this);
257
-
258
- return changed;
259
- };
260
-
261
- this.getPrice = function() {
262
- return this.price ? this.price : 0;
263
- };
264
-
265
- this.getPriceFixed = function() {
266
- if (typeof this.price === "number") {
267
- return parseFloat(this.price.toFixed(2));
268
- } else {
269
- return this.getPrice();
270
- }
271
- };
272
-
273
- this.getOriginalPrice = function() {
274
- return this.priceType ? this.priceMap[this.priceType] : null;
275
- };
276
-
277
- this.addPricing = function(type, price) {
278
- this.priceMap[type] = price;
279
- };
280
-
281
- this.addPricingFromStatus = function(productStatus, priceType) {
282
- let modified = false;
283
-
284
- if (!priceType) {
285
- priceType = this.priceType;
286
- }
287
-
288
- // previous code stored sku as all caps
289
- const productStatusSku = productStatus.SKU || productStatus.sku;
290
- if (productStatusSku === this.sku) {
291
- this.globalProductID = productStatus.globalProductID;
292
- this.status = productStatus.status;
293
- this.availableQuantity = productStatus.availableQuantity;
294
- this.maxQuantity = productStatus.maxQuantity || 999;
295
- this.locallyProduced = productStatus.locallyProduced || false;
296
- if (productStatus.backordered) {
297
- this.backOrderDate = productStatus.backorderedAvailableDate;
298
- }
299
- if (productStatus.price) {
300
- this.setPoints(productStatus.price.PTS);
301
- Object.keys(productStatus.price).forEach((priceKey) => {
302
- this.addPricing(priceKey, productStatus.price[priceKey]);
303
- }, this);
304
- }
305
- if (productStatus.psv) {
306
- Object.keys(productStatus.psv).forEach((psvKey) => {
307
- this.addPvWithType(psvKey, productStatus.psv[psvKey]);
308
- }, this);
309
- }
310
- if (productStatus.csv) {
311
- Object.keys(productStatus.csv).map((csvKey) => {
312
- return this.addCvWithType(csvKey, productStatus.csv[csvKey]);
313
- }, this);
314
- }
315
- this.orderTypes = ProductUtils.mergeOrderTypes(this.orderTypes, productStatus.orderType);
316
- if(productStatus.childSkus) {
317
- this.childSkus = productStatus.childSkus;
318
- }
319
- this.setPriceAndPvFromType(priceType);
320
- modified = true;
321
- } else {
322
- let variant = this.getVariant(productStatusSku);
323
-
324
- if (variant) {
325
- if (variant.addPricingFromStatus(productStatus, priceType)) {
326
- variant.setMarketAttributes(productStatus);
327
- updateBaseRanges(this, variant);
328
- modified = true;
329
- }
330
- }
331
- }
332
-
333
- return modified;
334
- };
335
-
336
- this.addPvWithType = function(type, pv) {
337
- this.pvMap[type] = pv;
338
- };
339
-
340
- this.addCvWithType = function(type, cv) {
341
- this.cvMap[type] = cv;
342
- };
343
-
344
- this.getPricing = function(type) {
345
- let retVal = null;
346
-
347
- if (type) {
348
- retVal = this.priceMap[type];
349
-
350
- if (!retVal && isAdrType(type)) {
351
- retVal = this.priceMap[getOppisiteAdrType(type)];
352
- }
353
- }
354
- return retVal;
355
- };
356
-
357
- this.getEventPriceTypes = () => {
358
- const eventPriceTypes = [];
359
-
360
- Object.keys(this.priceMap).forEach(priceTypeKey => {
361
- const eventPriceType = priceTypeKey.substring(0, priceTypeKey.indexOf(`-${this.priceType}`));
362
-
363
- if (eventPriceType) {
364
- eventPriceTypes.push(eventPriceType);
365
- }
366
- });
367
-
368
- return eventPriceTypes;
369
- };
370
-
371
- this.getPvWithType = function(type) {
372
- let rv = this.pvMap[type];
373
- if (rv == null) {
374
- // Assumption: we always have WWHL from the service
375
- // default to wholesale if a PV of a particular type is not there
376
- rv = this.pvMap[PriceType.WWHL];
377
- }
378
- return rv;
379
- };
380
-
381
- this.getCvWithType = function(type) {
382
- let rv = this.cvMap[type];
383
- if (rv == null) {
384
- rv = this.cvMap[PriceType.WWHL];
385
- }
386
- return rv;
387
- };
388
-
389
- this.hasAdrOption = function() {
390
- return this.orderTypes["adr"];
391
- };
392
-
393
- this.isAdrOnly = function() {
394
- return this.hasAdrOption() && !this.hasOrderOption();
395
- };
396
-
397
- this.hasMultipleOrderTypes = function() {
398
- return this.hasOrderOption() && this.hasAdrOption();
399
- };
400
-
401
- this.hasOrderOption = function() {
402
- return this.orderTypes["order"];
403
- };
404
-
405
- this.isDistributor = function() {
406
- return this.custTypes.includes("10");
407
- };
408
-
409
- this.isCustomer = function() {
410
- return this.custTypes.includes("20");
411
- };
412
-
413
- this.isPreferredCustomer = function() {
414
- return this.custTypes.includes("30");
415
- };
416
-
417
- this.isBase = function() {
418
- return this.sku.substring(2, 4) === "55";
419
- };
420
-
421
- this.isVariant = function() {
422
- return this.baseSku.length > 0 && this.variantSkus().length === 0;
423
- };
424
-
425
- this.setVariant = function(variantProduct) {
426
- if (variantProduct && variantProduct.sku) {
427
- this.variants[variantProduct.sku] = variantProduct;
428
- }
429
- };
430
-
431
- this.getVariant = function(sku) {
432
- return this.variants[sku];
433
- };
434
-
435
- this.variantSkus = function() {
436
- return Object.keys(this.variants);
437
- };
438
-
439
- this.getVariantsList = function() {
440
- return Object.values(this.variants);
441
- };
442
-
443
- this.getMinPrice = function() {
444
- return this.priceMap[`min-${this.priceType}`];
445
- };
446
-
447
- this.getMaxPrice = function() {
448
- return this.priceMap[`max-${this.priceType}`];
449
- };
450
-
451
- this.getMinPv = function() {
452
- return this.pvMap[`min-${this.priceType}`];
453
- };
454
-
455
- this.getMaxPv = function() {
456
- return this.pvMap[`max-${this.priceType}`];
457
- };
458
-
459
- this.toJSON = function() {
460
- let retData = {};
461
- retData.sku = this.sku;
462
- retData.globalProductID = this.globalProductID;
463
- retData.title = this.title;
464
- retData.country = this.country;
465
- retData.language = this.lang;
466
- retData.shortDescr = this.shortDescr;
467
- retData.longDescr = this.longDescr;
468
- retData.fullImage = this.fullImage;
469
- retData.imageAltText = this.imageAltText;
470
- retData.productCarouselImages = this.productCarouselImages;
471
- retData.thumbnail = this.thumbnail;
472
- retData.ingredients = this.ingredients;
473
- retData.benefits = this.benefits;
474
- retData.usage = this.usage;
475
- retData.resources = this.resources;
476
- retData.videos = this.videos;
477
- retData.movie = this.movie;
478
- retData.youtube = this.youtube;
479
- retData.salesEventText = this.salesEventText;
480
- retData.contentSection = this.contentSection;
481
- retData.scanQualified = this.scanQualified;
482
- retData.availableQuantity = this.availableQuantity;
483
- retData.maxQuantity = this.maxQuantity;
484
- retData.points = this.points;
485
- retData.cv = this.cv;
486
- retData.pv = this.pv;
487
- retData.priceType = this.priceType;
488
- retData.price = this.price;
489
- retData.priceMap = this.priceMap;
490
- retData.cvMap = this.cvMap;
491
- retData.pvMap = this.pvMap;
492
- retData.orderTypes = this.orderTypes;
493
- retData.childSkus = this.childSkus;
494
- retData.custTypes = this.custTypes;
495
- retData.division = this.division;
496
- retData.backOrderDate = this.backOrderDate;
497
- retData.locallyProduced = this.locallyProduced;
498
- retData.agelocme = this.agelocme ? this.agelocme.toJSON() : null;
499
- retData.count = this.count;
500
- retData.flavor = this.flavor;
501
- retData.size = this.size;
502
- retData.shade = this.shade;
503
- retData.status = this.status;
504
- retData.variantType = this.variantType;
505
- retData.variantDropdownLabel = this.variantDropdownLabel;
506
- retData.variantDropdownPlaceholder = this.variantDropdownPlaceholder;
507
- retData.variantsLabel = this.variantsLabel;
508
- retData.groupOffer = this.groupOffer;
509
- retData.personalOffer = this.personalOffer;
510
- retData.savedEventName = this.savedEventName;
511
- retData.salesLabel = this.salesLabel;
512
- retData.eventName = this.eventName;
513
- retData.sizeWeight = this.sizeWeight;
514
- retData.nettoWeight = this.nettoWeight;
515
- retData.productLabels = this.productLabels;
516
- retData.variants = toVariantJSON(this.variants);
517
- retData.searchScore = this.searchScore;
518
- retData.isExclusive = this.isExclusive;
519
- retData.marketAttributes = this.marketAttributes;
520
- retData.restrictedMarkets = this.restrictedMarkets;
521
- retData.addOns = this.addOns;
522
-
523
- return retData;
524
- };
525
-
526
- this.setMeCommerceProductData = function(/*productData*/) {
527
- console.error("setMeCommerceProductData is deprecated!");
528
- };
529
-
530
- this.setBaseUrl = function(baseUrl) {
531
- this.thumbnail = ProductUtils.applyBaseUrl(this.thumbnail, baseUrl);
532
- this.fullImage = ProductUtils.applyBaseUrl(this.fullImage, baseUrl);
533
-
534
- if (this.isBase()) {
535
- for (const variantSku in this.variants) {
536
- const variant = this.variants[variantSku];
537
- variant.setBaseUrl(baseUrl);
538
- }
539
- }
540
- };
541
-
542
- this.setProductData = function(productData) {
543
- let data;
544
-
545
- if (productData) {
546
- if (productData instanceof eqProduct) {
547
- data = productData.toJSON();
548
- } else {
549
- data = productData;
550
- }
551
- if (data.sku) {
552
- this.sku = data.sku;
553
- }
554
- this.globalProductID = data.globalProductID || '';
555
- this.title = data.title;
556
- this.country = data.country;
557
- this.lang = data.language;
558
- this.shortDescr = data.shortDescr;
559
- this.longDescr = data.longDescr;
560
- this.isExclusive = isTrue(data.isExclusive);
561
-
562
- // WARNING: Browser Detection will need to be handled in the client
563
- // if (!BrowserDetection.isEdge() && !BrowserDetection.isFirefox()) {
564
- // this.fullImage = data.fullImage ? `${data.fullImage.split('?')[0]}?format=pjpg` : undefined;
565
- // this.thumbnail = data.thumbnail ? `${data.thumbnail.split('?')[0]}?format=pjpg` : undefined;
566
- // }
567
- this.fullImage = data.fullImage;
568
- this.thumbnail = data.thumbnail;
569
- this.imageAltText = data.imageAltText;
570
- this.productCarouselImages = data.productCarouselImages;
571
-
572
- this.ingredients = data.ingredients;
573
- this.benefits = data.benefits;
574
- this.usage = data.usage;
575
- this.resources = data.resources;
576
- this.videos = data.videos;
577
- this.movie = data.movie;
578
- this.youtube = data.youtube;
579
- this.salesEventText = data.salesEventText;
580
- this.contentSection = data.contentSection;
581
- this.scanQualified = data.scanQualified;
582
- this.status = data.status ? data.status : this.status;
583
- if (data.availableQuantity) {
584
- this.availableQuantity = data.availableQuantity;
585
- }
586
- if (data.maxQuantity >= 0) {
587
- this.maxQuantity = data.maxQuantity;
588
- }
589
- if (data.points) {
590
- this.setPoints(data.points);
591
- }
592
- if (data.cv) {
593
- this.setCv(data.cv);
594
- }
595
- if (data.pv) {
596
- this.setPv(data.pv);
597
- }
598
- if (data.price) {
599
- this.setPrice(data.price);
600
- }
601
- if (data.formattedPrice) {
602
- this.setFormattedPrice(data.formattedPrice);
603
- }
604
- if (data.priceType) {
605
- this.priceType = data.priceType;
606
- }
607
- this.locallyProduced = data.locallyProduced;
608
- this.count = productData.count;
609
- this.flavor = productData.flavor;
610
- this.size = productData.size;
611
- this.shade = productData.shade;
612
- this.variantType = productData.variantType;
613
- this.variantDropdownLabel = productData.variantDropdownLabel;
614
- this.variantDropdownPlaceholder = productData.variantDropdownPlaceholder;
615
- this.variantsLabel = productData.variantsLabel;
616
- this.groupOffer = productData.groupOffer;
617
- this.personalOffer = productData.personalOffer;
618
- this.baseSku = productData.baseSku ? productData.baseSku : "";
619
- this.savedEventName = productData.savedEventName;
620
- this.sizeWeight = productData.sizeWeight;
621
- this.nettoWeight = productData.nettoWeight;
622
- this.productLabels = productData.productLabels;
623
- this.salesLabel = productData.salesLabel;
624
- this.eventName = productData.eventName;
625
- this.variants = fromVariantJSON(this, productData.variants);
626
- this.searchScore = productData.searchScore ? productData.searchScore : 0;
627
- this.marketAttributes = productData.marketAttributes;
628
- this.addOns = productData.addOns || [];
629
- this.restrictedMarkets = productData.restrictedMarkets || [];
630
-
631
- if (data.priceType) {
632
- this.addPricing(data.priceType, data.price);
633
- this.addPvWithType(data.priceType, data.pv);
634
- this.addCvWithType(data.priceType, data.cv);
635
- }
636
- this.priceMap =
637
- data.priceMap && Object.keys(data.priceMap).length > 0
638
- ? data.priceMap
639
- : this.priceMap;
640
- this.cvMap =
641
- data.cvMap && Object.keys(data.cvMap).length > 0
642
- ? data.cvMap
643
- : this.cvMap;
644
- this.pvMap =
645
- data.pvMap && Object.keys(data.pvMap).length > 0
646
- ? data.pvMap
647
- : this.pvMap;
648
- this.orderTypes = ProductUtils.mergeOrderTypes(this.orderTypes, data.orderTypes);
649
- if(data.childSkus) {
650
- this.childSkus = data.childSkus;
651
- }
652
- this.custTypes = data.custTypes || [];
653
-
654
- this.division = data.division;
655
- this.backOrderDate = data.backOrderDate;
656
-
657
- if (data.agelocme) {
658
- this.agelocme = new Agelocme(data.agelocme);
659
- }
660
- }
661
- };
662
-
663
- this.isEmpty = function() {
664
- return (
665
- isFieldEmpty(this.sku) &&
666
- isFieldEmpty(this.title) &&
667
- isFieldEmpty(this.country) &&
668
- isFieldEmpty(this.lang) &&
669
- isFieldEmpty(this.shortDescr) &&
670
- isFieldEmpty(this.longDescr) &&
671
- isFieldEmpty(this.fullImage) &&
672
- isFieldEmpty(this.thumbnail) &&
673
- isFieldEmpty(this.points) &&
674
- isFieldEmpty(this.cv) &&
675
- isFieldEmpty(this.pv) &&
676
- Object.keys(this.priceMap).length === 0
677
- );
678
- };
679
-
680
- function isAdrType(priceType) {
681
- return (
682
- priceType === PriceType.WADW ||
683
- priceType === PriceType.ADW ||
684
- priceType === PriceType.WADR ||
685
- priceType === PriceType.ADR
686
- );
687
- }
688
-
689
- function getOppisiteAdrType(priceType) {
690
- let retVal = priceType;
691
-
692
- switch (priceType) {
693
- case PriceType.WADW:
694
- retVal = PriceType.ADW;
695
- break;
696
- case PriceType.ADW:
697
- retVal = PriceType.WADW;
698
- break;
699
- case PriceType.WADR:
700
- retVal = PriceType.ADR;
701
- break;
702
- case PriceType.ADR:
703
- retVal = PriceType.WADR;
704
- break;
705
- }
706
- return retVal;
707
- }
708
-
709
- function toVariantJSON(variants) {
710
- let json = {};
711
-
712
- Object.keys(variants).forEach((sku) => {
713
- json[sku] = variants[sku].toJSON();
714
- });
715
-
716
- return json;
717
- }
718
-
719
- function fromVariantJSON(baseProduct, variantsJson) {
720
- let variants = {};
721
-
722
- if (variantsJson) {
723
- for (const variantSku of Object.keys(variantsJson)) {
724
- const variantJson = variantsJson[variantSku];
725
- const variant = new eqProduct(variantJson);
726
-
727
- variant.baseSku = baseProduct.sku;
728
-
729
- variants[variantSku] = variant;
730
- }
731
- }
732
-
733
- return variants;
734
- }
735
-
736
- function updateBaseRanges(scope, variant) {
737
- Object.keys(variant.priceMap).forEach((priceType) => {
738
- let minPriceType = `min-${priceType}`,
739
- maxPriceType = `max-${priceType}`;
740
-
741
- scope.priceMap[minPriceType] = Math.min(
742
- scope.priceMap[minPriceType] || variant.priceMap[priceType],
743
- variant.priceMap[priceType]
744
- );
745
- scope.priceMap[maxPriceType] = Math.max(
746
- scope.priceMap[maxPriceType] || 0.0,
747
- variant.priceMap[priceType]
748
- );
749
-
750
- scope.pvMap[minPriceType] = Math.min(
751
- scope.pvMap[minPriceType] || variant.pvMap[priceType],
752
- variant.pvMap[priceType]
753
- );
754
- scope.pvMap[maxPriceType] = Math.max(
755
- scope.pvMap[maxPriceType] || 0.0,
756
- variant.pvMap[priceType]
757
- );
758
- });
759
- }
760
-
761
- function ensureFloatVal(val, acceptString) {
762
- let intVal;
763
- if (typeof val === "number") {
764
- intVal = val;
765
- } else if (typeof val === "string") {
766
- intVal = Number(val.replace(/[^0-9.]+/g, ""));
767
- if (isNaN(intVal) && acceptString) {
768
- intVal = val;
769
- }
770
- }
771
- return intVal;
772
- }
773
-
774
- function isFieldEmpty(fieldValue) {
775
- return (
776
- fieldValue === null || fieldValue === undefined || fieldValue.length === 0
777
- );
778
- }
779
-
780
- let activeEvents = [];
781
- function getEventPricing(scope, priceType, _activeEvents = null) {
782
- let eventNames = scope.savedEventName
783
- ? [scope.savedEventName]
784
- : _activeEvents || activeEvents;
785
- let retVal = { eventName: null };
786
-
787
- eventNames.forEach((event) => {
788
- let _eventPriceType = `${event}-${priceType}`;
789
-
790
- if (scope.getPricing(_eventPriceType)) {
791
- retVal.price = scope.getPricing(_eventPriceType);
792
- retVal.cv = scope.getCvWithType(_eventPriceType);
793
- retVal.pv = scope.getPvWithType(_eventPriceType);
794
- retVal.priceType = _eventPriceType;
795
- retVal.eventName = scope.effectiveEventName = event;
796
- }
797
- });
798
-
799
- return retVal;
800
- }
801
-
802
- // WARNING: Product events will need to be handled in the client
803
- // $.getValue(events.salesevent.ACTIVE_EVENTS, _activeEvents => {
804
- // if (_activeEvents) {
805
- // activeEvents = _activeEvents;
806
- // }
807
- // }, true);
808
-
809
- this.setProductData(productData);
810
- };
811
-
812
- module.exports = eqProduct;