@nuskin/cs-product-lib 1.8.0-bundleonly-members.1 → 1.8.0-bundleonly-members.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 +1 -1
- package/src/csProduct.js +80 -44
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nuskin/cs-product-lib",
|
|
3
|
-
"version": "1.8.0-bundleonly-members.
|
|
3
|
+
"version": "1.8.0-bundleonly-members.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
|
@@ -42,9 +42,34 @@ const validateConfig = (props) => {
|
|
|
42
42
|
/* istanbul ignore next */
|
|
43
43
|
const csProduct = (props) => {
|
|
44
44
|
const DEV_ENVIRONMENT = 'public-dev'
|
|
45
|
-
// CS-native sellable gate for the bypassProductStatus path -- mirrors
|
|
46
|
-
// content-validator ValidationSteps (csStatus === 'sellable' || 'bundleOnly').
|
|
45
|
+
// CS-native sellable gate for the bypassProductStatus path -- mirrors
|
|
46
|
+
// content-validator ValidationSteps (csStatus === 'sellable' || 'bundleOnly').
|
|
47
47
|
const CS_SELLABLE_STATUSES = ['sellable', 'bundleOnly']
|
|
48
|
+
|
|
49
|
+
// Kit-member dedup tiebreak (order-independent). A kit member SKU can match
|
|
50
|
+
// more than one product entry (e.g. a market-localized product AND an older
|
|
51
|
+
// en-only promo product that still carries the same variant). Prefer the
|
|
52
|
+
// entry whose own locale matches the requested market locale; if that does
|
|
53
|
+
// not disambiguate, prefer the newer publish, then a stable uid -- so the
|
|
54
|
+
// kept entry never depends on query/array order.
|
|
55
|
+
const kitMemberLocaleRank = (member, requestedLocale) => {
|
|
56
|
+
const memberLocale = member?.csAtts?.locale
|
|
57
|
+
return memberLocale && requestedLocale &&
|
|
58
|
+
memberLocale.toLowerCase() === requestedLocale.toLowerCase()
|
|
59
|
+
? 1
|
|
60
|
+
: 0
|
|
61
|
+
}
|
|
62
|
+
const isPreferredKitMember = (candidate, current, requestedLocale) => {
|
|
63
|
+
if (!current) return true
|
|
64
|
+
const candRank = kitMemberLocaleRank(candidate, requestedLocale)
|
|
65
|
+
const currRank = kitMemberLocaleRank(current, requestedLocale)
|
|
66
|
+
if (candRank !== currRank) return candRank > currRank
|
|
67
|
+
const candTime = Date.parse(candidate?.csAtts?.updatedAt) || 0
|
|
68
|
+
const currTime = Date.parse(current?.csAtts?.updatedAt) || 0
|
|
69
|
+
if (candTime !== currTime) return candTime > currTime
|
|
70
|
+
return `${candidate?.id}` < `${current?.id}`
|
|
71
|
+
}
|
|
72
|
+
|
|
48
73
|
const VARIANTS = 'variants'
|
|
49
74
|
const VARIANT = 'variant'
|
|
50
75
|
const KEY_INGREDIENTS = 'ingredients.key_ingredients'
|
|
@@ -286,52 +311,63 @@ const csProduct = (props) => {
|
|
|
286
311
|
})
|
|
287
312
|
}
|
|
288
313
|
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
const
|
|
292
|
-
|
|
293
|
-
|
|
314
|
+
// Clone a product and keep only the variant matching the given sku.
|
|
315
|
+
const selectVariantProduct = (product, variantSku) => {
|
|
316
|
+
const productSelect = JSON.parse(JSON.stringify(product))
|
|
317
|
+
productSelect.variants = (productSelect?.variants || []).filter(
|
|
318
|
+
(variant) => variant.sku === variantSku
|
|
319
|
+
)
|
|
320
|
+
return productSelect
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// Build the kit's required-product list, deduped by sku+quantity. A member
|
|
324
|
+
// sku can match more than one product entry; keep the preferred one
|
|
325
|
+
// (locale-native over fallback) via isPreferredKitMember, order-independent.
|
|
326
|
+
const dedupeKitMembers = (products, variantSkus, kitVariants, requestedLocale) => {
|
|
327
|
+
const bestBySkuQuantity = new Map()
|
|
328
|
+
for (const product of products || []) {
|
|
329
|
+
for (const variantSku of variantSkus) {
|
|
330
|
+
const productSelect = selectVariantProduct(product, variantSku)
|
|
331
|
+
if (isNullOrEmpty(productSelect?.variants)) continue
|
|
332
|
+
const quantity = kitVariants.find((v) => v.sku === variantSku)?.quantity
|
|
333
|
+
const dedupKey = `${variantSku}:${quantity}`
|
|
334
|
+
const current = bestBySkuQuantity.get(dedupKey)
|
|
335
|
+
if (isPreferredKitMember(productSelect, current?.product?.[0], requestedLocale)) {
|
|
336
|
+
bestBySkuQuantity.set(dedupKey, { product: [productSelect], quantity })
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
return [...bestBySkuQuantity.values()]
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
const convertKitToRequiredProducts = async (skuKit, locale, bypassProductStatus = false) => {
|
|
344
|
+
let productBundle = {}
|
|
345
|
+
const variantSkus = []
|
|
346
|
+
for (let kitItem of skuKit[0].variants) {
|
|
347
|
+
const skuData = helpers.getBestSkuData((kitItem.variant || [])[0])
|
|
294
348
|
variantSkus.push((skuData || {}).sku)
|
|
295
349
|
kitItem.sku = (skuData || {}).sku
|
|
296
350
|
}
|
|
297
351
|
const products = await getProductsBySku(variantSkus, locale, true, bypassProductStatus)
|
|
298
|
-
const requiredProducts =
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
})
|
|
318
|
-
}
|
|
319
|
-
}
|
|
320
|
-
}
|
|
321
|
-
}
|
|
322
|
-
const discounts = skuKit[0].discounts || {}
|
|
323
|
-
const dangerousGoods = requiredProducts.some(({ product }) => {
|
|
324
|
-
const [requiredProduct] = product || []
|
|
325
|
-
const [variant] = requiredProduct?.variants || []
|
|
326
|
-
return variant?.dangerousGoods === true
|
|
327
|
-
})
|
|
328
|
-
productBundle.required_products = requiredProducts
|
|
329
|
-
productBundle.charge_shipping = skuKit[0].charge_shipping
|
|
330
|
-
productBundle.cvdis = discounts.cvDiscount || skuKit[0].cvdis
|
|
331
|
-
productBundle.dangerous_goods = dangerousGoods
|
|
332
|
-
productBundle.foreign_limit = skuKit[0].foreign_limit
|
|
333
|
-
productBundle.grpdis = discounts.grpDiscount || skuKit[0].grpdis
|
|
334
|
-
productBundle.is_exclusive = skuKit[0].is_exclusive
|
|
352
|
+
const requiredProducts = dedupeKitMembers(
|
|
353
|
+
products,
|
|
354
|
+
variantSkus,
|
|
355
|
+
skuKit[0].variants,
|
|
356
|
+
badLocaleMap(locale)
|
|
357
|
+
)
|
|
358
|
+
const discounts = skuKit[0].discounts || {}
|
|
359
|
+
const dangerousGoods = requiredProducts.some(({ product }) => {
|
|
360
|
+
const [requiredProduct] = product || []
|
|
361
|
+
const [variant] = requiredProduct?.variants || []
|
|
362
|
+
return variant?.dangerousGoods === true
|
|
363
|
+
})
|
|
364
|
+
productBundle.required_products = requiredProducts
|
|
365
|
+
productBundle.charge_shipping = skuKit[0].charge_shipping
|
|
366
|
+
productBundle.cvdis = discounts.cvDiscount || skuKit[0].cvdis
|
|
367
|
+
productBundle.dangerous_goods = dangerousGoods
|
|
368
|
+
productBundle.foreign_limit = skuKit[0].foreign_limit
|
|
369
|
+
productBundle.grpdis = discounts.grpDiscount || skuKit[0].grpdis
|
|
370
|
+
productBundle.is_exclusive = skuKit[0].is_exclusive
|
|
335
371
|
productBundle.bundle_id = skuKit[0].legacy_sku || skuKit[0].kit_id
|
|
336
372
|
productBundle.locale = skuKit[0].locale
|
|
337
373
|
productBundle.point_discount = skuKit[0].point_discount
|