Skip to content

Commit

Permalink
fix: respect ICM setting basket.maxItemQuantity (#993)
Browse files Browse the repository at this point in the history
* docs: add migration hint

Co-authored-by: Silke <s.grueber@intershop.de>
  • Loading branch information
dhhyi and SGrueber authored Feb 1, 2022
1 parent 3838171 commit 81341ce
Show file tree
Hide file tree
Showing 6 changed files with 56 additions and 35 deletions.
4 changes: 4 additions & 0 deletions docs/guides/migrations.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,10 @@ The feature toggle 'advancedVariationHandling' has been removed.
Instead the ICM channel preference 'AdvancedVariationHandling' is used to configure it.
You will find this preference as 'List View' in the ICM backoffice under Channel Preferences -> Product Variations.

The ICM channel preference 'basket.maxItemQuantity' is included to validate the product quantity if no specific setting is defined on the product.
You find this preference as 'Maximum Quantity per Product in Cart' under the Application Settings -> Shopping Cart & Checkout.
The default value is 100.

## 1.1 to 1.2

The `dist` folder now only contains results of the build process (except for `healthcheck.js`).
Expand Down
14 changes: 13 additions & 1 deletion src/app/core/facades/product-context.facade.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { Category } from 'ish-core/models/category/category.model';
import { ProductView } from 'ish-core/models/product-view/product-view.model';
import { ProductCompletenessLevel } from 'ish-core/models/product/product.model';

import { AppFacade } from './app.facade';
import {
EXTERNAL_DISPLAY_PROPERTY_PROVIDER,
ExternalDisplayPropertiesProvider,
Expand Down Expand Up @@ -39,9 +40,16 @@ describe('Product Context Facade', () => {
when(shoppingFacade.productVariationCount$(anything())).thenReturn(of(undefined));
when(shoppingFacade.inCompareProducts$(anything())).thenReturn(of(false));

const appFacade = mock(AppFacade);
when(appFacade.serverSetting$(anything())).thenReturn(of(undefined));

TestBed.configureTestingModule({
imports: [TranslateModule.forRoot()],
providers: [ProductContextFacade, { provide: ShoppingFacade, useFactory: () => instance(shoppingFacade) }],
providers: [
ProductContextFacade,
{ provide: ShoppingFacade, useFactory: () => instance(shoppingFacade) },
{ provide: AppFacade, useFactory: () => instance(appFacade) },
],
});

context = TestBed.inject(ProductContextFacade);
Expand Down Expand Up @@ -746,11 +754,15 @@ describe('Product Context Facade', () => {

when(shoppingFacade.product$(anyString(), anything())).thenCall(sku => of({ ...product, sku }));

const appFacade = mock(AppFacade);
when(appFacade.serverSetting$(anything())).thenReturn(of(undefined));

TestBed.configureTestingModule({
imports: [TranslateModule.forRoot()],
providers: [
ProductContextFacade,
{ provide: ShoppingFacade, useFactory: () => instance(shoppingFacade) },
{ provide: AppFacade, useFactory: () => instance(appFacade) },
{ provide: EXTERNAL_DISPLAY_PROPERTY_PROVIDER, useClass: ProviderA, multi: true },
{ provide: EXTERNAL_DISPLAY_PROPERTY_PROVIDER, useClass: ProviderB, multi: true },
{ provide: EXTERNAL_DISPLAY_PROPERTY_PROVIDER, useClass: ProviderC, multi: true },
Expand Down
37 changes: 25 additions & 12 deletions src/app/core/facades/product-context.facade.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { generateProductUrl } from 'ish-core/routing/product/product.route';
import { mapToProperty, whenTruthy } from 'ish-core/utils/operators';
import { ProductContextDisplayPropertiesService } from 'ish-core/utils/product-context-display-properties/product-context-display-properties.service';

import { AppFacade } from './app.facade';
import { ShoppingFacade } from './shopping.facade';

declare type DisplayEval = ((product: ProductView) => boolean) | boolean;
Expand Down Expand Up @@ -106,7 +107,7 @@ export interface ProductContext {
@Injectable()
export class ProductContextFacade extends RxState<ProductContext> {
private privateConfig$ = new BehaviorSubject<Partial<ProductContextDisplayProperties>>({});
private loggingActive = false;
private loggingActive: boolean;
private lazyFieldsInitialized: string[] = [];

set config(config: Partial<ProductContextDisplayProperties>) {
Expand All @@ -118,7 +119,12 @@ export class ProductContextFacade extends RxState<ProductContext> {
mapToProperty('sku')
);

constructor(private shoppingFacade: ShoppingFacade, private translate: TranslateService, injector: Injector) {
constructor(
private shoppingFacade: ShoppingFacade,
private appFacade: AppFacade,
private translate: TranslateService,
injector: Injector
) {
super();

this.set({
Expand Down Expand Up @@ -172,30 +178,37 @@ export class ProductContextFacade extends RxState<ProductContext> {

this.connect(
'minQuantity',
combineLatest([this.select('product', 'minOrderQuantity'), this.select('allowZeroQuantity')]).pipe(
map(([minOrderQuantity, allowZeroQuantity]) => (allowZeroQuantity ? 0 : minOrderQuantity))
combineLatest([this.select('product'), this.select('allowZeroQuantity')]).pipe(
map(([product, allowZeroQuantity]) => (allowZeroQuantity ? 0 : product.minOrderQuantity || 1))
)
);

this.connect('maxQuantity', this.select('product', 'maxOrderQuantity'));
this.connect('stepQuantity', this.select('product', 'stepOrderQuantity'));
this.connect(
'maxQuantity',
combineLatest([this.select('product'), this.appFacade.serverSetting$<number>('basket.maxItemQuantity')]).pipe(
map(([product, fromConfig]) => product?.maxOrderQuantity || fromConfig || 100)
)
);
this.connect('stepQuantity', this.select('product').pipe(map(product => product?.stepOrderQuantity || 1)));

this.connect(
combineLatest([
this.select('product'),
this.select('minQuantity'),
this.select('maxQuantity'),
this.select('stepQuantity'),
this.select('quantity').pipe(distinctUntilChanged()),
]).pipe(
map(([product, minOrderQuantity, quantity]) => {
map(([product, minOrderQuantity, maxOrderQuantity, stepQuantity, quantity]) => {
if (product && !product.failed) {
if (Number.isNaN(quantity)) {
return this.translate.instant('product.quantity.integer.text');
} else if (quantity < minOrderQuantity) {
return this.translate.instant('product.quantity.greaterthan.text', { 0: product.minOrderQuantity });
} else if (quantity > product.maxOrderQuantity) {
return this.translate.instant('product.quantity.lessthan.text', { 0: product.maxOrderQuantity });
} else if (quantity % product.stepOrderQuantity !== 0) {
return this.translate.instant('product.quantity.step.text', { 0: product.stepOrderQuantity });
return this.translate.instant('product.quantity.greaterthan.text', { 0: minOrderQuantity });
} else if (quantity > maxOrderQuantity) {
return this.translate.instant('product.quantity.lessthan.text', { 0: maxOrderQuantity });
} else if (quantity % stepQuantity !== 0) {
return this.translate.instant('product.quantity.step.text', { 0: stepQuantity });
}
}
return;
Expand Down
12 changes: 6 additions & 6 deletions src/app/core/facades/selected-product-context.facade.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,10 @@ export class SelectedProductContextFacade extends ProductContextFacade {
shoppingFacade: ShoppingFacade,
translate: TranslateService,
injector: Injector,
private router: Router,
private appFacade: AppFacade
router: Router,
appFacade: AppFacade
) {
super(shoppingFacade, translate, injector);
super(shoppingFacade, appFacade, translate, injector);
this.set('requiredCompletenessLevel', () => true);
this.connect('categoryId', shoppingFacade.selectedCategoryId$);
this.connect('sku', shoppingFacade.selectedProductId$);
Expand All @@ -28,7 +28,7 @@ export class SelectedProductContextFacade extends ProductContextFacade {
this.select('product').pipe(
filter(ProductVariationHelper.hasDefaultVariation),
concatMap(p =>
this.appFacade.serverSetting$<boolean>('preferences.ChannelPreferences.EnableAdvancedVariationHandling').pipe(
appFacade.serverSetting$<boolean>('preferences.ChannelPreferences.EnableAdvancedVariationHandling').pipe(
filter(advancedVariationHandling => advancedVariationHandling !== undefined && !advancedVariationHandling),
map(() => p.defaultVariationSKU)
)
Expand All @@ -39,10 +39,10 @@ export class SelectedProductContextFacade extends ProductContextFacade {
this.hold(
this.select('productURL').pipe(
skip(1),
withLatestFrom(this.appFacade.routingInProgress$),
withLatestFrom(appFacade.routingInProgress$),
filter(([, progress]) => !progress)
),
([url]) => this.router.navigateByUrl(url)
([url]) => router.navigateByUrl(url)
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ Object {
},
"longDescription": undefined,
"manufacturer": "Kodak",
"maxOrderQuantity": 100,
"maxOrderQuantity": undefined,
"minOrderQuantity": 5,
"name": "Kodak M series EasyShare M552",
"packingUnit": "pcs.",
Expand All @@ -61,7 +61,7 @@ Object {
},
"shortDescription": "EasyShare M552, 14MP, 6.858 cm (2.7 \\") LCD, 4x, 28mm, HD 720p, Black",
"sku": "7912057",
"stepOrderQuantity": 1,
"stepOrderQuantity": undefined,
"type": "Product",
}
`;
20 changes: 6 additions & 14 deletions src/app/core/models/product/product.mapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,10 +47,6 @@ export class ProductMapper {
private categoryMapper: CategoryMapper
) {}

private defaultMinOrderQuantity = 1;
private defaultMaxOrderQuantity = 100;
private defaultStepOrderQuantity = 1;

static parseSkuFromURI(uri: string): string {
const match = /products[^\/]*\/([^\?]*)/.exec(uri);
if (match) {
Expand Down Expand Up @@ -157,13 +153,9 @@ export class ProductMapper {
retrieveStubAttributeValue(data, 'inStock')
),
longDescription: undefined,
minOrderQuantity:
retrieveStubAttributeValue<{ value: number }>(data, 'minOrderQuantity')?.value || this.defaultMinOrderQuantity,
maxOrderQuantity:
retrieveStubAttributeValue<{ value: number }>(data, 'maxOrderQuantity')?.value || this.defaultMaxOrderQuantity,
stepOrderQuantity:
retrieveStubAttributeValue<{ value: number }>(data, 'stepOrderQuantity')?.value ||
this.defaultStepOrderQuantity,
minOrderQuantity: retrieveStubAttributeValue<{ value: number }>(data, 'minOrderQuantity')?.value,
maxOrderQuantity: retrieveStubAttributeValue<{ value: number }>(data, 'maxOrderQuantity')?.value,
stepOrderQuantity: retrieveStubAttributeValue<{ value: number }>(data, 'stepOrderQuantity')?.value,
packingUnit: retrieveStubAttributeValue(data, 'packingUnit'),
attributeGroups: data.attributeGroup && mapAttributeGroups(data),
readyForShipmentMin: undefined,
Expand All @@ -187,9 +179,9 @@ export class ProductMapper {
shortDescription: data.shortDescription,
longDescription: data.longDescription,
available: this.calculateAvailable(data.availability, data.inStock),
minOrderQuantity: data.minOrderQuantity || this.defaultMinOrderQuantity,
maxOrderQuantity: data.maxOrderQuantity || this.defaultMaxOrderQuantity,
stepOrderQuantity: data.stepOrderQuantity || this.defaultStepOrderQuantity,
minOrderQuantity: data.minOrderQuantity,
maxOrderQuantity: data.maxOrderQuantity,
stepOrderQuantity: data.stepOrderQuantity,
packingUnit: data.packingUnit,
availableStock: data.availableStock,
attributes: data.attributeGroups?.PRODUCT_DETAIL_ATTRIBUTES?.attributes || data.attributes,
Expand Down

0 comments on commit 81341ce

Please sign in to comment.