@nuskin/cs-product-lib 1.1.2 → 1.1.3-cx12-8656.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nuskin/cs-product-lib",
3
- "version": "1.1.2",
3
+ "version": "1.1.3-cx12-8656.1",
4
4
  "description": "Library for pulling and parsing product content (and applying business logic) retrieved from Contentstack",
5
5
  "main": "src/index.js",
6
6
  "scripts": {
package/src/csProduct.js CHANGED
@@ -134,6 +134,9 @@ const csProduct = (props) => {
134
134
  validVariants.push({
135
135
  ...variant,
136
136
  status: varStatus,
137
+ backorderQuantity: varStatus?.backordered ? varStatus?.availableQuantity : 0,
138
+ availableQuantity: varStatus?.backordered ? 0 : varStatus?.availableQuantity,
139
+ backorderedAvailableDate: varStatus?.backorderedAvailableDate || null,
137
140
  marketData
138
141
  })
139
142
  }
@@ -564,7 +567,8 @@ const csProduct = (props) => {
564
567
  getProductTranslations,
565
568
  getBundle,
566
569
  getBundles,
567
- getCategoryPageTranslations
570
+ getCategoryPageTranslations,
571
+ formatProductWithSchema
568
572
  }
569
573
  }
570
574
 
package/src/formatKit.js CHANGED
@@ -2,71 +2,6 @@ const { isNullOrEmpty, isTrue } = require('@nuskin/ns-common-lib');
2
2
  const { formatDiscounts } = require('./helpers/formatDiscounts');
3
3
  const moment = require('moment')
4
4
 
