diff --git a/src/app/core/facades/checkout.facade.ts b/src/app/core/facades/checkout.facade.ts
index d3e1a2ca9b..1d739af806 100644
--- a/src/app/core/facades/checkout.facade.ts
+++ b/src/app/core/facades/checkout.facade.ts
@@ -11,7 +11,6 @@ import { LineItemUpdate } from 'ish-core/models/line-item-update/line-item-updat
import { PaymentInstrument } from 'ish-core/models/payment-instrument/payment-instrument.model';
import { selectRouteData } from 'ish-core/store/core/router';
import { getServerConfigParameter } from 'ish-core/store/core/server-config';
-import { getAllAddresses } from 'ish-core/store/customer/addresses';
import {
addMessageToMerchant,
addPromotionCodeToBasket,
@@ -25,6 +24,7 @@ import {
deleteBasketItems,
deleteBasketPayment,
deleteBasketShippingAddress,
+ getBasketEligibleAddresses,
getBasketEligiblePaymentMethods,
getBasketEligibleShippingMethods,
getBasketError,
@@ -38,6 +38,7 @@ import {
getCurrentBasket,
getSubmittedBasket,
isBasketInvoiceAndShippingAddressEqual,
+ loadBasketEligibleAddresses,
loadBasketEligiblePaymentMethods,
loadBasketEligibleShippingMethods,
loadBasketWithId,
@@ -283,18 +284,27 @@ export class CheckoutFacade {
select(
createSelector(
getLoggedInUser,
- getAllAddresses,
+ getBasketEligibleAddresses,
getBasketShippingAddress,
(user, addresses, shippingAddress): boolean =>
!!shippingAddress &&
!!user &&
- addresses.length > 1 &&
+ addresses?.length > 1 &&
(!user.preferredInvoiceToAddressUrn || user.preferredInvoiceToAddressUrn !== shippingAddress.urn) &&
(!user.preferredShipToAddressUrn || user.preferredShipToAddressUrn !== shippingAddress.urn)
)
)
);
+ eligibleAddresses$() {
+ return this.basket$.pipe(
+ whenTruthy(),
+ take(1),
+ tap(() => this.store.dispatch(loadBasketEligibleAddresses())),
+ switchMap(() => this.store.pipe(select(getBasketEligibleAddresses)))
+ );
+ }
+
assignBasketAddress(addressId: string, scope: 'invoice' | 'shipping' | 'any') {
this.store.dispatch(assignBasketAddress({ addressId, scope }));
}
diff --git a/src/app/core/services/basket/basket.service.spec.ts b/src/app/core/services/basket/basket.service.spec.ts
index b89fc0deb9..fb076897ef 100644
--- a/src/app/core/services/basket/basket.service.spec.ts
+++ b/src/app/core/services/basket/basket.service.spec.ts
@@ -195,6 +195,15 @@ describe('Basket Service', () => {
});
});
+ it("should get eligible addresses for a basket when 'getBasketEligibleAddresses' is called", done => {
+ when(apiService.get(anything(), anything())).thenReturn(of({ data: [] }));
+
+ basketService.getBasketEligibleAddresses().subscribe(() => {
+ verify(apiService.get('eligible-addresses', anything())).once();
+ done();
+ });
+ });
+
it("should get eligible shipping methods for a basket when 'getBasketEligibleShippingMethods' is called", done => {
when(apiService.get(anything(), anything())).thenReturn(of({ data: [] }));
diff --git a/src/app/core/services/basket/basket.service.ts b/src/app/core/services/basket/basket.service.ts
index beae39c4f3..f4c8afd0a8 100644
--- a/src/app/core/services/basket/basket.service.ts
+++ b/src/app/core/services/basket/basket.service.ts
@@ -3,6 +3,7 @@ import { Injectable } from '@angular/core';
import { Observable, of, throwError } from 'rxjs';
import { catchError, concatMap, map } from 'rxjs/operators';
+import { AddressData } from 'ish-core/models/address/address.interface';
import { AddressMapper } from 'ish-core/models/address/address.mapper';
import { Address } from 'ish-core/models/address/address.model';
import { Attribute } from 'ish-core/models/attribute/attribute.model';
@@ -32,41 +33,6 @@ export type BasketUpdateType =
| { invoiceToAddress: string }
| { messageToMerchant: string };
-type BasketIncludeType =
- | 'invoiceToAddress'
- | 'commonShipToAddress'
- | 'commonShippingMethod'
- | 'discounts'
- | 'lineItems_discounts'
- | 'lineItems'
- | 'payments'
- | 'payments_paymentMethod'
- | 'payments_paymentInstrument';
-
-type MergeBasketIncludeType =
- | 'targetBasket'
- | 'targetBasket_invoiceToAddress'
- | 'targetBasket_commonShipToAddress'
- | 'targetBasket_commonShippingMethod'
- | 'targetBasket_discounts'
- | 'targetBasket_lineItems_discounts'
- | 'targetBasket_lineItems'
- | 'targetBasket_payments'
- | 'targetBasket_payments_paymentMethod'
- | 'targetBasket_payments_paymentInstrument';
-
-type ValidationBasketIncludeType =
- | 'basket'
- | 'basket_invoiceToAddress'
- | 'basket_commonShipToAddress'
- | 'basket_commonShippingMethod'
- | 'basket_discounts'
- | 'basket_lineItems_discounts'
- | 'basket_lineItems'
- | 'basket_payments'
- | 'basket_payments_paymentMethod'
- | 'basket_payments_paymentInstrument';
-
/**
* The Basket Service handles the interaction with the 'baskets' REST API.
* Methods related to basket-items are handled in the basket-items.service.
@@ -84,7 +50,7 @@ export class BasketService {
Accept: 'application/vnd.intershop.basket.v1+json',
});
- private allBasketIncludes: BasketIncludeType[] = [
+ private readonly allBasketIncludes = [
'invoiceToAddress',
'commonShipToAddress',
'commonShippingMethod',
@@ -96,7 +62,7 @@ export class BasketService {
'payments_paymentInstrument',
];
- private allTargetBasketIncludes: MergeBasketIncludeType[] = [
+ private readonly allTargetBasketIncludes = [
'targetBasket',
'targetBasket_invoiceToAddress',
'targetBasket_commonShipToAddress',
@@ -109,7 +75,7 @@ export class BasketService {
'targetBasket_payments_paymentInstrument',
];
- private allBasketValidationIncludes: ValidationBasketIncludeType[] = [
+ private readonly allBasketValidationIncludes = [
'basket',
'basket_invoiceToAddress',
'basket_commonShipToAddress',
@@ -309,6 +275,23 @@ export class BasketService {
});
}
+ /**
+ * Get eligible addresses for the currently used basket.
+ *
+ * @returns The eligible addresses.
+ */
+ getBasketEligibleAddresses(): Observable
{
+ return this.apiService
+ .currentBasketEndpoint()
+ .get('eligible-addresses', {
+ headers: this.basketHeaders,
+ })
+ .pipe(
+ unpackEnvelope('data'),
+ map(addressesData => addressesData.map(AddressMapper.fromData))
+ );
+ }
+
/**
* Create a basket address for the currently used basket of an anonymous user.
*
diff --git a/src/app/core/store/customer/addresses/addresses.reducer.ts b/src/app/core/store/customer/addresses/addresses.reducer.ts
index 8bcbc60cbf..60100ff27c 100644
--- a/src/app/core/store/customer/addresses/addresses.reducer.ts
+++ b/src/app/core/store/customer/addresses/addresses.reducer.ts
@@ -3,12 +3,7 @@ import { createReducer, on } from '@ngrx/store';
import { Address } from 'ish-core/models/address/address.model';
import { HttpError } from 'ish-core/models/http-error/http-error.model';
-import {
- createBasketAddress,
- createBasketAddressSuccess,
- deleteBasketShippingAddress,
- updateBasketAddress,
-} from 'ish-core/store/customer/basket';
+import { deleteBasketShippingAddress, updateBasketAddress } from 'ish-core/store/customer/basket';
import { setErrorOn, setLoadingOn, unsetLoadingAndErrorOn } from 'ish-core/utils/ngrx-creators';
import {
@@ -43,7 +38,6 @@ export const addressesReducer = createReducer(
setLoadingOn(
loadAddresses,
createCustomerAddress,
- createBasketAddress,
updateCustomerAddress,
updateBasketAddress,
deleteCustomerAddress,
@@ -53,12 +47,11 @@ export const addressesReducer = createReducer(
unsetLoadingAndErrorOn(
loadAddressesSuccess,
createCustomerAddressSuccess,
- createBasketAddressSuccess,
updateCustomerAddressSuccess,
deleteCustomerAddressSuccess
),
on(loadAddressesSuccess, (state, action) => addressAdapter.setAll(action.payload.addresses, state)),
- on(createCustomerAddressSuccess, createBasketAddressSuccess, updateCustomerAddressSuccess, (state, action) =>
+ on(createCustomerAddressSuccess, updateCustomerAddressSuccess, (state, action) =>
addressAdapter.upsertOne(action.payload.address, state)
),
on(deleteCustomerAddressSuccess, (state, action) => addressAdapter.removeOne(action.payload.addressId, state))
diff --git a/src/app/core/store/customer/addresses/addresses.selectors.spec.ts b/src/app/core/store/customer/addresses/addresses.selectors.spec.ts
index 33d464f7a2..9038cf1d5b 100644
--- a/src/app/core/store/customer/addresses/addresses.selectors.spec.ts
+++ b/src/app/core/store/customer/addresses/addresses.selectors.spec.ts
@@ -2,7 +2,7 @@ import { TestBed } from '@angular/core/testing';
import { Address } from 'ish-core/models/address/address.model';
import { CoreStoreModule } from 'ish-core/store/core/core-store.module';
-import { createBasketAddress, createBasketAddressSuccess, updateBasketAddress } from 'ish-core/store/customer/basket';
+import { updateBasketAddress } from 'ish-core/store/customer/basket';
import { CustomerStoreModule } from 'ish-core/store/customer/customer-store.module';
import { makeHttpError } from 'ish-core/utils/dev/api-service-utils';
import { BasketMockData } from 'ish-core/utils/dev/basket-mock-data';
@@ -125,46 +125,6 @@ describe('Addresses Selectors', () => {
});
});
- describe('create basket addresses', () => {
- const address = BasketMockData.getAddress();
-
- beforeEach(() => {
- store$.dispatch(createBasketAddress({ address, scope: 'invoice' }));
- });
-
- it('should set the state to loading', () => {
- expect(getAddressesLoading(store$.state)).toBeTrue();
- });
-
- describe('and reporting success', () => {
- beforeEach(() => {
- store$.dispatch(createBasketAddressSuccess({ address, scope: 'invoice' }));
- });
-
- it('should set loading to false and add basket address', () => {
- expect(getAddressesLoading(store$.state)).toBeFalse();
- expect(getAllAddresses(store$.state)).toEqual([address]);
- });
- });
-
- describe('and reporting failure', () => {
- beforeEach(() => {
- store$.dispatch(createCustomerAddressFail({ error: makeHttpError({ message: 'error' }) }));
- });
-
- it('should not have loaded addresses on error', () => {
- expect(getAddressesLoading(store$.state)).toBeFalse();
- expect(getAllAddresses(store$.state)).toBeEmpty();
- expect(getAddressesError(store$.state)).toMatchInlineSnapshot(`
- {
- "message": "error",
- "name": "HttpErrorResponse",
- }
- `);
- });
- });
- });
-
describe('update basket addresses', () => {
const address = BasketMockData.getAddress();
diff --git a/src/app/core/store/customer/basket/basket-addresses.effects.spec.ts b/src/app/core/store/customer/basket/basket-addresses.effects.spec.ts
index 89891f090f..1994bdb3c9 100644
--- a/src/app/core/store/customer/basket/basket-addresses.effects.spec.ts
+++ b/src/app/core/store/customer/basket/basket-addresses.effects.spec.ts
@@ -27,6 +27,9 @@ import {
createBasketAddressSuccess,
deleteBasketShippingAddress,
loadBasket,
+ loadBasketEligibleAddresses,
+ loadBasketEligibleAddressesFail,
+ loadBasketEligibleAddressesSuccess,
resetBasketErrors,
updateBasket,
updateBasketAddress,
@@ -57,6 +60,45 @@ describe('Basket Addresses Effects', () => {
store = TestBed.inject(Store);
});
+ describe('loadBasketEligibleAddresses$', () => {
+ beforeEach(() => {
+ when(basketServiceMock.getBasketEligibleAddresses()).thenReturn(of([]));
+ });
+
+ it('should call the basketService for loadBasketEligibleAddresses', done => {
+ const action = loadBasketEligibleAddresses();
+ actions$ = of(action);
+
+ effects.loadBasketEligibleAddresses$.subscribe(() => {
+ verify(basketServiceMock.getBasketEligibleAddresses()).once();
+ done();
+ });
+ });
+
+ it('should map to action of type LoadBasketEligibleAddressesSuccess', () => {
+ const action = loadBasketEligibleAddresses();
+ const completion = loadBasketEligibleAddressesSuccess({ addresses: [] });
+
+ actions$ = hot('-a-a-a', { a: action });
+ const expected$ = cold('-c-c-c', { c: completion });
+
+ expect(effects.loadBasketEligibleAddresses$).toBeObservable(expected$);
+ });
+
+ it('should map invalid request to action of type LoadBasketEligibleAddressesFail', () => {
+ when(basketServiceMock.getBasketEligibleAddresses()).thenReturn(
+ throwError(() => makeHttpError({ message: 'invalid' }))
+ );
+
+ const action = loadBasketEligibleAddresses();
+ const completion = loadBasketEligibleAddressesFail({ error: makeHttpError({ message: 'invalid' }) });
+ actions$ = hot('-a-a-a', { a: action });
+ const expected$ = cold('-c-c-c', { c: completion });
+
+ expect(effects.loadBasketEligibleAddresses$).toBeObservable(expected$);
+ });
+ });
+
describe('createAddressForBasket$ for a logged in user', () => {
beforeEach(() => {
when(addressServiceMock.createCustomerAddress('-', anything())).thenReturn(of(BasketMockData.getAddress()));
@@ -212,9 +254,10 @@ describe('Basket Addresses Effects', () => {
const action = updateBasketAddress({ address });
const completion1 = updateCustomerAddressSuccess({ address });
const completion2 = loadBasket();
- const completion3 = resetBasketErrors();
+ const completion3 = loadBasketEligibleAddresses();
+ const completion4 = resetBasketErrors();
actions$ = hot('-a', { a: action });
- const expected$ = cold('-(cde)', { c: completion1, d: completion2, e: completion3 });
+ const expected$ = cold('-(cdef)', { c: completion1, d: completion2, e: completion3, f: completion4 });
expect(effects.updateBasketAddress$).toBeObservable(expected$);
});
@@ -298,8 +341,9 @@ describe('Basket Addresses Effects', () => {
const action = deleteBasketShippingAddress({ addressId });
const completion1 = deleteCustomerAddressSuccess({ addressId });
const completion2 = loadBasket();
+ const completion3 = loadBasketEligibleAddresses();
actions$ = hot('-a', { a: action });
- const expected$ = cold('-(cd)', { c: completion1, d: completion2 });
+ const expected$ = cold('-(cde)', { c: completion1, d: completion2, e: completion3 });
expect(effects.deleteBasketShippingAddress$).toBeObservable(expected$);
});
diff --git a/src/app/core/store/customer/basket/basket-addresses.effects.ts b/src/app/core/store/customer/basket/basket-addresses.effects.ts
index 178acf3a4a..fbe0110f10 100644
--- a/src/app/core/store/customer/basket/basket-addresses.effects.ts
+++ b/src/app/core/store/customer/basket/basket-addresses.effects.ts
@@ -6,7 +6,6 @@ import { map, mergeMap } from 'rxjs/operators';
import { AddressService } from 'ish-core/services/address/address.service';
import { BasketService, BasketUpdateType } from 'ish-core/services/basket/basket.service';
import {
- createCustomerAddressFail,
deleteCustomerAddressFail,
deleteCustomerAddressSuccess,
updateCustomerAddressFail,
@@ -18,9 +17,13 @@ import { mapErrorToAction, mapToPayload, mapToPayloadProperty } from 'ish-core/u
import {
assignBasketAddress,
createBasketAddress,
+ createBasketAddressFail,
createBasketAddressSuccess,
deleteBasketShippingAddress,
loadBasket,
+ loadBasketEligibleAddresses,
+ loadBasketEligibleAddressesFail,
+ loadBasketEligibleAddressesSuccess,
resetBasketErrors,
updateBasket,
updateBasketAddress,
@@ -35,6 +38,21 @@ export class BasketAddressesEffects {
private addressService: AddressService
) {}
+ /**
+ * The load basket eligible addresses effect.
+ */
+ loadBasketEligibleAddresses$ = createEffect(() =>
+ this.actions$.pipe(
+ ofType(loadBasketEligibleAddresses),
+ mergeMap(() =>
+ this.basketService.getBasketEligibleAddresses().pipe(
+ map(result => loadBasketEligibleAddressesSuccess({ addresses: result })),
+ mapErrorToAction(loadBasketEligibleAddressesFail)
+ )
+ )
+ )
+ );
+
/**
* Creates a new invoice/shipping address which is assigned to the basket later on
* if the user is logged in a customer address will be created, otherwise a new basket address will be created
@@ -49,13 +67,13 @@ export class BasketAddressesEffects {
if (customer) {
return this.addressService.createCustomerAddress('-', action.payload.address).pipe(
map(newAddress => createBasketAddressSuccess({ address: newAddress, scope: action.payload.scope })),
- mapErrorToAction(createCustomerAddressFail)
+ mapErrorToAction(createBasketAddressFail)
);
// create address at basket for anonymous user
} else {
return this.basketService.createBasketAddress(action.payload.address).pipe(
map(newAddress => createBasketAddressSuccess({ address: newAddress, scope: action.payload.scope })),
- mapErrorToAction(createCustomerAddressFail)
+ mapErrorToAction(createBasketAddressFail)
);
}
})
@@ -119,7 +137,12 @@ export class BasketAddressesEffects {
// create address at customer for logged in user
if (customer) {
return this.addressService.updateCustomerAddress('-', address).pipe(
- mergeMap(() => [updateCustomerAddressSuccess({ address }), loadBasket(), resetBasketErrors()]),
+ mergeMap(() => [
+ updateCustomerAddressSuccess({ address }),
+ loadBasket(),
+ loadBasketEligibleAddresses(),
+ resetBasketErrors(),
+ ]),
mapErrorToAction(updateCustomerAddressFail)
);
// create address at basket for anonymous user
@@ -142,7 +165,7 @@ export class BasketAddressesEffects {
mapToPayloadProperty('addressId'),
mergeMap(addressId =>
this.addressService.deleteCustomerAddress('-', addressId).pipe(
- mergeMap(() => [deleteCustomerAddressSuccess({ addressId }), loadBasket()]),
+ mergeMap(() => [deleteCustomerAddressSuccess({ addressId }), loadBasket(), loadBasketEligibleAddresses()]),
mapErrorToAction(deleteCustomerAddressFail)
)
)
diff --git a/src/app/core/store/customer/basket/basket.actions.ts b/src/app/core/store/customer/basket/basket.actions.ts
index 389970f9e5..7a34d2aeb5 100644
--- a/src/app/core/store/customer/basket/basket.actions.ts
+++ b/src/app/core/store/customer/basket/basket.actions.ts
@@ -48,6 +48,8 @@ export const createBasketAddressSuccess = createAction(
payload<{ address: Address; scope: 'invoice' | 'shipping' | 'any' }>()
);
+export const createBasketAddressFail = createAction('[Basket API] Create Basket Address Fail', httpError());
+
export const assignBasketAddress = createAction(
'[Basket] Assign an Address to the Basket',
payload<{ addressId: string; scope: 'invoice' | 'shipping' | 'any' }>()
@@ -205,6 +207,18 @@ export const deleteBasketAttributeFail = createAction('[Basket API] Delete Baske
export const deleteBasketAttributeSuccess = createAction('[Basket API] Delete Basket Attribute Success');
+export const loadBasketEligibleAddresses = createAction('[Basket Internal] Load Basket Eligible Addresses');
+
+export const loadBasketEligibleAddressesFail = createAction(
+ '[Basket API] Load Basket Eligible Addresses Fail',
+ httpError()
+);
+
+export const loadBasketEligibleAddressesSuccess = createAction(
+ '[Basket API] Load Basket Eligible Addresses Success',
+ payload<{ addresses: Address[] }>()
+);
+
export const loadBasketEligibleShippingMethods = createAction(
'[Basket Internal] Load Basket Eligible Shipping Methods'
);
diff --git a/src/app/core/store/customer/basket/basket.reducer.ts b/src/app/core/store/customer/basket/basket.reducer.ts
index fbb2b872bd..aba81c5485 100644
--- a/src/app/core/store/customer/basket/basket.reducer.ts
+++ b/src/app/core/store/customer/basket/basket.reducer.ts
@@ -1,6 +1,7 @@
import { createReducer, on } from '@ngrx/store';
import { unionBy } from 'lodash-es';
+import { Address } from 'ish-core/models/address/address.model';
import { BasketInfo } from 'ish-core/models/basket-info/basket-info.model';
import { BasketValidationResultType } from 'ish-core/models/basket-validation/basket-validation.model';
import { Basket } from 'ish-core/models/basket/basket.model';
@@ -23,6 +24,9 @@ import {
continueCheckoutFail,
continueCheckoutSuccess,
continueCheckoutWithIssues,
+ createBasketAddress,
+ createBasketAddressFail,
+ createBasketAddressSuccess,
createBasketPayment,
createBasketPaymentFail,
createBasketPaymentSuccess,
@@ -39,6 +43,9 @@ import {
loadBasket,
loadBasketByAPIToken,
loadBasketByAPITokenFail,
+ loadBasketEligibleAddresses,
+ loadBasketEligibleAddressesFail,
+ loadBasketEligibleAddressesSuccess,
loadBasketEligiblePaymentMethods,
loadBasketEligiblePaymentMethodsFail,
loadBasketEligiblePaymentMethodsSuccess,
@@ -86,6 +93,7 @@ import {
export interface BasketState {
basket: Basket;
+ eligibleAddresses: Address[];
eligibleShippingMethods: ShippingMethod[];
eligiblePaymentMethods: PaymentMethod[];
loading: boolean;
@@ -105,6 +113,7 @@ const initialValidationResults: BasketValidationResultType = {
const initialState: BasketState = {
basket: undefined,
+ eligibleAddresses: undefined,
eligibleShippingMethods: undefined,
eligiblePaymentMethods: undefined,
loading: false,
@@ -134,6 +143,8 @@ export const basketReducer = createReducer(
deleteBasketItem,
setBasketAttribute,
deleteBasketAttribute,
+ createBasketAddress,
+ loadBasketEligibleAddresses,
loadBasketEligibleShippingMethods,
loadBasketEligiblePaymentMethods,
setBasketPayment,
@@ -158,12 +169,14 @@ export const basketReducer = createReducer(
deleteBasketItemSuccess,
addItemsToBasketSuccess,
setBasketPaymentSuccess,
+ createBasketAddressSuccess,
createBasketPaymentSuccess,
updateBasketPaymentSuccess,
deleteBasketPaymentSuccess,
removePromotionCodeFromBasketSuccess,
continueCheckoutSuccess,
continueCheckoutWithIssues,
+ loadBasketEligibleAddressesSuccess,
loadBasketEligibleShippingMethodsSuccess,
loadBasketEligiblePaymentMethodsSuccess,
updateConcardisCvcLastUpdatedSuccess,
@@ -182,6 +195,8 @@ export const basketReducer = createReducer(
deleteBasketItemFail,
setBasketAttributeFail,
deleteBasketAttributeFail,
+ createBasketAddressFail,
+ loadBasketEligibleAddressesFail,
loadBasketEligibleShippingMethodsFail,
loadBasketEligiblePaymentMethodsFail,
setBasketPaymentFail,
@@ -255,6 +270,22 @@ export const basketReducer = createReducer(
validationResults: validation?.results,
};
}),
+ on(
+ loadBasketEligibleAddressesSuccess,
+ (state, action): BasketState => ({
+ ...state,
+ eligibleAddresses: action.payload.addresses,
+ })
+ ),
+ on(
+ createBasketAddressSuccess,
+ (state, action): BasketState => ({
+ ...state,
+ eligibleAddresses: state.eligibleAddresses
+ ? [...state.eligibleAddresses, action.payload.address]
+ : [action.payload.address],
+ })
+ ),
on(
loadBasketEligibleShippingMethodsSuccess,
(state, action): BasketState => ({
diff --git a/src/app/core/store/customer/basket/basket.selectors.spec.ts b/src/app/core/store/customer/basket/basket.selectors.spec.ts
index 1cb5781556..cc6430aa1e 100644
--- a/src/app/core/store/customer/basket/basket.selectors.spec.ts
+++ b/src/app/core/store/customer/basket/basket.selectors.spec.ts
@@ -20,6 +20,9 @@ import {
continueCheckoutSuccess,
createBasketSuccess,
loadBasket,
+ loadBasketEligibleAddresses,
+ loadBasketEligibleAddressesFail,
+ loadBasketEligibleAddressesSuccess,
loadBasketEligiblePaymentMethods,
loadBasketEligiblePaymentMethodsFail,
loadBasketEligiblePaymentMethodsSuccess,
@@ -31,6 +34,7 @@ import {
submitBasketSuccess,
} from './basket.actions';
import {
+ getBasketEligibleAddresses,
getBasketEligiblePaymentMethods,
getBasketEligibleShippingMethods,
getBasketError,
@@ -183,6 +187,44 @@ describe('Basket Selectors', () => {
});
});
+ describe('loading eligible addresses', () => {
+ beforeEach(() => {
+ store$.dispatch(loadBasketEligibleAddresses());
+ });
+
+ it('should set the state to loading', () => {
+ expect(getBasketLoading(store$.state)).toBeTrue();
+ });
+
+ describe('and reporting success', () => {
+ beforeEach(() => {
+ store$.dispatch(loadBasketEligibleAddressesSuccess({ addresses: [BasketMockData.getAddress()] }));
+ });
+
+ it('should set loading to false', () => {
+ expect(getBasketLoading(store$.state)).toBeFalse();
+ expect(getBasketEligibleAddresses(store$.state)).toEqual([BasketMockData.getAddress()]);
+ });
+ });
+
+ describe('and reporting failure', () => {
+ beforeEach(() => {
+ store$.dispatch(loadBasketEligibleAddressesFail({ error: makeHttpError({ message: 'error' }) }));
+ });
+
+ it('should not have loaded addresses on error', () => {
+ expect(getBasketLoading(store$.state)).toBeFalse();
+ expect(getBasketEligibleAddresses(store$.state)).toBeUndefined();
+ expect(getBasketError(store$.state)).toMatchInlineSnapshot(`
+ {
+ "message": "error",
+ "name": "HttpErrorResponse",
+ }
+ `);
+ });
+ });
+ });
+
describe('loading eligible shipping methods', () => {
beforeEach(() => {
store$.dispatch(loadBasketEligibleShippingMethods());
diff --git a/src/app/core/store/customer/basket/basket.selectors.ts b/src/app/core/store/customer/basket/basket.selectors.ts
index a8e97f3305..4ad95b80db 100644
--- a/src/app/core/store/customer/basket/basket.selectors.ts
+++ b/src/app/core/store/customer/basket/basket.selectors.ts
@@ -69,6 +69,8 @@ export const getBasketPromotionError = createSelector(getBasketState, basket =>
export const getBasketLastTimeProductAdded = createSelector(getBasketState, basket => basket.lastTimeProductAdded);
+export const getBasketEligibleAddresses = createSelector(getBasketState, basket => basket.eligibleAddresses);
+
export const getBasketEligibleShippingMethods = createSelector(
getBasketState,
basket => basket.eligibleShippingMethods
diff --git a/src/app/pages/checkout-address/checkout-address-anonymous/checkout-address-anonymous.component.ts b/src/app/pages/checkout-address/checkout-address-anonymous/checkout-address-anonymous.component.ts
index 9de8aad462..60336560c6 100644
--- a/src/app/pages/checkout-address/checkout-address-anonymous/checkout-address-anonymous.component.ts
+++ b/src/app/pages/checkout-address/checkout-address-anonymous/checkout-address-anonymous.component.ts
@@ -95,13 +95,15 @@ export class CheckoutAddressAnonymousComponent implements OnChanges {
? undefined
: this.form.get('shippingAddress').value.address;
- if (this.form.get('additionalAddressAttributes').value.taxationID) {
- this.checkoutFacade.setBasketCustomAttribute({
- name: 'taxationID',
- value: this.form.get('additionalAddressAttributes').value.taxationID,
- });
- } else {
- this.checkoutFacade.deleteBasketCustomAttribute('taxationID');
+ if (this.form.get('additionalAddressAttributes').get('taxationID')) {
+ if (this.form.get('additionalAddressAttributes').value.taxationID) {
+ this.checkoutFacade.setBasketCustomAttribute({
+ name: 'taxationID',
+ value: this.form.get('additionalAddressAttributes').value.taxationID,
+ });
+ } else {
+ this.checkoutFacade.deleteBasketCustomAttribute('taxationID');
+ }
}
if (shippingAddress) {
diff --git a/src/app/pages/checkout-address/checkout-address/checkout-address.component.html b/src/app/pages/checkout-address/checkout-address/checkout-address.component.html
index 4e904909ad..46872de995 100644
--- a/src/app/pages/checkout-address/checkout-address/checkout-address.component.html
+++ b/src/app/pages/checkout-address/checkout-address/checkout-address.component.html
@@ -16,6 +16,7 @@
{
let element: HTMLElement;
beforeEach(async () => {
+ const checkoutFacade = mock(CheckoutFacade);
+ when(checkoutFacade.eligibleAddresses$()).thenReturn(of([]));
await TestBed.configureTestingModule({
declarations: [
CheckoutAddressComponent,
@@ -32,6 +37,7 @@ describe('Checkout Address Component', () => {
MockDirective(ServerHtmlDirective),
],
imports: [TranslateModule.forRoot()],
+ providers: [{ provide: CheckoutFacade, useFactory: () => instance(checkoutFacade) }],
}).compileComponents();
});
diff --git a/src/app/pages/checkout-address/checkout-address/checkout-address.component.ts b/src/app/pages/checkout-address/checkout-address/checkout-address.component.ts
index 2c501551ea..d259a743c2 100644
--- a/src/app/pages/checkout-address/checkout-address/checkout-address.component.ts
+++ b/src/app/pages/checkout-address/checkout-address/checkout-address.component.ts
@@ -1,5 +1,8 @@
-import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core';
+import { ChangeDetectionStrategy, Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
+import { Observable, shareReplay } from 'rxjs';
+import { CheckoutFacade } from 'ish-core/facades/checkout.facade';
+import { Address } from 'ish-core/models/address/address.model';
import { Basket } from 'ish-core/models/basket/basket.model';
import { HttpError } from 'ish-core/models/http-error/http-error.model';
@@ -11,15 +14,23 @@ import { HttpError } from 'ish-core/models/http-error/http-error.model';
templateUrl: './checkout-address.component.html',
changeDetection: ChangeDetectionStrategy.OnPush,
})
-export class CheckoutAddressComponent {
+export class CheckoutAddressComponent implements OnInit {
@Input({ required: true }) basket: Basket;
@Input() error: HttpError;
@Output() nextStep = new EventEmitter();
+ eligibleAddresses$: Observable;
+
submitted = false;
active: 'invoice' | 'shipping';
+ constructor(private checkoutFacade: CheckoutFacade) {}
+
+ ngOnInit(): void {
+ this.eligibleAddresses$ = this.checkoutFacade.eligibleAddresses$().pipe(shareReplay(1));
+ }
+
/**
* leads to next checkout page (checkout shipping)
*/
diff --git a/src/app/shared/components/checkout/basket-invoice-address-widget/basket-invoice-address-widget.component.spec.ts b/src/app/shared/components/checkout/basket-invoice-address-widget/basket-invoice-address-widget.component.spec.ts
index 86252a11ae..468468f4ac 100644
--- a/src/app/shared/components/checkout/basket-invoice-address-widget/basket-invoice-address-widget.component.spec.ts
+++ b/src/app/shared/components/checkout/basket-invoice-address-widget/basket-invoice-address-widget.component.spec.ts
@@ -34,7 +34,6 @@ describe('Basket Invoice Address Widget Component', () => {
when(checkoutFacade.basketInvoiceAddress$).thenReturn(EMPTY);
accountFacade = mock(AccountFacade);
- when(accountFacade.addresses$()).thenReturn(EMPTY);
when(accountFacade.isLoggedIn$).thenReturn(of(true));
await TestBed.configureTestingModule({
@@ -85,7 +84,6 @@ describe('Basket Invoice Address Widget Component', () => {
describe('with address on basket', () => {
beforeEach(() => {
when(checkoutFacade.basketInvoiceAddress$).thenReturn(of(BasketMockData.getAddress()));
- when(accountFacade.addresses$()).thenReturn(of([BasketMockData.getAddress()]));
});
it('should render if invoice is set', () => {
@@ -165,7 +163,7 @@ describe('Basket Invoice Address Widget Component', () => {
beforeEach(() => {
when(checkoutFacade.basketInvoiceAddress$).thenReturn(of(addresses[1]));
- when(accountFacade.addresses$()).thenReturn(of(addresses));
+ component.eligibleAddresses$ = of(addresses);
});
it('should only use valid addresses for selection display', done => {
diff --git a/src/app/shared/components/checkout/basket-invoice-address-widget/basket-invoice-address-widget.component.ts b/src/app/shared/components/checkout/basket-invoice-address-widget/basket-invoice-address-widget.component.ts
index bce5370518..ae9b943563 100644
--- a/src/app/shared/components/checkout/basket-invoice-address-widget/basket-invoice-address-widget.component.ts
+++ b/src/app/shared/components/checkout/basket-invoice-address-widget/basket-invoice-address-widget.component.ts
@@ -3,7 +3,7 @@ import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { UntypedFormControl, UntypedFormGroup } from '@angular/forms';
import { FormlyFieldConfig } from '@ngx-formly/core';
import { BehaviorSubject, Observable, combineLatest } from 'rxjs';
-import { filter, map, shareReplay, take } from 'rxjs/operators';
+import { filter, map, take } from 'rxjs/operators';
import { AccountFacade } from 'ish-core/facades/account.facade';
import { CheckoutFacade } from 'ish-core/facades/checkout.facade';
@@ -22,6 +22,7 @@ import { FormsService } from 'ish-shared/forms/utils/forms.service';
changeDetection: ChangeDetectionStrategy.Default,
})
export class BasketInvoiceAddressWidgetComponent implements OnInit {
+ @Input({ required: true }) eligibleAddresses$: Observable;
@Input() showErrors = true;
@Output() collapseChange = new BehaviorSubject(true);
@@ -35,7 +36,6 @@ export class BasketInvoiceAddressWidgetComponent implements OnInit {
}
invoiceAddress$: Observable;
addresses$: Observable;
- customerAddresses$: Observable;
isLoggedIn$: Observable;
form = new UntypedFormGroup({});
@@ -53,7 +53,6 @@ export class BasketInvoiceAddressWidgetComponent implements OnInit {
) {}
ngOnInit() {
- this.customerAddresses$ = this.accountFacade.addresses$().pipe(shareReplay(1));
this.invoiceAddress$ = this.checkoutFacade.basketInvoiceAddress$;
this.invoiceAddress$
@@ -68,7 +67,7 @@ export class BasketInvoiceAddressWidgetComponent implements OnInit {
.subscribe(label => (this.emptyOptionLabel = label));
// prepare data for invoice select drop down
- this.addresses$ = combineLatest([this.customerAddresses$, this.invoiceAddress$]).pipe(
+ this.addresses$ = combineLatest([this.eligibleAddresses$, this.invoiceAddress$]).pipe(
map(([addresses, invoiceAddress]) =>
addresses?.filter(address => address.invoiceToAddress).filter(address => address.id !== invoiceAddress?.id)
)
diff --git a/src/app/shared/components/checkout/basket-shipping-address-widget/basket-shipping-address-widget.component.html b/src/app/shared/components/checkout/basket-shipping-address-widget/basket-shipping-address-widget.component.html
index ec27c23cb8..4b18e1a24d 100644
--- a/src/app/shared/components/checkout/basket-shipping-address-widget/basket-shipping-address-widget.component.html
+++ b/src/app/shared/components/checkout/basket-shipping-address-widget/basket-shipping-address-widget.component.html
@@ -6,11 +6,12 @@ {{ 'checkout.address.shipping.label' | translate }}
@@ -18,10 +19,11 @@ {{ 'checkout.address.shipping.label' | translate }}
diff --git a/src/app/shared/components/checkout/basket-shipping-address-widget/basket-shipping-address-widget.component.spec.ts b/src/app/shared/components/checkout/basket-shipping-address-widget/basket-shipping-address-widget.component.spec.ts
index bb800dc284..1df143e956 100644
--- a/src/app/shared/components/checkout/basket-shipping-address-widget/basket-shipping-address-widget.component.spec.ts
+++ b/src/app/shared/components/checkout/basket-shipping-address-widget/basket-shipping-address-widget.component.spec.ts
@@ -1,6 +1,5 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
-import { RouterTestingModule } from '@angular/router/testing';
import { FaIconComponent } from '@fortawesome/angular-fontawesome';
import { NgbCollapse } from '@ng-bootstrap/ng-bootstrap';
import { TranslateModule } from '@ngx-translate/core';
@@ -38,15 +37,9 @@ describe('Basket Shipping Address Widget Component', () => {
when(checkoutFacade.basketInvoiceAndShippingAddressEqual$).thenReturn(of(false));
when(accountFacade.isLoggedIn$).thenReturn(of(true));
- when(accountFacade.addresses$()).thenReturn(EMPTY);
await TestBed.configureTestingModule({
- imports: [
- FeatureToggleModule.forTesting('addressDoctor'),
- FormlyTestingModule,
- RouterTestingModule,
- TranslateModule.forRoot(),
- ],
+ imports: [FeatureToggleModule.forTesting('addressDoctor'), FormlyTestingModule, TranslateModule.forRoot()],
declarations: [
BasketShippingAddressWidgetComponent,
MockComponent(AddressComponent),
@@ -102,7 +95,7 @@ describe('Basket Shipping Address Widget Component', () => {
beforeEach(() => {
const address = BasketMockData.getAddress();
when(checkoutFacade.basketShippingAddress$).thenReturn(of(address));
- when(accountFacade.addresses$()).thenReturn(of([address, { ...address, id: 'test' }]));
+ component.eligibleAddresses$ = of([address, { ...address, id: 'test' }]);
});
it('should render if shipping is set', () => {
@@ -226,7 +219,7 @@ describe('Basket Shipping Address Widget Component', () => {
beforeEach(() => {
when(checkoutFacade.basketShippingAddress$).thenReturn(of(addresses[1]));
- when(accountFacade.addresses$()).thenReturn(of(addresses));
+ component.eligibleAddresses$ = of(addresses);
});
it('should only use valid addresses for selection display', done => {
diff --git a/src/app/shared/components/checkout/basket-shipping-address-widget/basket-shipping-address-widget.component.ts b/src/app/shared/components/checkout/basket-shipping-address-widget/basket-shipping-address-widget.component.ts
index 49cf024553..99f290eddc 100644
--- a/src/app/shared/components/checkout/basket-shipping-address-widget/basket-shipping-address-widget.component.ts
+++ b/src/app/shared/components/checkout/basket-shipping-address-widget/basket-shipping-address-widget.component.ts
@@ -3,7 +3,7 @@ import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { UntypedFormControl, UntypedFormGroup } from '@angular/forms';
import { FormlyFieldConfig } from '@ngx-formly/core/lib/core';
import { BehaviorSubject, Observable, combineLatest } from 'rxjs';
-import { filter, map, shareReplay, take } from 'rxjs/operators';
+import { filter, map, take } from 'rxjs/operators';
import { AccountFacade } from 'ish-core/facades/account.facade';
import { CheckoutFacade } from 'ish-core/facades/checkout.facade';
@@ -22,6 +22,7 @@ import { FormsService } from 'ish-shared/forms/utils/forms.service';
changeDetection: ChangeDetectionStrategy.Default,
})
export class BasketShippingAddressWidgetComponent implements OnInit {
+ @Input({ required: true }) eligibleAddresses$: Observable;
@Input() showErrors = true;
@Output() collapseChange = new BehaviorSubject(true);
@@ -36,7 +37,6 @@ export class BasketShippingAddressWidgetComponent implements OnInit {
shippingAddress$: Observable;
addresses$: Observable;
- customerAddresses$: Observable;
displayAddAddressLink$: Observable;
basketInvoiceAndShippingAddressEqual$: Observable;
@@ -61,7 +61,6 @@ export class BasketShippingAddressWidgetComponent implements OnInit {
}
ngOnInit() {
- this.customerAddresses$ = this.accountFacade.addresses$().pipe(shareReplay(1));
this.shippingAddress$ = this.checkoutFacade.basketShippingAddress$;
this.basketInvoiceAndShippingAddressEqual$ = this.checkoutFacade.basketInvoiceAndShippingAddressEqual$;
this.basketShippingAddressDeletable$ = this.checkoutFacade.basketShippingAddressDeletable$;
@@ -78,7 +77,7 @@ export class BasketShippingAddressWidgetComponent implements OnInit {
.subscribe(label => (this.emptyOptionLabel = label));
// prepare data for shipping select drop down
- this.addresses$ = combineLatest([this.customerAddresses$, this.shippingAddress$]).pipe(
+ this.addresses$ = combineLatest([this.eligibleAddresses$, this.shippingAddress$]).pipe(
map(([addresses, shippingAddress]) =>
addresses?.filter(address => address.shipToAddress).filter(address => address.id !== shippingAddress?.id)
)