@nuskin/cs-product-lib 1.0.26 → 1.0.27-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.0.26",
3
+ "version": "1.0.27-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
@@ -132,6 +132,9 @@ const csProduct = (props) => {
132
132
  validVariants.push({
133
133
  ...variant,
134
134
  status: varStatus,
135
+ backorderQuantity: varStatus?.backordered ? varStatus?.availableQuantity : 0,
136
+ availableQuantity: varStatus?.backordered ? 0 : varStatus?.availableQuantity,
137
+ backorderedAvailableDate: varStatus?.backorderedAvailableDate || null,
135
138
  marketData
136
139
  })
137
140
  }
package/src/formatKit.js CHANGED
@@ -16,52 +16,40 @@ const parseStatus = (variant) => {
16
16
  let backorderedAvailableDate = null
17
17
  if (variant?.type === 'kit') {
18
18
  try {
19
- /* eslint-disable no-unsafe-optional-chaining */
20
- const latestBODate = Math.max(
21
- ...variant?.requiredProducts?.flatMap((a) =>
22
- a?.product?.variants?.map(
23
- (s) =>
24
- Number(
25
- new Date(s?.status?.backorderedAvailableDate)
26
- ) || 0
27
- )
28
- )
29
- )
19
+ let dates = [];
20
+ variant?.requiredProducts?.flatMap(a => a?.product?.variants?.map(s => {
21
+ const backorderDate = s?.status?.backorderedAvailableDate || null;
22
+ dates.push(moment(backorderDate))
23
+ }))
24
+ dates = dates.filter(date => date.isValid());
25
+ const latestBODate = dates && dates.length > 0 ? moment.max(dates).format(dateFormat) : null;
30
26
  if (latestBODate) {
31
27
  isBackordered = true
32
- backorderedAvailableDate = moment(
33
- new Date(latestBODate)
34
- ).format(dateFormat)
28
+ backorderedAvailableDate = latestBODate
35
29
  }
36
30
  } catch {
37
31
  backorderedAvailableDate = null
38
32
  }
39
33
  } else if (variant?.type === 'bundle') {
40
34
  try {
41
- const latestBODate = Math.max(
42
- ...variant?.requiredProducts?.flatMap(
43
- (a) =>
44
- Number(
45
- new Date(
46
- a?.product?.variants[0]?.status?.backorderedAvailableDate
47
- )
48
- ) || 0
49
- )
50
- )
35
+ let dates = [];
36
+ variant?.requiredProducts?.flatMap(a => a?.product?.variants?.map(s => {
37
+ const backorderDate = s?.status?.backorderedAvailableDate || null;
38
+ dates.push(moment(backorderDate))
39
+ }))
40
+ dates = dates.filter(date => date.isValid());
41
+ const latestBODate = dates && dates.length > 0 ? moment.max(dates).format(dateFormat) : null;
51
42
  if (latestBODate) {
52
43
  isBackordered = true
53
- backorderedAvailableDate = moment(
54
- new Date(latestBODate)
55
- ).format(dateFormat)
44
+ backorderedAvailableDate = latestBODate
56
45
  }
57
46
  } catch {
58
47
  backorderedAvailableDate = null
59
48
  }
60
49
  }
61
50
  return {
62
- // changed
63
- status: variant?.status?.status,
64
- isBackordered: isBackordered,
51
+ status: variant?.status?.status,
52
+ isBackordered: isBackordered,
65
53
  backorderedAvailableDate: backorderedAvailableDate
66
54
  }
67
55
  }
@@ -154,36 +142,72 @@ const parsePrice = (product = []) => {
154
142
  return sumOfPriceAndPoints
155
143
  }
156
144
 
