@nuskin/ns-product-lib 1.3.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/CHANGELOG.md +146 -0
- package/README.md +33 -0
- package/package.json +37 -0
- package/src/agelocme.js +35 -0
- package/src/index.js +24 -0
- package/src/priceType.js +18 -0
- package/src/product.js +803 -0
- package/src/productContentMapper.js +196 -0
- package/src/productStatus.js +20 -0
- package/src/productStatusMapper.js +190 -0
- package/src/productUtils.js +83 -0
@@ -0,0 +1,196 @@
|
|
1
|
+
"use strict";
|
2
|
+
|
3
|
+
const { isNullOrEmpty, isTrue } = require("@nuskin/ns-common-lib");
|
4
|
+
|
5
|
+
const Product = require("./product.js");
|
6
|
+
const ProductUtils = require("./productUtils.js");
|
7
|
+
|
8
|
+
/**
|
9
|
+
* Contains functions to map the AEM Product Content to a Product (product.js).
|
10
|
+
*/
|
11
|
+
const ProductContentMapper = {
|
12
|
+
/**
|
13
|
+
* Creates a Product (product.js) object from the AEM Product Content response.
|
14
|
+
* @param {Object} productContent the AEM response
|
15
|
+
* @param {Object} options {language, country, isGroupOffer, isPersonalOffer, productStatus, priceType}
|
16
|
+
*/
|
17
|
+
createProduct: function(productContent, options = {}) {
|
18
|
+
if (isNullOrEmpty(productContent)) {
|
19
|
+
return null;
|
20
|
+
}
|
21
|
+
|
22
|
+
if (isNullOrEmpty(options)) {
|
23
|
+
options = {};
|
24
|
+
}
|
25
|
+
|
26
|
+
let product = new Product();
|
27
|
+
|
28
|
+
// set market values
|
29
|
+
let marketData = productContent.market;
|
30
|
+
|
31
|
+
if (Array.isArray(marketData)) {
|
32
|
+
marketData = getMarketData(marketData, options.country);
|
33
|
+
}
|
34
|
+
|
35
|
+
if (marketData) {
|
36
|
+
product.country = marketData.countryCode;
|
37
|
+
product.productLabels = marketData.populateProductLabels;
|
38
|
+
product.scanQualified = marketData.scanQualified;
|
39
|
+
|
40
|
+
const marketOrderTypes = marketData.orderType;
|
41
|
+
if (marketOrderTypes) {
|
42
|
+
product.orderTypes = ProductUtils.getOrderTypesMapping(marketOrderTypes);
|
43
|
+
}
|
44
|
+
|
45
|
+
const marketCustomerTypes = marketData.custType;
|
46
|
+
if (marketCustomerTypes) {
|
47
|
+
product.custTypes = marketCustomerTypes;
|
48
|
+
}
|
49
|
+
|
50
|
+
const restrictedMarkets = marketData.restrictedMarket;
|
51
|
+
if (restrictedMarkets) {
|
52
|
+
product.restrictedMarkets = restrictedMarkets;
|
53
|
+
}
|
54
|
+
}
|
55
|
+
|
56
|
+
// set product values
|
57
|
+
product.sku = productContent.sku;
|
58
|
+
product.isExclusive = isTrue(productContent.isExclusive);
|
59
|
+
|
60
|
+
if (productContent.contents) {
|
61
|
+
const fullImage = productContent.contents.fullImage;
|
62
|
+
if (fullImage) {
|
63
|
+
product.setFullImage(fullImage);
|
64
|
+
product.setThumbnail(
|
65
|
+
fullImage.replace(
|
66
|
+
/\.img\.[0-9]+\.[0-9]+\.png/i,
|
67
|
+
".img.160.160.png"
|
68
|
+
)
|
69
|
+
);
|
70
|
+
}
|
71
|
+
|
72
|
+
const imageCarousel = productContent.contents.imageCarousel;
|
73
|
+
if (imageCarousel) {
|
74
|
+
product.setProductCarouselImages(imageCarousel);
|
75
|
+
}
|
76
|
+
|
77
|
+
// set product language data
|
78
|
+
let languageData = getLanguageData(
|
79
|
+
productContent.contents.language,
|
80
|
+
options.language
|
81
|
+
);
|
82
|
+
|
83
|
+
// AEM-8320 - it returns two BP (08007070 and 08129080), 08129080 does not have any translation yet.
|
84
|
+
// language data can be null (have a BP that does not have any translation)
|
85
|
+
if (languageData) {
|
86
|
+
product.lang = languageData.languageCode || "";
|
87
|
+
product.title = languageData.name || "";
|
88
|
+
product.shortDescr = languageData.shortDescription || "";
|
89
|
+
product.longDescr = languageData.longDescription || "";
|
90
|
+
product.ingredients = languageData.ingredients || "";
|
91
|
+
product.benefits = languageData.benefits || "";
|
92
|
+
product.usage = languageData.usage || "";
|
93
|
+
product.resources = languageData.resources || "";
|
94
|
+
product.videos = languageData.videos || "";
|
95
|
+
product.contentSection = languageData.contentSection || "";
|
96
|
+
product.sizeWeight = languageData.size_weight || "";
|
97
|
+
product.nettoWeight = languageData.netto_weight || "";
|
98
|
+
product.movie = languageData.movie || "";
|
99
|
+
product.youtube = languageData.youtube || "";
|
100
|
+
product.salesEventText = languageData.tdc_salesEventText || "";
|
101
|
+
product.salesLabel = languageData.salesLabel || "";
|
102
|
+
|
103
|
+
if (languageData.variantInfo) {
|
104
|
+
if (languageData.variantInfo.tdc_dropdownLabel) {
|
105
|
+
product.variantDropdownLabel =
|
106
|
+
languageData.variantInfo.tdc_dropdownLabel;
|
107
|
+
}
|
108
|
+
if (languageData.variantInfo.tdc_dropdownPlaceholder) {
|
109
|
+
product.variantDropdownPlaceholder =
|
110
|
+
languageData.variantInfo.tdc_dropdownPlaceholder;
|
111
|
+
}
|
112
|
+
if (languageData.variantInfo.tdc_variantsLabel) {
|
113
|
+
product.variantsLabel = languageData.variantInfo.tdc_variantsLabel;
|
114
|
+
}
|
115
|
+
}
|
116
|
+
|
117
|
+
product.setImageAltText(productContent.contents.altText);
|
118
|
+
}
|
119
|
+
}
|
120
|
+
|
121
|
+
product.division = productContent.division;
|
122
|
+
|
123
|
+
product.isGroupOffer = !!options.isGroupOffer;
|
124
|
+
product.isPersonalOffer = !!options.isPersonalOffer;
|
125
|
+
|
126
|
+
// set product variants values
|
127
|
+
if (productContent.variantConfig) {
|
128
|
+
if (productContent.variantConfig.variantType) {
|
129
|
+
product.variantType = productContent.variantConfig.variantType;
|
130
|
+
}
|
131
|
+
if (productContent.variantConfig.variants) {
|
132
|
+
productContent.variantConfig.variants.forEach((variant) => {
|
133
|
+
product.setVariant(this.createProduct(variant, options));
|
134
|
+
});
|
135
|
+
}
|
136
|
+
if (productContent.variantConfig.swatchColor) {
|
137
|
+
product.shade = productContent.variantConfig.swatchColor;
|
138
|
+
}
|
139
|
+
if (productContent.variantConfig.baseSku) {
|
140
|
+
product.baseSku = productContent.variantConfig.baseSku;
|
141
|
+
}
|
142
|
+
}
|
143
|
+
|
144
|
+
// set options
|
145
|
+
if (options.productStatus) {
|
146
|
+
product.setMarketAttributes(options.productStatus);
|
147
|
+
product.addPricingFromStatus(options.productStatus);
|
148
|
+
}
|
149
|
+
|
150
|
+
if (options.priceType) {
|
151
|
+
product.setPriceAndPvFromType(options.priceType);
|
152
|
+
}
|
153
|
+
|
154
|
+
return product;
|
155
|
+
}
|
156
|
+
};
|
157
|
+
|
158
|
+
function getLanguageData(languageDataArray, language) {
|
159
|
+
if (!Array.isArray(languageDataArray) || !languageDataArray.length) {
|
160
|
+
return null;
|
161
|
+
}
|
162
|
+
|
163
|
+
if (languageDataArray.length === 1) {
|
164
|
+
return languageDataArray[0];
|
165
|
+
}
|
166
|
+
|
167
|
+
const fallbackLanguageData = languageDataArray.find(md => md.languageCode === "en");
|
168
|
+
|
169
|
+
if (language) {
|
170
|
+
const nativeLanguageData = languageDataArray.find(md => md.languageCode === language);
|
171
|
+
return nativeLanguageData || fallbackLanguageData;
|
172
|
+
}
|
173
|
+
|
174
|
+
return fallbackLanguageData;
|
175
|
+
}
|
176
|
+
|
177
|
+
function getMarketData(marketDataArray, country) {
|
178
|
+
if (!Array.isArray(marketDataArray) || !marketDataArray.length) {
|
179
|
+
return null;
|
180
|
+
}
|
181
|
+
|
182
|
+
if (marketDataArray.length === 1) {
|
183
|
+
return marketDataArray[0];
|
184
|
+
}
|
185
|
+
|
186
|
+
const fallbackMarketData = marketDataArray.find(md => md.countryCode === "US");
|
187
|
+
|
188
|
+
if (country) {
|
189
|
+
const nativeMarketData = marketDataArray.find(md => md.countryCode === country);
|
190
|
+
return nativeMarketData || fallbackMarketData;
|
191
|
+
}
|
192
|
+
|
193
|
+
return fallbackMarketData;
|
194
|
+
}
|
195
|
+
|
196
|
+
module.exports = ProductContentMapper;
|
@@ -0,0 +1,20 @@
|
|
1
|
+
"use strict";
|
2
|
+
|
3
|
+
const ProductStatus = {
|
4
|
+
Discontinued: "DISCONTINUED",
|
5
|
+
NotReleasedForSale: "NOT_RELEASED_FOR_SALE",
|
6
|
+
ReleasedForSale: "RELEASED_FOR_SALE",
|
7
|
+
NotFound: "NOT_FOUND",
|
8
|
+
StopStatus: "STOP_STATUS",
|
9
|
+
|
10
|
+
//properties allows you do do something like ProductStatus.properties['DISCONTINUED'].code to get the status code
|
11
|
+
properties: {
|
12
|
+
DISCONTINUED: { stringKey: "discontinued", code: 2 },
|
13
|
+
NOT_RELEASED_FOR_SALE: { stringKey: "notReleasedForSale", code: 9 },
|
14
|
+
RELEASED_FOR_SALE: { stringKey: "", code: 10 },
|
15
|
+
STOP_STATUS: { stringKey: "stopStatus", code: 5 },
|
16
|
+
NOT_FOUND: { stringKey: "", code: 0 }
|
17
|
+
}
|
18
|
+
};
|
19
|
+
|
20
|
+
module.exports = ProductStatus;
|
@@ -0,0 +1,190 @@
|
|
1
|
+
"use strict";
|
2
|
+
|
3
|
+
/**
|
4
|
+
* Contains functions to map Product Status responses to the object fit for
|
5
|
+
* Product (product.js) consumption in the addPricingFromStatus function.
|
6
|
+
*/
|
7
|
+
const ProductStatusMapper = {
|
8
|
+
/**
|
9
|
+
* Creates Product Status objects fit for Product (product.js) consumption from the V2 response
|
10
|
+
* @param {Object} productStatusV2Response
|
11
|
+
* @return {Array<object>}
|
12
|
+
* @private
|
13
|
+
*/
|
14
|
+
createProductStatusesFromV2: function(productStatusV2Response) {
|
15
|
+
const productStatuses = [];
|
16
|
+
if (productStatusV2Response.items) {
|
17
|
+
for (const productStatusSku of Object.keys(productStatusV2Response.items)) {
|
18
|
+
const productStatusV2 = productStatusV2Response.items[productStatusSku];
|
19
|
+
productStatusV2.sku = productStatusSku;
|
20
|
+
const productStatus = this.createProductStatusFromV2(productStatusV2);
|
21
|
+
productStatuses.push(productStatus);
|
22
|
+
}
|
23
|
+
}
|
24
|
+
return productStatuses;
|
25
|
+
},
|
26
|
+
|
27
|
+
/**
|
28
|
+
* Creates Product Status object fit for Product (product.js) consumption from its V2 counterpart
|
29
|
+
* @param {Object} productStatusV2
|
30
|
+
* @return {object}
|
31
|
+
* @private
|
32
|
+
*/
|
33
|
+
createProductStatusFromV2: function(productStatusV2) {
|
34
|
+
const productStatus = {
|
35
|
+
sku: productStatusV2.sku,
|
36
|
+
globalProductID: productStatusV2.globalProductID,
|
37
|
+
backordered: productStatusV2.backordered,
|
38
|
+
backorderedAvailableDate: productStatusV2.backorderedAvailableDate,
|
39
|
+
availableQuantity: productStatusV2.availableQuantity,
|
40
|
+
webEnabled: productStatusV2.webEnabled,
|
41
|
+
status: getStatusPartFromV2(productStatusV2),
|
42
|
+
orderType: productStatusV2.orderType,
|
43
|
+
marketAttributes: productStatusV2.marketAttributes,
|
44
|
+
childSkus: productStatusV2.childSkus != null ? productStatusV2.childSkus : []
|
45
|
+
};
|
46
|
+
addPricingFromV2(productStatusV2, productStatus);
|
47
|
+
return productStatus
|
48
|
+
}
|
49
|
+
};
|
50
|
+
|
51
|
+
/**
|
52
|
+
* Gets the status value (productStatus.status) from V2
|
53
|
+
* @param {Object} productStatusV2
|
54
|
+
* @return {String}
|
55
|
+
*/
|
56
|
+
function getStatusPartFromV2(productStatusV2) {
|
57
|
+
const retailStatusIsReleased = productStatusV2.status.retail === 'RELEASED_FOR_SALE';
|
58
|
+
const wholesaleStatusIsReleased =
|
59
|
+
productStatusV2.status.wholesale === 'RELEASED_FOR_SALE';
|
60
|
+
const retailNotSet = productStatusV2.status.retail === 'NOT_SET_FOR_DIST_TYPE';
|
61
|
+
const wholesaleNotSet = productStatusV2.status.wholesale === 'NOT_SET_FOR_DIST_TYPE';
|
62
|
+
if (retailStatusIsReleased || wholesaleStatusIsReleased) {
|
63
|
+
return 'RELEASED_FOR_SALE';
|
64
|
+
}
|
65
|
+
|
66
|
+
if (retailNotSet || wholesaleNotSet) {
|
67
|
+
if (retailNotSet && wholesaleNotSet) {
|
68
|
+
return 'NOT_RELEASED_FOR_SALE';
|
69
|
+
}
|
70
|
+
if (retailNotSet) {
|
71
|
+
return productStatusV2.status.wholesale;
|
72
|
+
}
|
73
|
+
if (wholesaleNotSet) {
|
74
|
+
return productStatusV2.status.retail;
|
75
|
+
}
|
76
|
+
}
|
77
|
+
|
78
|
+
return productStatusV2.status.retail;
|
79
|
+
}
|
80
|
+
|
81
|
+
/**
|
82
|
+
* Adds Product Status Pricing from V2
|
83
|
+
* @param {Object} productStatusV2
|
84
|
+
* @param {Object} productStatus
|
85
|
+
*/
|
86
|
+
function addPricingFromV2(productStatusV2, productStatus) {
|
87
|
+
if (productStatusV2 && productStatusV2.pricing) {
|
88
|
+
const pricing = productStatusV2.pricing;
|
89
|
+
Object.keys(pricing).forEach((channelKey) => {
|
90
|
+
Object.keys(pricing[channelKey]).forEach((contextKey) => {
|
91
|
+
Object.keys(pricing[channelKey][contextKey]).forEach((priceTypeKey) => {
|
92
|
+
const priceValue = pricing[channelKey][contextKey][priceTypeKey];
|
93
|
+
switch (channelKey) {
|
94
|
+
case 'wholesale':
|
95
|
+
switch (contextKey) {
|
96
|
+
case 'webOrder':
|
97
|
+
addPrice(productStatus, priceTypeKey, 'WWHL', priceValue);
|
98
|
+
break;
|
99
|
+
case 'webAdr':
|
100
|
+
case 'WADW':
|
101
|
+
case 'ADR5':
|
102
|
+
addPrice(productStatus, priceTypeKey, 'WADW', priceValue);
|
103
|
+
break;
|
104
|
+
case 'order':
|
105
|
+
case 'WHL':
|
106
|
+
addPrice(productStatus, priceTypeKey, 'WHL', priceValue);
|
107
|
+
break;
|
108
|
+
case 'adr':
|
109
|
+
addPrice(productStatus, priceTypeKey, 'ADW', priceValue);
|
110
|
+
break;
|
111
|
+
case 'ADR10':
|
112
|
+
case 'WADWD':
|
113
|
+
addPrice(productStatus, priceTypeKey, 'WADWD', priceValue);
|
114
|
+
break;
|
115
|
+
default:
|
116
|
+
addPrice(
|
117
|
+
productStatus,
|
118
|
+
priceTypeKey,
|
119
|
+
`${contextKey}-WWHL`,
|
120
|
+
priceValue
|
121
|
+
);
|
122
|
+
break;
|
123
|
+
}
|
124
|
+
break;
|
125
|
+
case 'retail':
|
126
|
+
switch (contextKey) {
|
127
|
+
case 'webOrder':
|
128
|
+
addPrice(productStatus, priceTypeKey, 'WRTL', priceValue);
|
129
|
+
break;
|
130
|
+
case 'webAdr':
|
131
|
+
addPrice(productStatus, priceTypeKey, 'WADR', priceValue);
|
132
|
+
break;
|
133
|
+
case 'order':
|
134
|
+
case 'RTL':
|
135
|
+
addPrice(productStatus, priceTypeKey, 'RTL', priceValue);
|
136
|
+
break;
|
137
|
+
case 'adr':
|
138
|
+
addPrice(productStatus, priceTypeKey, 'ADR', priceValue);
|
139
|
+
break;
|
140
|
+
default:
|
141
|
+
addPrice(
|
142
|
+
productStatus,
|
143
|
+
priceTypeKey,
|
144
|
+
`${contextKey}-WRTL`,
|
145
|
+
priceValue
|
146
|
+
);
|
147
|
+
break;
|
148
|
+
}
|
149
|
+
break;
|
150
|
+
}
|
151
|
+
});
|
152
|
+
});
|
153
|
+
});
|
154
|
+
}
|
155
|
+
}
|
156
|
+
|
157
|
+
/**
|
158
|
+
* Adds a price tp the Product Status
|
159
|
+
* @param {Object} productStatus
|
160
|
+
* @param {String} priceTypeKey
|
161
|
+
* @param {String} subKey
|
162
|
+
* @param {number} priceValue
|
163
|
+
*/
|
164
|
+
function addPrice(productStatus, priceTypeKey, subKey, priceValue) {
|
165
|
+
if (!['Points', 'cv', 'price', 'pv'].includes(priceTypeKey)) {
|
166
|
+
// if the priceTypeKey is not one of these, then we don't want it to show.
|
167
|
+
return;
|
168
|
+
}
|
169
|
+
if (priceTypeKey === 'pv') {
|
170
|
+
priceTypeKey = 'psv';
|
171
|
+
}
|
172
|
+
if (priceTypeKey === 'cv') {
|
173
|
+
priceTypeKey = 'csv';
|
174
|
+
}
|
175
|
+
|
176
|
+
if (productStatus[priceTypeKey] === undefined) {
|
177
|
+
if (priceTypeKey === 'Points') {
|
178
|
+
if (!productStatus.price) {
|
179
|
+
productStatus.price = {};
|
180
|
+
}
|
181
|
+
productStatus.price.PTS = priceValue;
|
182
|
+
return;
|
183
|
+
} else {
|
184
|
+
productStatus[priceTypeKey] = {};
|
185
|
+
}
|
186
|
+
}
|
187
|
+
productStatus[priceTypeKey][subKey] = priceValue;
|
188
|
+
}
|
189
|
+
|
190
|
+
module.exports = ProductStatusMapper;
|
@@ -0,0 +1,83 @@
|
|
1
|
+
/* eslint-disable max-len */
|
2
|
+
|
3
|
+
"use strict";
|
4
|
+
|
5
|
+
const { isNullOrEmpty, getFullUrl } = require("@nuskin/ns-common-lib");
|
6
|
+
|
7
|
+
const ProductUtils = {
|
8
|
+
/**
|
9
|
+
* Returns string needed for getting product data from PMD
|
10
|
+
* Example full url where this is used: "https://test.nuskin.com/content/products/01/16/08/01160862/en.service.US.json"
|
11
|
+
* @param {string} sku - ex. "01160862"
|
12
|
+
* @returns tokenized sku - ex. "01/16/08/01160862"
|
13
|
+
*/
|
14
|
+
getTokenizedSku: function(sku) {
|
15
|
+
let { part1, part2, part3 } = this.getTokenizedSkuParts(sku);
|
16
|
+
return `${part1}/${part2}/${part3}/${sku}`;
|
17
|
+
},
|
18
|
+
|
19
|
+
/**
|
20
|
+
* Returns string parts needed for getting product data from PMD
|
21
|
+
* @param {string} sku - ex. "01160862"
|
22
|
+
* @returns {{ part1, part2, part3 }} tokenized sku parts - ex. { "part1": "01", "part2": "16", "part3": "08" }
|
23
|
+
*/
|
24
|
+
getTokenizedSkuParts: function(sku) {
|
25
|
+
let part1 = sku.substring(0, 2); // "01"
|
26
|
+
let part2 = sku.substring(2, 4); // "11"
|
27
|
+
let part3 = sku.substring(4, 6); // "11"
|
28
|
+
return {
|
29
|
+
part1,
|
30
|
+
part2,
|
31
|
+
part3
|
32
|
+
};
|
33
|
+
},
|
34
|
+
|
35
|
+
/**
|
36
|
+
* Applies a Base URL by prepending it to an existing URL when applicable
|
37
|
+
* @param {string} url - ex. "content/..." or "http://test.nuskin.com/content/..."
|
38
|
+
* @param {string} baseUrl - ex. "http://test.nuskin.com"
|
39
|
+
* @returns {string} full url
|
40
|
+
*/
|
41
|
+
applyBaseUrl: function(url, baseUrl) {
|
42
|
+
const fullUrl = getFullUrl(baseUrl, url);
|
43
|
+
return fullUrl.replace("http://", "https://");
|
44
|
+
},
|
45
|
+
|
46
|
+
mergeOrderTypes: function(previousOrderTypes, newOrderTypes) {
|
47
|
+
// untrust previous order types, override them to false
|
48
|
+
const previousOrderTypesMapping = this.getOrderTypesMapping(previousOrderTypes, true);
|
49
|
+
|
50
|
+
// trust new order types
|
51
|
+
const newOrderTypesMapping = this.getOrderTypesMapping(newOrderTypes);
|
52
|
+
|
53
|
+
return {
|
54
|
+
...previousOrderTypesMapping,
|
55
|
+
...newOrderTypesMapping
|
56
|
+
};
|
57
|
+
},
|
58
|
+
|
59
|
+
/**
|
60
|
+
* Gets the order types as a dictionary/mapping of order type to value
|
61
|
+
* @param {string[] | object[]} orderTypes - ex. ["adr", "order"] or { adr: false, order: true }
|
62
|
+
* @param {boolean} override - used to force the value of each orderType to false
|
63
|
+
* @returns {object} order types dictionary/mapping
|
64
|
+
*/
|
65
|
+
getOrderTypesMapping: function(orderTypes, override = false) {
|
66
|
+
if (isNullOrEmpty(orderTypes)) {
|
67
|
+
return {};
|
68
|
+
}
|
69
|
+
const orderTypesMapping = {};
|
70
|
+
if (Array.isArray(orderTypes)) {
|
71
|
+
for (const orderType of orderTypes) {
|
72
|
+
orderTypesMapping[orderType.toLowerCase()] = !override;
|
73
|
+
}
|
74
|
+
} else {
|
75
|
+
for (const orderType in orderTypes) {
|
76
|
+
orderTypesMapping[orderType.toLowerCase()] = !override && !!orderTypes[orderType];
|
77
|
+
}
|
78
|
+
}
|
79
|
+
return orderTypesMapping;
|
80
|
+
}
|
81
|
+
};
|
82
|
+
|
83
|
+
module.exports = ProductUtils;
|