diff --git a/CHANGELOG.md b/CHANGELOG.md index 40428d2a6b..31ef6e2774 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,6 +29,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Disable overriding `route` state in __INITIAL_STATE__ - @gibkigonzo (pr#4095) - Fix gtm order placement event when user was guest - @Michal-Dziedzinski (#4064) - Fix gtm event switched properties - @Michal-Dziedzinski (pr#4106) +- Group 'productChecksum' and 'productsEquals' logic for all supported products types. Remove 'checksum' when editing product. +Remove and add coupon when user login Remove 'NA' as default company. Show qty in microcart for all types of product. +Remove preload font - it gives good performance, but vue-meta refresh page, because there is script onload. - @gibkigonzo (pr#4128) ## [1.11.1] - 2020.02.05 diff --git a/core/modules/cart/helpers/productChecksum.ts b/core/modules/cart/helpers/productChecksum.ts index 6a73209d5f..e2850df5d8 100644 --- a/core/modules/cart/helpers/productChecksum.ts +++ b/core/modules/cart/helpers/productChecksum.ts @@ -1,5 +1,8 @@ import CartItem from '@vue-storefront/core/modules/cart/types/CartItem' import { sha3_224 } from 'js-sha3' +import get from 'lodash-es/get' +import flow from 'lodash-es/flow' +import cloneDeep from 'lodash-es/cloneDeep'; const replaceNumberToString = obj => { Object.keys(obj).forEach(key => { @@ -11,21 +14,33 @@ const replaceNumberToString = obj => { return obj; } +const transformToArray = value => Array.isArray(value) ? value : Object.values(value) + +export const getProductOptions = (product, optionsName) => { + return flow([ + get, + cloneDeep, + transformToArray, + replaceNumberToString + ])(product, `product_option.extension_attributes.${optionsName}`, []) +} + const getDataToHash = (product: CartItem): any => { if (!product.product_option) { return product.sku ? product.sku : null } - const { extension_attributes } = product.product_option - const { bundle_options, custom_options } = extension_attributes + const supportedProductOptions = ['bundle_options', 'custom_options', 'configurable_item_options'] - if (bundle_options && ((Array.isArray(bundle_options) && bundle_options.length > 0) || (typeof bundle_options === 'object' && bundle_options !== null && Object.values(bundle_options).length > 0))) { - return Array.isArray(bundle_options) ? bundle_options : Object.values(replaceNumberToString(bundle_options)) - } - if (custom_options && ((Array.isArray(custom_options) && custom_options.length > 0) || (typeof custom_options === 'object' && custom_options !== null && Object.values(custom_options).length > 0))) { - return Array.isArray(custom_options) ? custom_options : Object.values(replaceNumberToString(JSON.parse(JSON.stringify(custom_options)))) + // returns first options that has array with options + for (let optionName of supportedProductOptions) { + const options = getProductOptions(product, optionName) + if (options.length) { + return options + } } + // if there are options that are not supported then just return all options return product.product_option } diff --git a/core/modules/cart/helpers/productsEquals.ts b/core/modules/cart/helpers/productsEquals.ts index 72667baea7..b1d3151398 100644 --- a/core/modules/cart/helpers/productsEquals.ts +++ b/core/modules/cart/helpers/productsEquals.ts @@ -1,17 +1,12 @@ import CartItem from '@vue-storefront/core/modules/cart/types/CartItem' -import productChecksum from './productChecksum'; +import productChecksum, { getProductOptions } from './productChecksum'; -const getChecksum = (product: CartItem) => { - if (product.checksum) { - return product.checksum - } - return productChecksum(product) -} +type ProductEqualCheckFn = (product1: CartItem, product2: CartItem) => boolean +// 'id' check const getServerItemId = (product: CartItem): string | number => product.server_item_id || product.item_id - -const isServerIdsEquals = (product1: CartItem, product2: CartItem): boolean => { +const isServerIdsEquals = (product1, product2) => { const product1ItemId = getServerItemId(product1) const product2ItemId = getServerItemId(product2) @@ -20,14 +15,73 @@ const isServerIdsEquals = (product1: CartItem, product2: CartItem): boolean => { return areItemIdsDefined && product1ItemId === product2ItemId } +// 'checksum' check +const getChecksum = (product: CartItem) => { + if (product.checksum) { + return product.checksum + } + return productChecksum(product) +} const isChecksumEquals = (product1: CartItem, product2: CartItem): boolean => getChecksum(product1) === getChecksum(product2) +// 'sku' check +const isSkuEqual = (product1: CartItem, product2: CartItem): boolean => + String(product1.sku) === String(product2.sku) + +/** + * Returns product equality check function + * @param checkName - determines what type of check we want to do + */ +const getCheckFn = (checkName: string): ProductEqualCheckFn => { + switch (checkName) { + case 'id': { + return isServerIdsEquals + } + case 'checksum': { + return isChecksumEquals + } + case 'sku': { + return isSkuEqual + } + default: { + return isSkuEqual + } + } +} + +/** + * It passes all types of checks and returns the first passed. The order of checks matters! + */ +const makeCheck = (product1: CartItem, product2: CartItem, checks: string[]): boolean => { + for (let checkName of checks) { + const fn = getCheckFn(checkName) + if (fn(product1, product2)) { + return true + } + } +} + const productsEquals = (product1: CartItem, product2: CartItem): boolean => { if (!product1 || !product2) { return false } - return isServerIdsEquals(product1, product2) || isChecksumEquals(product1, product2) + + const check = makeCheck.bind(null, product1, product2) + + if (getProductOptions(product1, 'bundle_options').length || getProductOptions(product2, 'bundle_options').length) { + return check(['id', 'checksum']) + } + + if (getProductOptions(product1, 'custom_options').length || getProductOptions(product2, 'custom_options').length) { + return check(['id', 'checksum']) + } + + if (getProductOptions(product1, 'configurable_item_options').length || getProductOptions(product2, 'configurable_item_options').length) { + return check(['checksum', 'sku']) + } + + return check(['id', 'checksum', 'sku']) } export default productsEquals diff --git a/core/modules/cart/store/actions/connectActions.ts b/core/modules/cart/store/actions/connectActions.ts index 4383749c63..03d91d252e 100644 --- a/core/modules/cart/store/actions/connectActions.ts +++ b/core/modules/cart/store/actions/connectActions.ts @@ -18,7 +18,7 @@ const connectActions = { async clear ({ commit, dispatch }, { disconnect = true, sync = true } = {}) { await commit(types.CART_LOAD_CART, []) if (sync) { - await dispatch('sync', { forceClientState: true }) + await dispatch('sync', { forceClientState: true, forceSync: true }) } if (disconnect) { await commit(types.CART_SET_ITEMS_HASH, null) @@ -29,14 +29,18 @@ const connectActions = { commit(types.CART_LOAD_CART_SERVER_TOKEN, null) }, async authorize ({ dispatch, getters }) { - await dispatch('connect', { guestCart: false }) - const coupon = getters.getCoupon.code - if (!getters.getCoupon) { + if (coupon) { + await dispatch('removeCoupon', { sync: false }) + } + + await dispatch('connect', { guestCart: false, mergeQty: true }) + + if (coupon) { await dispatch('applyCoupon', coupon) } }, - async connect ({ getters, dispatch, commit }, { guestCart = false, forceClientState = false }) { + async connect ({ getters, dispatch, commit }, { guestCart = false, forceClientState = false, mergeQty = false }) { if (!getters.isCartSyncEnabled) return const { result, resultCode } = await CartService.getCartToken(guestCart, forceClientState) @@ -44,7 +48,7 @@ const connectActions = { Logger.info('Server cart token created.', 'cart', result)() commit(types.CART_LOAD_CART_SERVER_TOKEN, result) - return dispatch('sync', { forceClientState, dryRun: !config.cart.serverMergeByDefault, mergeQty: true }) + return dispatch('sync', { forceClientState, dryRun: !config.cart.serverMergeByDefault, mergeQty }) } if (resultCode === 401 && getters.bypassCounter < config.queues.maxCartBypassAttempts) { diff --git a/core/modules/cart/store/actions/couponActions.ts b/core/modules/cart/store/actions/couponActions.ts index 404c37e426..1cebd576ea 100644 --- a/core/modules/cart/store/actions/couponActions.ts +++ b/core/modules/cart/store/actions/couponActions.ts @@ -1,12 +1,12 @@ import { CartService } from '@vue-storefront/core/data-resolver' const couponActions = { - async removeCoupon ({ getters, dispatch }) { + async removeCoupon ({ getters, dispatch }, { sync = true } = {}) { if (getters.canSyncTotals) { const { result } = await CartService.removeCoupon() - if (result) { - dispatch('syncTotals', { forceServerSync: true }) + if (result && sync) { + await dispatch('syncTotals', { forceServerSync: true }) return result } } @@ -16,7 +16,7 @@ const couponActions = { const { result } = await CartService.applyCoupon(couponCode) if (result) { - dispatch('syncTotals', { forceServerSync: true }) + await dispatch('syncTotals', { forceServerSync: true }) } return result } diff --git a/core/modules/cart/store/actions/mergeActions.ts b/core/modules/cart/store/actions/mergeActions.ts index 0b024b500e..36b500af96 100644 --- a/core/modules/cart/store/actions/mergeActions.ts +++ b/core/modules/cart/store/actions/mergeActions.ts @@ -199,7 +199,7 @@ const mergeActions = { } const mergeClientItemsDiffLog = await dispatch('mergeClientItems', mergeParameters) const mergeServerItemsDiffLog = await dispatch('mergeServerItems', mergeParameters) - dispatch('updateTotalsAfterMerge', { clientItems, dryRun }) + await dispatch('updateTotalsAfterMerge', { clientItems, dryRun }) diffLog .merge(mergeClientItemsDiffLog) diff --git a/core/modules/cart/store/actions/synchronizeActions.ts b/core/modules/cart/store/actions/synchronizeActions.ts index 23d4fd9105..cf4b8b4773 100644 --- a/core/modules/cart/store/actions/synchronizeActions.ts +++ b/core/modules/cart/store/actions/synchronizeActions.ts @@ -47,10 +47,10 @@ const synchronizeActions = { Logger.warn('The "cart/serverPull" action is deprecated and will not be supported with the Vue Storefront 1.11', 'cart')() return dispatch('sync', { forceClientState, dryRun }) }, - async sync ({ getters, rootGetters, commit, dispatch, state }, { forceClientState = false, dryRun = false, mergeQty = false }) { + async sync ({ getters, rootGetters, commit, dispatch, state }, { forceClientState = false, dryRun = false, mergeQty = false, forceSync = false }) { const shouldUpdateClientState = rootGetters['checkout/isUserInCheckout'] || forceClientState const { getCartItems, canUpdateMethods, isSyncRequired, bypassCounter } = getters - if (!canUpdateMethods || !isSyncRequired) return createDiffLog() + if ((!canUpdateMethods || !isSyncRequired) && !forceSync) return createDiffLog() commit(types.CART_SET_SYNC) const { result, resultCode } = await CartService.getItems() const { serverItems, clientItems } = cartHooksExecutors.beforeSync({ clientItems: getCartItems, serverItems: result }) diff --git a/core/modules/cart/store/actions/totalsActions.ts b/core/modules/cart/store/actions/totalsActions.ts index d6e687136d..b70020ce39 100644 --- a/core/modules/cart/store/actions/totalsActions.ts +++ b/core/modules/cart/store/actions/totalsActions.ts @@ -62,10 +62,11 @@ const totalsActions = { }) if (shippingMethodsData.country) { - return dispatch('overrideServerTotals', { + await dispatch('overrideServerTotals', { hasShippingInformation: shippingMethodsData.method_code || shippingMethodsData.carrier_code, addressInformation: createShippingInfoData(shippingMethodsData) }) + return } Logger.error('Please do set the tax.defaultCountry in order to calculate totals', 'cart')() @@ -73,7 +74,7 @@ const totalsActions = { }, async refreshTotals ({ dispatch }, payload) { Logger.warn('The "cart/refreshTotals" action is deprecated and will not be supported with the Vue Storefront 1.11', 'cart')() - return dispatch('syncTotals', payload) + await dispatch('syncTotals', payload) } } diff --git a/core/modules/cart/test/unit/helpers/productChecksum.spec.ts b/core/modules/cart/test/unit/helpers/productChecksum.spec.ts index f3a5c9a9df..3ed37bda09 100644 --- a/core/modules/cart/test/unit/helpers/productChecksum.spec.ts +++ b/core/modules/cart/test/unit/helpers/productChecksum.spec.ts @@ -5,14 +5,8 @@ const configurableProduct: CartItem = { product_option: { extension_attributes: { configurable_item_options: [ - { - option_id: '93', - option_value: 53 - }, - { - option_id: '142', - option_value: 169 - } + { option_id: '93', option_value: '53' }, + { option_id: '142', option_value: '169' } ] } } @@ -22,26 +16,10 @@ const bundleProduct: CartItem = { product_option: { extension_attributes: { bundle_options: [ - { - option_id: 1, - option_qty: 1, - option_selections: [2] - }, - { - option_id: 2, - option_qty: 1, - option_selections: [4] - }, - { - option_id: 3, - option_qty: 1, - option_selections: [5] - }, - { - option_id: 4, - option_qty: 1, - option_selections: [8] - } + { option_id: '1', option_qty: '1', option_selections: [ '2' ] }, + { option_id: '2', option_qty: '1', option_selections: [ '4' ] }, + { option_id: '3', option_qty: '1', option_selections: [ '5' ] }, + { option_id: '4', option_qty: '1', option_selections: [ '8' ] } ] } } @@ -49,10 +27,10 @@ const bundleProduct: CartItem = { describe('Cart productChecksum', () => { it('returns checksum for bundle product', async () => { - expect(productChecksum(bundleProduct)).toBe('d8ba5d5baf59fe28647d6a08fdaeb683a7b39ccdebc77eecabc6457c'); + expect(productChecksum(bundleProduct)).toBe('3e183f026489207a9cd535d20f141e07ddfea729af58a9088b82612f'); }); it('returns checksum for configurable product', async () => { - expect(productChecksum(configurableProduct)).toBe('0bbb27ec7a3cb5dfd1d3f6c4ee54c8b522c4063fe6ea0571794d446f'); + expect(productChecksum(configurableProduct)).toBe('357e8f9f8918873f12ed993c3073ddd3e8980c933034b5e2fdab10b6'); }); }); diff --git a/core/modules/cart/test/unit/store/connectActions.spec.ts b/core/modules/cart/test/unit/store/connectActions.spec.ts index b61184510b..fe248332ba 100644 --- a/core/modules/cart/test/unit/store/connectActions.spec.ts +++ b/core/modules/cart/test/unit/store/connectActions.spec.ts @@ -60,7 +60,7 @@ describe('Cart connectActions', () => { await wrapper(cartActions); expect(contextMock.commit).toHaveBeenNthCalledWith(1, types.CART_LOAD_CART, []); - expect(contextMock.dispatch).toHaveBeenNthCalledWith(1, 'sync', { forceClientState: true }); + expect(contextMock.dispatch).toHaveBeenNthCalledWith(1, 'sync', { forceClientState: true, forceSync: true }); expect(contextMock.commit).toHaveBeenNthCalledWith(2, types.CART_SET_ITEMS_HASH, null); expect(contextMock.dispatch).toHaveBeenNthCalledWith(2, 'disconnect'); }); @@ -78,7 +78,7 @@ describe('Cart connectActions', () => { await wrapper(cartActions); expect(contextMock.commit).toHaveBeenNthCalledWith(1, types.CART_LOAD_CART, []); - expect(contextMock.dispatch).toHaveBeenNthCalledWith(1, 'sync', { forceClientState: true }); + expect(contextMock.dispatch).toHaveBeenNthCalledWith(1, 'sync', { forceClientState: true, forceSync: true }); }); it('clear deletes all cart products and token, but not sync with backend', async () => { @@ -118,7 +118,7 @@ describe('Cart connectActions', () => { }) await (cartActions as any).authorize(contextMock) - expect(contextMock.dispatch).toHaveBeenNthCalledWith(1, 'connect', { guestCart: false }); + expect(contextMock.dispatch).toHaveBeenNthCalledWith(1, 'connect', { guestCart: false, mergeQty: true }); }) it('creates cart token', async () => { @@ -138,7 +138,7 @@ describe('Cart connectActions', () => { await (cartActions as any).connect(contextMock, {}) expect(contextMock.commit).toBeCalledWith(types.CART_LOAD_CART_SERVER_TOKEN, 'server-cart-token') - expect(contextMock.dispatch).toBeCalledWith('sync', { forceClientState: false, dryRun: true, mergeQty: true }) + expect(contextMock.dispatch).toBeCalledWith('sync', { forceClientState: false, dryRun: true, mergeQty: false }) }) it('attempts bypassing guest cart', async () => { diff --git a/core/modules/catalog/helpers/prefetchCachedAttributes.ts b/core/modules/catalog/helpers/prefetchCachedAttributes.ts index 174eb73acd..6a40b10cf7 100644 --- a/core/modules/catalog/helpers/prefetchCachedAttributes.ts +++ b/core/modules/catalog/helpers/prefetchCachedAttributes.ts @@ -6,7 +6,7 @@ async function prefetchCachedAttributes (filterField, filterValues) { if (!config.attributes || !config.attributes.disablePersistentAttributesCache) { const attrCollection = StorageManager.get('attributes') const cachedAttributes = filterValues.map( - async filterValue => attrCollection.getItem(entityKeyName(filterField, filterValue.toLowerCase())) + async filterValue => attrCollection.getItem(entityKeyName(filterField, String(filterValue).toLowerCase())) ) return Promise.all(cachedAttributes) } diff --git a/core/pages/Checkout.js b/core/pages/Checkout.js index c2687d4a02..304ec5ec3c 100644 --- a/core/pages/Checkout.js +++ b/core/pages/Checkout.js @@ -295,7 +295,7 @@ export default { region_id: this.shipping.region_id ? this.shipping.region_id : 0, country_id: this.shipping.country, street: [this.shipping.streetAddress, this.shipping.apartmentNumber], - company: 'NA', // TODO: Fix me! https://github.com/DivanteLtd/vue-storefront/issues/224 + company: '', telephone: this.shipping.phoneNumber, postcode: this.shipping.zipCode, city: this.shipping.city, diff --git a/src/modules/instant-checkout/components/InstantCheckout.vue b/src/modules/instant-checkout/components/InstantCheckout.vue index d8765a0705..86f4f9d2b9 100644 --- a/src/modules/instant-checkout/components/InstantCheckout.vue +++ b/src/modules/instant-checkout/components/InstantCheckout.vue @@ -266,7 +266,7 @@ export default { region_id: 0, country_id: paymentResponse.shippingAddress.country, street: [paymentResponse.shippingAddress.addressLine[0], paymentResponse.shippingAddress.addressLine[1]], - company: paymentResponse.shippingAddress.organization ? paymentResponse.shippingAddress.organization : 'NA', + company: paymentResponse.shippingAddress.organization ? paymentResponse.shippingAddress.organization : '', telephone: paymentResponse.shippingAddress.phone, postcode: paymentResponse.shippingAddress.postalCode, city: paymentResponse.shippingAddress.city, @@ -280,7 +280,7 @@ export default { region_id: 0, country_id: paymentResponse.shippingAddress.country, street: [paymentResponse.shippingAddress.addressLine[0], paymentResponse.shippingAddress.addressLine[1]], - company: paymentResponse.shippingAddress.organization ? paymentResponse.shippingAddress.organization : 'NA', + company: paymentResponse.shippingAddress.organization ? paymentResponse.shippingAddress.organization : '', telephone: paymentResponse.payerPhone, postcode: paymentResponse.shippingAddress.postalCode, city: paymentResponse.shippingAddress.city, diff --git a/src/themes/default/components/core/ProductPrice.vue b/src/themes/default/components/core/ProductPrice.vue index d38d5ece2a..e9db8b7ae6 100644 --- a/src/themes/default/components/core/ProductPrice.vue +++ b/src/themes/default/components/core/ProductPrice.vue @@ -2,7 +2,7 @@