diff --git a/CHANGELOG.md b/CHANGELOG.md index cbf008ec13..e9211497d4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,6 +29,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Fix overlapping text in PersonalDetails component - @jakubmakielkowski (#4024) - Redirect from checkout to home with a proper store code - @Fifciu - Added back error notification when user selects invalid configuration - @1070rik (#4033) +- findConfigurableChildAsync - return best match for configurable variant - @gibkigonzo (#4042) ### Changed / Improved - Optimized `translation.processor` to process only enabled locale CSV files - @pkarw (#3950) diff --git a/core/modules/catalog/helpers/index.ts b/core/modules/catalog/helpers/index.ts index b7f0877a74..ed0ed4314f 100644 --- a/core/modules/catalog/helpers/index.ts +++ b/core/modules/catalog/helpers/index.ts @@ -5,6 +5,7 @@ import omit from 'lodash-es/omit' import remove from 'lodash-es/remove' import toString from 'lodash-es/toString' import union from 'lodash-es/union' +import isObject from 'lodash-es/union' // TODO: Remove this dependency import { optionLabel } from './optionLabel' import i18n from '@vue-storefront/i18n' @@ -42,8 +43,25 @@ const getVariantWithLowestPrice = (prevVariant, nextVariant) => ( nextVariant.price_incl_tax <= prevVariant.price_incl_tax // prev variant price is higher then next ) ? nextVariant : prevVariant +/** + * Counts how much coniguration match for specific variant + */ +const getConfigurationMatchLevel = (configuration, variant): number => { + if (!variant || !configuration) return 0 + const configProperties = Object.keys(omit(configuration, ['price'])) + return configProperties + .map(configProperty => { + const filter = configuration[configProperty] + const configurationId = filter && isObject(filter) && filter.id + const variantPropertyId = variant[configProperty] + return (configurationId && variantPropertyId) && + (toString(configurationId) === toString(variantPropertyId)) + }) + .filter(Boolean) + .length +} + export function findConfigurableChildAsync ({ product, configuration = null, selectDefaultChildren = false, availabilityCheck = true }) { - const regularProductPrice = product.original_price_incl_tax ? product.original_price_incl_tax : product.price_incl_tax const selectedVariant = product.configurable_children.reduce((prevVariant, nextVariant) => { if (availabilityCheck) { if (nextVariant.stock && !config.products.listOutOfStockProducts) { @@ -61,23 +79,14 @@ export function findConfigurableChildAsync ({ product, configuration = null, sel if (configuration.sku && nextVariant.sku === configuration.sku) { // by sku or first one return nextVariant } else { - if (!configuration || (configuration && Object.keys(configuration).length === 0)) { // no configuration - return the first child cheaper than the original price - if found - if (nextVariant.price_incl_tax <= regularProductPrice) { - return getVariantWithLowestPrice(prevVariant, nextVariant) - } - } else { - const matchConfiguration = Object.keys(omit(configuration, ['price'])).every((configProperty) => { - let configurationPropertyFilters = configuration[configProperty] || [] - if (!Array.isArray(configurationPropertyFilters)) configurationPropertyFilters = [configurationPropertyFilters] - const configurationIds = configurationPropertyFilters.map(filter => toString(filter.id)).filter(filterId => !!filterId) - if (!configurationIds.length) return true // skip empty - return configurationIds.includes(toString(nextVariant[configProperty])) - }) + const prevVariantMatch = getConfigurationMatchLevel(configuration, prevVariant) + const nextVariantMatch = getConfigurationMatchLevel(configuration, nextVariant) - if (matchConfiguration) { - return getVariantWithLowestPrice(prevVariant, nextVariant) - } + if (prevVariantMatch === nextVariantMatch) { + return getVariantWithLowestPrice(prevVariant, nextVariant) } + + return nextVariantMatch > prevVariantMatch ? nextVariant : prevVariant } }, undefined) return selectedVariant