157
- const parseKitAvailableQuantity = (kit) => {
145
+ const getBundleQuantities = (properties) => {
146
+ let maxQty = null;
147
+ let backOrderedQty = null;
148
+ let availableQty = null;
149
+ let quantities = [];
150
+ let availableQuantities = [];
151
+ let prodQty = 0;
158
152
  try {
159
- return (
160
- Math.min(
161
- ...kit.flatMap((a) =>
162
- a?.sku?.map((s) => s?.inventoryProperties?.atpQty)
163
- )
164
- ) || 0
165
- )
166
- } catch {
167
- return 0
153
+ properties?.requiredProducts?.flatMap(a => {
154
+ prodQty = Number(a?.quantity || 1);
155
+ a?.product?.variants?.map(s => {
156
+ const backorderQty = s?.backorderQuantity || 0;
157
+ const availableQty = s?.availableQuantity || 0;
158
+ quantities.push({maxPurchasable: (backorderQty + availableQty)/prodQty});
159
+ availableQuantities.push({availableQty});
160
+ })
161
+ })
162
+ const maxQtyPurchasable = quantities && quantities.length > 0 ? Math.min(...quantities.map(obj=>obj.maxPurchasable)) : 0;
163
+ const maxAvailableQty = availableQuantities && availableQuantities.length > 0 ? Math.min(...availableQuantities.map(obj=>obj.availableQty)) : 0;
164
+ maxQty = Math.floor(maxQtyPurchasable);
165
+ backOrderedQty = maxQty - maxAvailableQty;
166
+ availableQty = maxAvailableQty;
167
+ } catch (error) {
168
+ console.error('back order qty, available qty, or max qty error', error);
169
+ maxQty = 0;
170
+ backOrderedQty = 0;
171
+ availableQty = 0;
168
172
  }
173
+ return {maxQty, backOrderedQty, availableQty}
169
174
  }
170
175
 
171
- const parseBundleAvailableQuantity = (product) => {
176
+ const getKitQuantities = (properties) => {
177
+ let maxQty = null;
178
+ let backOrderedQty = null;
179
+ let availableQty = null;
180
+ let quantities = [];
181
+ let availableQuantities = [];
182
+ let prodQty = 0;
172
183
  try {
173
- const { requiredProducts = [] } = !isNullOrEmpty(product) ? product : {}
174
- const quantities = requiredProducts?.map((p) =>
175
- p?.product?.variants?.map((a) => a?.availableQuantity)
176
- )
177
- return Math.min(...quantities) || 0
178
- } catch {
179
- console.log('some error in availableQuantity')
180
- return 0
184
+ properties?.flatMap(a => {
185
+ prodQty = Number(a?.properties?.quantity || 1);
186
+ a.sku.map(s => {
187
+ const backorderQty = s?.inventoryProperties?.backOrderedQty|| 0;
188
+ const availableQty = s?.inventoryProperties?.atpQty || 0;
189
+ quantities.push({maxPurchasable: (backorderQty + availableQty)/prodQty});
190
+ availableQuantities.push({availableQty});
191
+ })
192
+ })
193
+ const maxQtyPurchasable = quantities && quantities.length > 0 ? Math.min(...quantities.map(obj=>obj.maxPurchasable)) : 0;
194
+ const maxAvailableQty = availableQuantities && availableQuantities.length > 0 ? Math.min(...availableQuantities.map(obj=>obj.availableQty)) : 0;
195
+ maxQty = Math.floor(maxQtyPurchasable);
196
+ backOrderedQty = maxQty - maxAvailableQty;
197
+ availableQty = maxAvailableQty;
198
+ } catch (error) {
199
+ console.error('back order qty, available qty, or max qty error', error);
200
+ maxQty = 0;
201
+ backOrderedQty = 0;
202
+ availableQty = 0;
181
203
  }
204
+ return {maxQty, backOrderedQty, availableQty}
182
205
  }
183
206
 
184
207
  const parseKit = (product, market) => {
185
208
  const kits = {}
186
209
  const [properties] = !isNullOrEmpty(product) ? product : []
210
+ const status = parseStatus(properties)
187
211
  kits.id = properties?.id
188
212
  if (properties?.type === 'kit' && properties?.legacy_sku) {
189
213
  kits.id = properties?.legacy_sku
@@ -195,11 +219,9 @@ const parseKit = (product, market) => {
195
219
  kits.chargeShipping = properties?.chargeShipping === 'true'
196
220
  kits.customerTypes = properties?.customerTypes
197
221
  kits.dangerousGoods = properties?.dangerousGoods === 'true'
198
- // missing
199
222
  kits.excludeFromSearch = properties?.excludeFromSearch === 'true'
200
223
  kits.isExclusive = properties?.isExclusive === 'true'
201
224
  kits.marketAttributes = properties?.marketAttributes
202
- kits.maxQuantity = (product?.inventoryProperties || {}).atpQty
203
225
  const priceData = parsePrice(product)
204
226
  kits.points = priceData?.points
205
227
  kits.price = priceData?.price
@@ -215,14 +237,16 @@ const parseKit = (product, market) => {
215
237
  kits.grpDiscount = properties?.grpDiscount
216
238
  kits.scanQualifiedCount = properties?.scanQualifiedCount
217
239
  kits.taxClass = properties?.taxClass
218
- // newVariant.slug = undefined
219
- kits.status = parseStatus(properties)
220
- if ((kits?.status || {})?.status === 'active')
221
- kits.status.status = 'sellable'
240
+ kits.status = status
241
+ if ((kits?.status || {})?.status === 'active') kits.status.status = 'sellable'
222
242
  const kitProducts = []
223
243
  if (properties?.type === 'kit' && product?.kit) {
224
244
  kits.type = 'kit'
225
- kits.availableQuantity = parseKitAvailableQuantity(product?.kit)
245
+ const quantities = getKitQuantities(properties)
246
+ kits.maxQuantity = quantities.maxQty
247
+ kits.availableQuantity = quantities.availableQty
248
+ kits.backorderQuantity = quantities.backOrderedQty
249
+ kits.backorderedAvailableDate = status?.backorderedAvailableDate
226
250
  for (const kit of product?.kit || []) {
227
251
  const kitProduct = formatKit(kit, market)
228
252
  kitProduct.id = kit?.identifier
@@ -237,7 +261,11 @@ const parseKit = (product, market) => {
237
261
  (properties?.type === 'kit' && properties?.requiredProducts)
238
262
  ) {
239
263
  kits.type = properties?.type
240
- kits.availableQuantity = parseBundleAvailableQuantity(properties)
264
+ const quantities = getBundleQuantities(properties)
265
+ kits.maxQuantity = quantities.maxQty
266
+ kits.availableQuantity = quantities.availableQty
267
+ kits.backorderQuantity = quantities.backOrderedQty
268
+ kits.backorderedAvailableDate = status?.backorderedAvailableDate
241
269
  for (const kit of properties?.requiredProducts || []) {
242
270
  if (kit?.product) {
243
271
  const kitProduct = formatKit(kit?.product, market)
@@ -283,8 +311,8 @@ const formatKit = (product, market) => {
283
311
  module.exports = {
284
312
  formatKit,
285
313
  parseKit,
286
- parseBundleAvailableQuantity,
287
- parseKitAvailableQuantity,
314
+ getKitQuantities,
315
+ getBundleQuantities,
288
316
  parsePrice,
289
317
  parseStatus
290
318
  }
@@ -2,111 +2,117 @@ 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
- description: entities?.decodeHTML(
19
- parent?.short_description_override || parent?.defaults?.description || ''
20
- ),
21
- salesDisclaimer: helpers?.mapSalesDisclaimer(parent?.sales_disclaimer),
22
- nettoWeight: parent?.netto_weight,
23
- productImages: helpers?.mapProductImages(parent),
24
- isExclusive: parent?.is_exclusive,
25
- pricingJson: JSON.stringify(parent?.defaults?.status?.pricing) || null,
26
- price: helpers?.mapPrice(parent),
27
- points: helpers?.mapPoints(parent),
28
- availableQuantity: _.get(parent, 'defaults.status.availableQuantity', 0),
29
- maxQuantity: _.get(parent, 'defaults.status.maxQuantity', 0),
30
- status: {
31
- status: _.get(parent, 'defaults.status.status.retail', null),
32
- retail: _.get(parent, 'defaults.status.status.retail', null),
33
- wholesale: _.get(parent, 'defaults.status.status.wholesale', null),
34
- isBackordered: _.get(parent, 'defaults.status.backordered', null),
35
- backorderedAvailableDate: _.get(
36
- parent,
37
- 'defaults.status.backorderedAvailableDate',
38
- ''
39
- )
40
- },
41
- csStatus: helpers?.mapCsStatus(parent),
42
- purchaseTypes: helpers?.mapPurchaseTypes(parent),
43
- features: helpers?.mapFeatures(parent),
44
- productDetails: helpers?.mapProductDetails(parent),
45
- ingredients: helpers?.mapIngredients(
46
- parent.ingredients_override,
47
- parent.legacy_ingredients
48
- ),
49
- benefitsAndFeatures: helpers?.mapBenefitsAndFeatures(parent),
50
- results: helpers?.mapResults(parent),
51
- usage: helpers?.mapUsage(parent, parent.legacy_usage),
52
- resources: helpers?.mapResources(parent),
53
- faqs: helpers?.mapFaqs(parent),
54
- disclaimers: parent?.disclaimers || [],
55
- disclaimerWarnings: helpers?.mapDisclaimerWarnings(
56
- parent.disclaimer_warnings
57
- ),
58
- matchingVariant:
59
- parent.sap_data?.sku_data?.findIndex((s) =>
60
- parent.searchedSkus.includes(s.sku)
61
- ) > -1,
62
- shadeable: parent?.shadeable || false,
63
- restrictedMarkets: parent?.restricted_markets || [],
64
- productType: parent?.sap_data?.product_type,
65
- primaryBrand: parent.sap_data?.primary_brand,
66
- brandFamily: parent?.sap_data?.brand_family,
67
- marketAttributes: {
68
- discount: _.get(
69
- parent,
70
- 'defaults.status.marketAttributes.discount',
71
- 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
+ description: entities?.decodeHTML(
26
+ parent?.short_description_override || parent?.defaults?.description || ''
27
+ ),
28
+ salesDisclaimer: helpers?.mapSalesDisclaimer(parent?.sales_disclaimer),
29
+ nettoWeight: parent?.netto_weight,
30
+ productImages: helpers?.mapProductImages(parent),
31
+ isExclusive: parent?.is_exclusive,
32
+ pricingJson: JSON.stringify(parent?.defaults?.status?.pricing) || null,
33
+ price: helpers?.mapPrice(parent),
34
+ points: helpers?.mapPoints(parent),
35
+ backorderedAvailableDate: backOrderedDate,
36
+ backorderQuantity: backOrderedQty,
37
+ availableQuantity: availableQty,
38
+ maxQuantity: _.get(parent, 'defaults.status.maxQuantity', 0), // will be product status overrideMaxQty else its availableQty
39
+ status: {
40
+ status: _.get(parent, 'defaults.status.status.retail', null),
41
+ retail: _.get(parent, 'defaults.status.status.retail', null),
42
+ wholesale: _.get(parent, 'defaults.status.status.wholesale', null),
43
+ isBackordered: isBackOrdered,
44
+ backorderedAvailableDate: backOrderedDate
45
+ },
46
+ csStatus: helpers?.mapCsStatus(parent),
47
+ purchaseTypes: helpers?.mapPurchaseTypes(parent),
48
+ features: helpers?.mapFeatures(parent),
49
+ productDetails: helpers?.mapProductDetails(parent),
50
+ ingredients: helpers?.mapIngredients(
51
+ parent.ingredients_override,
52
+ parent.legacy_ingredients
72
53
  ),
73
- redeem: _.get(parent, 'defaults.status.marketAttributes.redeem', null),
74
- earn: _.get(parent, 'defaults.status.marketAttributes.earn', null)
75
- },
76
- legacyBenefits: parent.legacy_benefits,
77
- legacyLongDescription: parent.legacy_long_description,
78
- legacyResources: parent.legacy_resources,
79
- legacyUsage: parent.legacy_usage,
80
- legacyIngredients: parent.legacy_ingredients,
81
- legacyImages: parent.legacy_images || [],
82
- legacyYouTube: parent.legacy_youtube_video,
83
- csAtts: {
84
- version: parent._version || 0,
85
- updatedAt: parent.updated_at || '',
86
- updatedBy: parent.updated_by || '',
87
- createdAt: parent.created_at || '',
88
- createdBy: parent.created_by || '',
89
- locale: parent.locale || ''
90
- },
91
- searchKeywords: parent?.search_keywords,
92
- excludeFromSearch: parent?.exclude_from_search || helpers?.mapCsStatus(parent).status === 'discontinued' || _.get(parent, 'defaults.status.status.retail', null) === 'DISCONTINUED',
93
- customerTypes: helpers?.getBestSkuData(parent)?.status_selections
94
- ?.customer_types,
95
- availableChannels: helpers?.getBestSkuData(parent)?.status_selections
96
- ?.display_channels,
97
- displayChannels: helpers?.getBestSkuData(parent)?.status_selections
98
- ?.display_channels,
99
- scanQualifiedCount: helpers?.getBestSkuData(parent)?.status_selections
100
- ?.scan_qualify_count,
101
- scanQualifyCount: helpers?.getBestSkuData(parent)?.status_selections
102
- ?.scan_qualify_count,
103
- chargeShipping: helpers?.getBestSkuData(parent)?.charge_shipping,
104
- dangerousGoods: helpers?.getBestSkuData(parent)?.dangerous_goods,
105
- channels: helpers?.getBestSkuData(parent)?.channels,
106
- foreignLimit: helpers?.getBestSkuData(parent)?.foreign_limit,
107
- foreignOrderLimit: helpers?.getBestSkuData(parent)?.foreign_limit,
108
- orderType: parent.sap_data?.order_type
109
- })
54
+ benefitsAndFeatures: helpers?.mapBenefitsAndFeatures(parent),
55
+ results: helpers?.mapResults(parent),
56
+ usage: helpers?.mapUsage(parent, parent.legacy_usage),
57
+ resources: helpers?.mapResources(parent),
58
+ faqs: helpers?.mapFaqs(parent),
59
+ disclaimers: parent?.disclaimers || [],
60
+ disclaimerWarnings: helpers?.mapDisclaimerWarnings(
61
+ parent.disclaimer_warnings
62
+ ),
63
+ matchingVariant:
64
+ parent.sap_data?.sku_data?.findIndex((s) =>
65
+ parent.searchedSkus.includes(s.sku)
66
+ ) > -1,
67
+ shadeable: parent?.shadeable || false,
68
+ restrictedMarkets: parent?.restricted_markets || [],
69
+ productType: parent?.sap_data?.product_type,
70
+ primaryBrand: parent.sap_data?.primary_brand,
71
+ brandFamily: parent?.sap_data?.brand_family,
72
+ marketAttributes: {
73
+ discount: _.get(
74
+ parent,
75
+ 'defaults.status.marketAttributes.discount',
76
+ null
77
+ ),
78
+ redeem: _.get(parent, 'defaults.status.marketAttributes.redeem', null),
79
+ earn: _.get(parent, 'defaults.status.marketAttributes.earn', null)
80
+ },
81
+ legacyBenefits: parent.legacy_benefits,
82
+ legacyLongDescription: parent.legacy_long_description,
83
+ legacyResources: parent.legacy_resources,
84
+ legacyUsage: parent.legacy_usage,
85
+ legacyIngredients: parent.legacy_ingredients,
86
+ legacyImages: parent.legacy_images || [],
87
+ legacyYouTube: parent.legacy_youtube_video,
88
+ csAtts: {
89
+ version: parent._version || 0,
90
+ updatedAt: parent.updated_at || '',
91
+ updatedBy: parent.updated_by || '',
92
+ createdAt: parent.created_at || '',
93
+ createdBy: parent.created_by || '',
94
+ locale: parent.locale || ''
95
+ },
96
+ searchKeywords: parent?.search_keywords,
97
+ excludeFromSearch: parent?.exclude_from_search || helpers?.mapCsStatus(parent).status === 'discontinued' || _.get(parent, 'defaults.status.status.retail', null) === 'DISCONTINUED',
98
+ customerTypes: helpers?.getBestSkuData(parent)?.status_selections
99
+ ?.customer_types,
100
+ availableChannels: helpers?.getBestSkuData(parent)?.status_selections
101
+ ?.display_channels,
102
+ displayChannels: helpers?.getBestSkuData(parent)?.status_selections
103
+ ?.display_channels,
104
+ scanQualifiedCount: helpers?.getBestSkuData(parent)?.status_selections
105
+ ?.scan_qualify_count,
106
+ scanQualifyCount: helpers?.getBestSkuData(parent)?.status_selections
107
+ ?.scan_qualify_count,
108
+ chargeShipping: helpers?.getBestSkuData(parent)?.charge_shipping,
109
+ dangerousGoods: helpers?.getBestSkuData(parent)?.dangerous_goods,
110
+ channels: helpers?.getBestSkuData(parent)?.channels,
111
+ foreignLimit: helpers?.getBestSkuData(parent)?.foreign_limit,
112
+ foreignOrderLimit: helpers?.getBestSkuData(parent)?.foreign_limit,
113
+ orderType: parent.sap_data?.order_type
114
+ }
115
+ }
110
116
 
111
117
  module.exports = {
112
118
  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