@nuskin/cs-product-lib 1.0.24 → 1.0.25-cx12-8656.2

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.24",
3
+ "version": "1.0.25-cx12-8656.2",
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
  kits.uid = properties?.uid
189
213
  kits.title = properties?.title
@@ -192,11 +216,9 @@ const parseKit = (product, market) => {
192
216
  kits.chargeShipping = properties?.chargeShipping === 'true'
193
217
  kits.customerTypes = properties?.customerTypes
194
218
  kits.dangerousGoods = properties?.dangerousGoods === 'true'
195
- // missing
196
219
  kits.excludeFromSearch = properties?.excludeFromSearch === 'true'
197
220
  kits.isExclusive = properties?.isExclusive === 'true'
198
221
  kits.marketAttributes = properties?.marketAttributes
199
- kits.maxQuantity = (product?.inventoryProperties || {}).atpQty
200
222
  const priceData = parsePrice(product)
201
223
  kits.points = priceData?.points
202
224
  kits.price = priceData?.price
@@ -212,14 +234,16 @@ const parseKit = (product, market) => {
212
234
  kits.grpDiscount = properties?.grpDiscount
213
235
  kits.scanQualifiedCount = properties?.scanQualifiedCount
214
236
  kits.taxClass = properties?.taxClass
215
- // newVariant.slug = undefined
216
- kits.status = parseStatus(properties)
217
- if ((kits?.status || {})?.status === 'active')
218
- kits.status.status = 'sellable'
237
+ kits.status = status
238
+ if ((kits?.status || {})?.status === 'active') kits.status.status = 'sellable'
219
239
  const kitProducts = []
220
240
  if (properties?.type === 'kit' && product?.kit) {
221
241
  kits.type = 'kit'
222
- kits.availableQuantity = parseKitAvailableQuantity(product?.kit)
242
+ const quantities = getKitQuantities(properties)
243
+ kits.maxQuantity = quantities.maxQty
244
+ kits.availableQuantity = quantities.availableQty
245
+ kits.backorderQuantity = quantities.backOrderedQty
246
+ kits.backorderedAvailableDate = status?.backorderedAvailableDate
223
247
  for (const kit of product?.kit || []) {
224
248
  const kitProduct = formatKit(kit, market)
225
249
  kitProduct.id = kit?.identifier
@@ -234,7 +258,11 @@ const parseKit = (product, market) => {
234
258
  (properties?.type === 'kit' && properties?.requiredProducts)
235
259
  ) {
236
260
  kits.type = properties?.type
237
- kits.availableQuantity = parseBundleAvailableQuantity(properties)
261
+ const quantities = getBundleQuantities(properties)
262
+ kits.maxQuantity = quantities.maxQty
263
+ kits.availableQuantity = quantities.availableQty
264
+ kits.backorderQuantity = quantities.backOrderedQty
265
+ kits.backorderedAvailableDate = status?.backorderedAvailableDate
238
266
  for (const kit of properties?.requiredProducts || []) {
239
267
  if (kit?.product) {
240
268
  const kitProduct = formatKit(kit?.product, market)
@@ -280,8 +308,8 @@ const formatKit = (product, market) => {
280
308
  module.exports = {
281
309
  formatKit,
282
310
  parseKit,
283
- parseBundleAvailableQuantity,
284
- parseKitAvailableQuantity,
311
+ getKitQuantities,
312
+ getBundleQuantities,
285
313
  parsePrice,
286
314
  parseStatus
287
315
  }
@@ -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