5
- const parseStatus = (variant) => {
6
- const dateFormat = 'YYYY-MM-DD'
7
-
8
- if (variant?.type !== 'bundle' && variant?.type !== 'kit') {
9
- return {
10
- status: variant?.status?.status,
11
- isBackordered: variant?.status?.isBackordered,
12
- backorderedAvailableDate: variant?.status?.backorderedAvailableDate
13
- }
14
- }
15
-
16
- let isBackordered = false
17
- let backorderedAvailableDate = null
18
- if (variant?.type === 'kit') {
19
- try {
20
- /* eslint-disable no-unsafe-optional-chaining */
21
- const latestBODate = Math.max(
22
- ...variant?.requiredProducts?.flatMap((a) =>
23
- a?.product?.variants?.map(
24
- (s) =>
25
- Number(
26
- new Date(s?.status?.backorderedAvailableDate)
27
- ) || 0
28
- )
29
- )
30
- )
31
- if (latestBODate) {
32
- isBackordered = true
33
- backorderedAvailableDate = moment(
34
- new Date(latestBODate)
35
- ).format(dateFormat)
36
- }
37
- } catch {
38
- backorderedAvailableDate = null
39
- }
40
- } else if (variant?.type === 'bundle') {
41
- try {
42
- const latestBODate = Math.max(
43
- ...variant?.requiredProducts?.flatMap(
44
- (a) =>
45
- Number(
46
- new Date(
47
- a?.product?.variants[0]?.status?.backorderedAvailableDate
48
- )
49
- ) || 0
50
- )
51
- )
52
- if (latestBODate) {
53
- isBackordered = true
54
- backorderedAvailableDate = moment(
55
- new Date(latestBODate)
56
- ).format(dateFormat)
57
- }
58
- } catch {
59
- backorderedAvailableDate = null
60
- }
61
- }
62
- return {
63
- // changed
64
- status: variant?.status?.status,
65
- isBackordered: isBackordered,
66
- backorderedAvailableDate: backorderedAvailableDate
67
- }
68
- }
69
-
70
5
  const parsePrice = (product = []) => {
71
6
  const requiredProducts = !isNullOrEmpty(product)
72
7
  ? product?.map((p) => p?.requiredProducts).flat()
@@ -155,36 +90,49 @@ const parsePrice = (product = []) => {
155
90
  return sumOfPriceAndPoints
156
91
  }
157
92
 
158
- const parseKitAvailableQuantity = (kit) => {
159
- try {
160
- return (
161
- Math.min(
162
- ...kit.flatMap((a) =>
163
- a?.sku?.map((s) => s?.inventoryProperties?.atpQty)
164
- )
165
- ) || 0
166
- )
167
- } catch {
168
- return 0
169
- }
170
- }
171
-
172
- const parseBundleAvailableQuantity = (product) => {
93
+ const getKitOrBundleDetails = (bundle) => {
94
+ const dateFormat = 'YYYY-MM-DD'
95
+ let maxQty = null;
96
+ let backOrderedQty = null;
97
+ let availableQty = null;
98
+ let quantities = [];
99
+ let availableQuantities = [];
100
+ let prodQty = 0;
101
+ let dates = [];
102
+ let latestBackOrderedDate = null;
173
103
  try {
174
- const { requiredProducts = [] } = !isNullOrEmpty(product) ? product : {}
175
- const quantities = requiredProducts?.map((p) =>
176
- p?.product?.variants?.map((a) => a?.availableQuantity)
177
- )
178
- return Math.min(...quantities) || 0
179
- } catch {
180
- console.log('some error in availableQuantity')
181
- return 0
104
+ bundle?.requiredProducts?.flatMap(a => {
105
+ prodQty = Number(a?.quantity || 1);
106
+ a?.product?.variants?.map(s => {
107
+ const backorderQty = s?.backorderQuantity || 0;
108
+ const availableQty = s?.availableQuantity || 0;
109
+ const backorderDate = s?.status?.backorderedAvailableDate || null;
110
+ dates.push(moment(backorderDate))
111
+ quantities.push({maxPurchasable: (backorderQty + availableQty)/prodQty});
112
+ availableQuantities.push({availableQty});
113
+ })
114
+ })
115
+ const maxQtyPurchasable = quantities && quantities.length > 0 ? Math.min(...quantities.map(obj=>obj.maxPurchasable)) : 0;
116
+ const maxAvailableQty = availableQuantities && availableQuantities.length > 0 ? Math.min(...availableQuantities.map(obj=>obj.availableQty)) : 0;
117
+ maxQty = Math.floor(maxQtyPurchasable);
118
+ const checkIfNegative = (maxQty - maxAvailableQty) < 0;
119
+ backOrderedQty = checkIfNegative ? 0 : maxQty - maxAvailableQty;
120
+ availableQty = maxAvailableQty;
121
+ dates = dates.filter(date => date.isValid());
122
+ latestBackOrderedDate = dates && dates.length > 0 && backOrderedQty !== 0 ? moment.max(dates).format(dateFormat) : null;
123
+ } catch (error) {
124
+ console.error('getKitOrBundleDetails() - back order qty, available qty, max qty, latest back order date or status error', error);
125
+ maxQty = 0;
126
+ backOrderedQty = 0;
127
+ availableQty = 0;
128
+ latestBackOrderedDate = null;
182
129
  }
130
+ return {status: bundle?.status?.status, maxQty, backOrderedQty, availableQty, latestBackOrderedDate}
183
131
  }
184
132
 
185
- const parseKit = (product, market) => {
133
+ const parseKit = (bundle, market) => {
186
134
  const kits = {}
187
- const [properties] = !isNullOrEmpty(product) ? product : []
135
+ const [properties] = !isNullOrEmpty(bundle) ? bundle : []
188
136
  kits.id = properties?.id
189
137
  if (properties?.type === 'kit' && properties?.legacy_sku) {
190
138
  kits.id = properties?.legacy_sku
@@ -199,8 +147,7 @@ const parseKit = (product, market) => {
199
147
  kits.excludeFromSearch = isTrue(properties?.excludeFromSearch)
200
148
  kits.isExclusive = isTrue(properties?.isExclusive)
201
149
  kits.marketAttributes = properties?.marketAttributes
202
- kits.maxQuantity = (product?.inventoryProperties || {}).atpQty
203
- const priceData = parsePrice(product)
150
+ const priceData = parsePrice(bundle)
204
151
  kits.points = priceData?.points
205
152
  kits.price = priceData?.price
206
153
  kits.purchaseTypes = {
@@ -218,35 +165,20 @@ const parseKit = (product, market) => {
218
165
  kits.scanQualifiedCount = properties?.scanQualifiedCount
219
166
  kits.foreignOrderLimit = properties?.foreignOrderLimit || properties?.foreign_limit
220
167
  kits.taxClass = properties?.taxClass
221
- // newVariant.slug = undefined
222
- kits.status = parseStatus(properties)
223
- if ((kits?.status || {})?.status === 'active')
224
- kits.status.status = 'sellable'
225
168
  const kitProducts = []
226
- if (properties?.type === 'kit' && product?.kit) {
227
- kits.type = 'kit'
228
- kits.availableQuantity = parseKitAvailableQuantity(product?.kit)
229
- for (const kit of product?.kit || []) {
230
- const kitProduct = formatKit(kit, market)
231
- kitProduct.id = kit?.identifier
232
- kitProducts.push({
233
- product: kitProduct,
234
- quantity: (kit?.properties || {})?.quantity || 0,
235
- isMandatory: true
236
- })
237
- }
238
- } else if (
239
- properties?.type === 'bundle' ||
240
- (properties?.type === 'kit' && properties?.requiredProducts)
241
- ) {
242
- kits.type = properties?.type
243
- kits.availableQuantity = parseBundleAvailableQuantity(properties)
169
+ const type = properties?.type === 'bundle' ? 'bundle' : 'kit'
170
+ kits.type = type
171
+ const details = getKitOrBundleDetails(properties)
172
+ kits.status = { status: details.status, isBackordered: !!(details.backOrderedQty !== 0 && details.latestBackOrderedDate !== null), backorderedAvailableDate: details.latestBackOrderedDate }
173
+ kits.maxQuantity = details.maxQty
174
+ kits.availableQuantity = details.availableQty
175
+ kits.backorderQuantity = details.backOrderedQty
176
+ kits.backorderedAvailableDate = details.latestBackOrderedDate
177
+ if (type === 'bundle') {
244
178
  for (const kit of properties?.requiredProducts || []) {
245
179
  if (kit?.product) {
246
- const kitProduct = formatKit(kit?.product, market)
247
- kitProduct.id = kit?.product?.id
248
180
  kitProducts?.push({
249
- product: kitProduct,
181
+ product: formatKit(kit?.product, market),
250
182
  quantity: kit?.quantity || 0,
251
183
  isMandatory: true
252
184
  })
@@ -254,17 +186,24 @@ const parseKit = (product, market) => {
254
186
  }
255
187
  for (const kit of properties?.optionalProducts || []) {
256
188
  if (kit?.product) {
257
- const kitProduct = formatKit(kit?.product, market)
258
- kitProduct.id = kit?.product?.id
259
189
  kitProducts?.push({
260
- product: kitProduct,
190
+ product: formatKit(kit?.product, market),
261
191
  quantity: kit?.quantity || 0,
262
192
  isMandatory: false
263
193
  })
264
194
  }
265
195
  }
196
+ } else if (type === 'kit') {
197
+ for (const kit of properties?.requiredProducts || []) {
198
+ kitProducts.push({
199
+ product: formatKit(kit?.product, market),
200
+ quantity: kit?.quantity || 0,
201
+ isMandatory: true
202
+ })
203
+ }
266
204
  }
267
205
  kits.kitProducts = kitProducts
206
+ if ((kits?.status || {})?.status === 'active') kits.status.status = 'sellable'
268
207
  return kits
269
208
  }
270
209
 
@@ -277,6 +216,7 @@ const formatKit = (product, market) => {
277
216
  if (['kit', 'bundle'].includes(product?.type)) {
278
217
  kit = parseKit(product?.bundle, market)
279
218
  }
219
+
280
220
  return {
281
221
  ...product,
282
222
  bundle: kit
@@ -285,9 +225,5 @@ const formatKit = (product, market) => {
285
225
 
286
226
  module.exports = {
287
227
  formatKit,
288
- parseKit,
289
- parseBundleAvailableQuantity,
290
- parseKitAvailableQuantity,
291
- parsePrice,
292
- parseStatus
228
+ parseKit
293
229
  }
@@ -2,110 +2,114 @@ const helpers = require('./helpers')
2
2
  const _ = require('lodash')
3
3
  const entities = require('entities')
4
4
 
5
- const formatVariant = (parent) => ({
5
+ const formatVariant = (parent) => {
6
+ const isBackOrdered = _.get(parent, 'defaults.status.backordered', null);
7
+ const backOrderedDate = _.get(parent, 'defaults.status.backorderedAvailableDate', null);
8
+ const preliminaryAvailableQty = _.get(parent, 'defaults.status.availableQuantity', 0);
9
+ const availableQty = isBackOrdered ? 0 : preliminaryAvailableQty;
10
+ const backOrderedQty = isBackOrdered ? availableQty : 0;
11
+
6
12
  // Each value is a function with a parameter 'parent' that is the current instance of a Variant
7
13
  // ContentStack references need to be queried separately
8
- id: parent?.uid,
9
- globalId: parent?.sap_data?.global_id,
10
- sku: helpers?.getBestSkuData(parent)?.sku,
11
- slug: parent?.slug,
12
- size: parent?.size,
13
- variantLabel: parent?.select_label,
14
- variantColor: parent?.select_color,
15
- title: parent?.name || parent?.defaults?.name,
16
- salesLabel: parent?.sales_label,
17
- salesText: parent?.sales_text,
18
- activeEventDisclaimer: helpers?.mapActiveEventDisclaimer(parent?.active_event_disclaimer),
19
- description: entities?.decodeHTML(
20
- parent?.short_description_override || parent?.defaults?.description || ''
21
- ),
22
- salesDisclaimer: helpers?.mapSalesDisclaimer(parent?.sales_disclaimer),
23
- nettoWeight: parent?.netto_weight,
24
- productImages: helpers?.mapProductImages(parent),
25
- isExclusive: parent?.is_exclusive,
26
- pricingJson: JSON.stringify(parent?.defaults?.status?.pricing) || null,
27
- price: helpers?.mapPrice(parent),
28
- points: helpers?.mapPoints(parent),
29
- availableQuantity: _.get(parent, 'defaults.status.availableQuantity', 0),
30
- maxQuantity: _.get(parent, 'defaults.status.maxQuantity', 0),
31
- status: {
32
- status: _.get(parent, 'defaults.status.status.retail', null),
33
- retail: _.get(parent, 'defaults.status.status.retail', null),
34
- wholesale: _.get(parent, 'defaults.status.status.wholesale', null),
35
- isBackordered: _.get(parent, 'defaults.status.backordered', null),
36
- backorderedAvailableDate: _.get(
37
- parent,
38
- 'defaults.status.backorderedAvailableDate',
39
- ''
40
- )
41
- },
42
- csStatus: helpers?.mapCsStatus(parent),
43
- purchaseTypes: helpers?.mapPurchaseTypes(parent),
44
- features: helpers?.mapFeatures(parent),
45
- productDetails: helpers?.mapProductDetails(parent),
46
- ingredients: helpers?.mapIngredients(
47
- parent.ingredients_override,
48
- parent.legacy_ingredients
49
- ),
50
- benefitsAndFeatures: helpers?.mapBenefitsAndFeatures(parent),
51
- results: helpers?.mapResults(parent),
52
- usage: helpers?.mapUsage(parent, parent.legacy_usage),
53
- resources: helpers?.mapResources(parent),
54
- faqs: helpers?.mapFaqs(parent),
55
- disclaimers: parent?.disclaimers || [],
56
- disclaimerWarnings: helpers?.mapDisclaimerWarnings(
57
- parent.disclaimer_warnings
58
- ),
59
- matchingVariant:
60
- parent.sap_data?.sku_data?.findIndex((s) =>
61
- parent.searchedSkus.includes(s.sku)
62
- ) > -1,
63
- shadeable: parent?.shadeable || false,
64
- restrictedMarkets: parent?.restricted_markets || [],
65
- productType: parent?.sap_data?.product_type,
66
- primaryBrand: parent.sap_data?.primary_brand,
67
- brandFamily: parent?.sap_data?.brand_family,
68
- marketAttributes: {
69
- discount: _.get(
70
- parent,
71
- 'defaults.status.marketAttributes.discount',
72
- null
14
+ return {
15
+ id: parent?.uid,
16
+ globalId: parent?.sap_data?.global_id,
17
+ sku: helpers?.getBestSkuData(parent)?.sku,
18
+ slug: parent?.slug,
19
+ size: parent?.size,
20
+ variantLabel: parent?.select_label,
21
+ variantColor: parent?.select_color,
22
+ title: parent?.name || parent?.defaults?.name,
23
+ salesLabel: parent?.sales_label,
24
+ salesText: parent?.sales_text,
25
+ activeEventDisclaimer: helpers?.mapActiveEventDisclaimer(parent?.active_event_disclaimer),
26
+ description: entities?.decodeHTML(
27
+ parent?.short_description_override || parent?.defaults?.description || ''
28
+ ),
29
+ salesDisclaimer: helpers?.mapSalesDisclaimer(parent?.sales_disclaimer),
30
+ nettoWeight: parent?.netto_weight,
31
+ productImages: helpers?.mapProductImages(parent),
32
+ isExclusive: parent?.is_exclusive,
33
+ pricingJson: JSON.stringify(parent?.defaults?.status?.pricing) || null,
34
+ price: helpers?.mapPrice(parent),
35
+ points: helpers?.mapPoints(parent),
36
+ backorderedAvailableDate: backOrderedDate,
37
+ backorderQuantity: backOrderedQty,
38
+ availableQuantity: availableQty,
39
+ maxQuantity: _.get(parent, 'defaults.status.maxQuantity', 0), // will be product status overrideMaxQty else its availableQty
40
+ status: {
41
+ status: _.get(parent, 'defaults.status.status.retail', null),
42
+ retail: _.get(parent, 'defaults.status.status.retail', null),
43
+ wholesale: _.get(parent, 'defaults.status.status.wholesale', null)
44
+ },
45
+ csStatus: helpers?.mapCsStatus(parent),
46
+ purchaseTypes: helpers?.mapPurchaseTypes(parent),
47
+ features: helpers?.mapFeatures(parent),
48
+ productDetails: helpers?.mapProductDetails(parent),
49
+ ingredients: helpers?.mapIngredients(
50
+ parent.ingredients_override,
51
+ parent.legacy_ingredients
73
52
  ),
74
- redeem: _.get(parent, 'defaults.status.marketAttributes.redeem', null),
75
- earn: _.get(parent, 'defaults.status.marketAttributes.earn', null)
76
- },
77
- legacyBenefits: parent.legacy_benefits,
78
- legacyLongDescription: parent.legacy_long_description,
79
- legacyResources: parent.legacy_resources,
80
- legacyUsage: parent.legacy_usage,
81
- legacyIngredients: parent.legacy_ingredients,
82
- legacyImages: parent.legacy_images || [],
83
- legacyYouTube: parent.legacy_youtube_video,
84
- csAtts: {
85
- version: parent._version || 0,
86
- updatedAt: parent.updated_at || '',
87
- updatedBy: parent.updated_by || '',
88
- createdAt: parent.created_at || '',
89
- createdBy: parent.created_by || '',
90
- locale: parent.locale || ''
91
- },
92
- searchKeywords: parent?.search_keywords,
93
- excludeFromSearch: parent?.exclude_from_search || helpers?.mapCsStatus(parent).status === 'discontinued' || _.get(parent, 'defaults.status.status.retail', null) === 'DISCONTINUED',
94
- customerTypes: helpers?.getBestSkuData(parent)?.status_selections
95
- ?.customer_types,
96
- availableChannels: helpers?.getBestSkuData(parent)?.status_selections
97
- ?.display_channels,
98
- displayChannels: helpers?.getBestSkuData(parent)?.status_selections
99
- ?.display_channels,
100
- scanQualifiedCount: helpers?.getBestSkuData(parent)?.scan_qualify_count,
101
- scanQualifyCount: helpers?.getBestSkuData(parent)?.scan_qualify_count,
102
- chargeShipping: helpers?.getBestSkuData(parent)?.charge_shipping,
103
- dangerousGoods: helpers?.getBestSkuData(parent)?.dangerous_goods,
104
- channels: helpers?.getBestSkuData(parent)?.channels,
105
- foreignLimit: helpers?.getBestSkuData(parent)?.foreign_limit,
106
- foreignOrderLimit: helpers?.getBestSkuData(parent)?.foreign_limit,
107
- orderType: parent.sap_data?.order_type
108
- })
53
+ benefitsAndFeatures: helpers?.mapBenefitsAndFeatures(parent),
54
+ results: helpers?.mapResults(parent),
55
+ usage: helpers?.mapUsage(parent, parent.legacy_usage),
56
+ resources: helpers?.mapResources(parent),
57
+ faqs: helpers?.mapFaqs(parent),
58
+ disclaimers: parent?.disclaimers || [],
59
+ disclaimerWarnings: helpers?.mapDisclaimerWarnings(
60
+ parent.disclaimer_warnings
61
+ ),
62
+ matchingVariant:
63
+ parent.sap_data?.sku_data?.findIndex((s) =>
64
+ parent.searchedSkus.includes(s.sku)
65
+ ) > -1,
66
+ shadeable: parent?.shadeable || false,
67
+ restrictedMarkets: parent?.restricted_markets || [],
68
+ productType: parent?.sap_data?.product_type,
69
+ primaryBrand: parent.sap_data?.primary_brand,
70
+ brandFamily: parent?.sap_data?.brand_family,
71
+ marketAttributes: {
72
+ discount: _.get(
73
+ parent,
74
+ 'defaults.status.marketAttributes.discount',
75
+ null
76
+ ),
77
+ redeem: _.get(parent, 'defaults.status.marketAttributes.redeem', null),
78
+ earn: _.get(parent, 'defaults.status.marketAttributes.earn', null)
79
+ },
80
+ legacyBenefits: parent.legacy_benefits,
81
+ legacyLongDescription: parent.legacy_long_description,
82
+ legacyResources: parent.legacy_resources,
83
+ legacyUsage: parent.legacy_usage,
84
+ legacyIngredients: parent.legacy_ingredients,
85
+ legacyImages: parent.legacy_images || [],
86
+ legacyYouTube: parent.legacy_youtube_video,
87
+ csAtts: {
88
+ version: parent._version || 0,
89
+ updatedAt: parent.updated_at || '',
90
+ updatedBy: parent.updated_by || '',
91
+ createdAt: parent.created_at || '',
92
+ createdBy: parent.created_by || '',
93
+ locale: parent.locale || ''
94
+ },
95
+ searchKeywords: parent?.search_keywords,
96
+ excludeFromSearch: parent?.exclude_from_search || helpers?.mapCsStatus(parent).status === 'discontinued' || _.get(parent, 'defaults.status.status.retail', null) === 'DISCONTINUED',
97
+ customerTypes: helpers?.getBestSkuData(parent)?.status_selections
98
+ ?.customer_types,
99
+ availableChannels: helpers?.getBestSkuData(parent)?.status_selections
100
+ ?.display_channels,
101
+ displayChannels: helpers?.getBestSkuData(parent)?.status_selections
102
+ ?.display_channels,
103
+ scanQualifiedCount: helpers?.getBestSkuData(parent)?.scan_qualify_count,
104
+ scanQualifyCount: helpers?.getBestSkuData(parent)?.scan_qualify_count,
105
+ chargeShipping: helpers?.getBestSkuData(parent)?.charge_shipping,
106
+ dangerousGoods: helpers?.getBestSkuData(parent)?.dangerous_goods,
107
+ channels: helpers?.getBestSkuData(parent)?.channels,
108
+ foreignLimit: helpers?.getBestSkuData(parent)?.foreign_limit,
109
+ foreignOrderLimit: helpers?.getBestSkuData(parent)?.foreign_limit,
110
+ orderType: parent.sap_data?.order_type
111
+ }
112
+ }
109
113
 
110
114
  module.exports = {
111
115
  formatVariant
@@ -45,6 +45,7 @@ async function getProductStatus(skus, locale, homeUrl, awsBase, clientId) {
45
45
  const overrideMaxQuantity = marketConfig.skuMaxQty
46
46
  ? marketConfig.skuMaxQty[skus[i]]
47
47
  : undefined
48
+ // @TODO - Update this code when we update our config for minimum availableQuantity in CS
48
49
  const availableQuantity =
49
50
  productStatus.availableQuantity < 999
50
51
  ? productStatus.availableQuantity