Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
29 changes: 22 additions & 7 deletions core/modules/cart/helpers/productChecksum.ts
Original file line number Diff line number Diff line change
@@ -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 => {
Expand All @@ -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
}

Expand Down
74 changes: 64 additions & 10 deletions core/modules/cart/helpers/productsEquals.ts
Original file line number Diff line number Diff line change
@@ -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 => {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why types are removed?

const isServerIdsEquals = (product1, product2) => {
const product1ItemId = getServerItemId(product1)
const product2ItemId = getServerItemId(product2)

Expand All @@ -20,14 +15,73 @@ const isServerIdsEquals = (product1: CartItem, product2: CartItem): boolean => {
return areItemIdsDefined && product1ItemId === product2ItemId
}

// 'checksum' check
const getChecksum = (product: CartItem) => {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const getChecksum = (product: CartItem) => {
const getChecksum = (product: CartItem) => product.checksum ? product.checksum : productChecksum(product)

mayby shorter form, wdyt? :)

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)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can be sku another type than string?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can be sku another type than string?


/**
* 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': {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shouldn't it be remove if it is the same as default?

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
16 changes: 10 additions & 6 deletions core/modules/cart/store/actions/connectActions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -29,22 +29,26 @@ 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)

if (resultCode === 200) {
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) {
Expand Down
8 changes: 4 additions & 4 deletions core/modules/cart/store/actions/couponActions.ts
Original file line number Diff line number Diff line change
@@ -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
}
}
Expand All @@ -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
}
Expand Down
2 changes: 1 addition & 1 deletion core/modules/cart/store/actions/mergeActions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
4 changes: 2 additions & 2 deletions core/modules/cart/store/actions/synchronizeActions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 })
Expand Down
5 changes: 3 additions & 2 deletions core/modules/cart/store/actions/totalsActions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,18 +62,19 @@ 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')()
}
},
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)
}
}

Expand Down
38 changes: 8 additions & 30 deletions core/modules/cart/test/unit/helpers/productChecksum.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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' }
]
}
}
Expand All @@ -22,37 +16,21 @@ 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' ] }
]
}
}
} as any as 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');
});
});
8 changes: 4 additions & 4 deletions core/modules/cart/test/unit/store/connectActions.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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');
});
Expand All @@ -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 () => {
Expand Down Expand Up @@ -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 () => {
Expand All @@ -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 () => {
Expand Down
2 changes: 1 addition & 1 deletion core/modules/catalog/helpers/prefetchCachedAttributes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down
2 changes: 1 addition & 1 deletion core/pages/Checkout.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
4 changes: 2 additions & 2 deletions src/modules/instant-checkout/components/InstantCheckout.vue
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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,
Expand Down
Loading