Skip to content

Commit

Permalink
CXSPA-8577: Display real time stock in PDP
Browse files Browse the repository at this point in the history
  • Loading branch information
tiwariakshay55 committed Oct 26, 2024
1 parent c2c3a86 commit 493c9fb
Show file tree
Hide file tree
Showing 14 changed files with 145 additions and 117 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import {
FeatureConfigService,
FeatureToggles,
Product,
ProductScope,
isNotNullable,
} from '@spartacus/core';
import {
Expand All @@ -52,6 +53,7 @@ export class AddToCartComponent implements OnInit, OnDestroy {
@Input() showQuantity = true;
@Input() options: CartItemComponentOptions;
@Input() pickupStore: string | undefined;
@Input() sapUnit: string;
/**
* As long as we do not support #5026, we require product input, as we need
* a reference to the product model to fetch the stock data.
Expand Down Expand Up @@ -131,12 +133,15 @@ export class AddToCartComponent implements OnInit, OnDestroy {
this.subscription = (
this.productListItemContext
? this.productListItemContext.product$
: this.currentProductService.getProduct()
: this.featureToggles.showRealTimeStockInPDP
? this.currentProductService.getProduct(ProductScope.UNIT)
: this.currentProductService.getProduct()
)
.pipe(filter(isNotNullable))
.subscribe((product) => {
this.productCode = product.code ?? '';
this.refreshLiveStockData();
this.sapUnit = product.sapUnit?.sapCode ?? '';
this.getRealTimeStock();
this.setStockInfo(product);
this.cd.markForCheck();
});
Expand All @@ -161,19 +166,13 @@ export class AddToCartComponent implements OnInit, OnDestroy {
}
}

refreshLiveStockData(): void {
if (this.featureToggles.realTimeStockDispaly) {
getRealTimeStock(): void {
if (this.featureToggles.showRealTimeStockInPDP) {
this.currentProductService
.getRealTimeStockData(this.productCode)
.getRealTimeStock(this.productCode, this.sapUnit)
.pipe(take(1))
.subscribe((quantity: string) => {
this.realTimeStock = quantity;
this.cd.markForCheck();
});
this.currentProductService
.getRealTimeStockData(this.productCode)
.pipe(take(1))
.subscribe((availability: string) => {
.subscribe(({ quantity, availability }) => {
this.maxQuantity = Number(quantity);
this.hasStock = Boolean(
availability && availability !== 'outOfStock'
);
Expand All @@ -189,21 +188,13 @@ export class AddToCartComponent implements OnInit, OnDestroy {
* When out of stock, display no numerical value.
*/
getInventory(): string {
if (this.featureToggles.realTimeStockDispaly) {
return this.inventoryThreshold
? this.realTimeStock + '+'
: this.realTimeStock;
if (this.hasStock) {
const quantityDisplay = this.maxQuantity
? this.maxQuantity.toString()
: '';
return this.inventoryThreshold ? quantityDisplay + '+' : quantityDisplay;
} else {
if (this.hasStock) {
const quantityDisplay = this.maxQuantity
? this.maxQuantity.toString()
: '';
return this.inventoryThreshold
? quantityDisplay + '+'
: quantityDisplay;
} else {
return '';
}
return '';
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -683,7 +683,7 @@ export interface FeatureTogglesInterface {
* Enables Real time stock display in the PDP page.
* when set to `true`, the user will be able to see the real time stock in PDP
*/
realTimeStockDispaly?: boolean;
showRealTimeStockInPDP?: boolean;
}

export const defaultFeatureToggles: Required<FeatureTogglesInterface> = {
Expand Down Expand Up @@ -787,5 +787,5 @@ export const defaultFeatureToggles: Required<FeatureTogglesInterface> = {
enablePasswordsCannotMatchInPasswordUpdateForm: false,
allPageMetaResolversEnabledInCsr: false,
useExtendedMediaComponentConfiguration: false,
realTimeStockDispaly: false,
showRealTimeStockInPDP: false,
};
8 changes: 8 additions & 0 deletions projects/core/src/model/product.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,14 @@ export interface Product {
variantType?: VariantType;
volumePrices?: Price[];
volumePricesFlag?: boolean;
sapUnit?: SapUnit;
}

export interface SapUnit {
code?: string;
name?: string;
availabilityCode?: string;
sapCode?: string;
}

export enum VariantQualifier {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { OccProductAdapter } from './occ-product.adapter';
import createSpy = jasmine.createSpy;

const productCode = 'testCode';
const unit = 'EA';
const product = {
code: productCode,
name: 'testProduct',
Expand Down Expand Up @@ -94,7 +95,9 @@ describe('OccProductAdapter', () => {
describe('loadRealTimeStock', () => {
it('should load real time stock', () => {
let result;
service.loadRealTimeStock(productCode).subscribe((res) => (result = res));
service
.loadRealTimeStock(productCode, unit)
.subscribe((res) => (result = res));

const mockReq = httpMock.expectOne((req) => {
return req.method === 'GET';
Expand Down
47 changes: 22 additions & 25 deletions projects/core/src/occ/adapters/product/occ-product.adapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import { Occ } from '../../occ-models';
import { OccEndpointsService } from '../../services/occ-endpoints.service';
import { ScopedDataWithUrl } from '../../services/occ-fields.service';
import { OccRequestsOptimizerService } from '../../services/occ-requests-optimizer.service';
import { ProductScope } from '@spartacus/core';
import { ProductScope } from '../../../product/model/product-scope';

@Injectable()
export class OccProductAdapter implements ProductAdapter {
Expand All @@ -27,32 +27,29 @@ export class OccProductAdapter implements ProductAdapter {
protected requestsOptimizer: OccRequestsOptimizerService
) {}

loadRealTimeStock(productCode: string): Observable<string> {
return this.http.get(this.getEndpoint(productCode, ProductScope.UNIT)).pipe(
loadRealTimeStock(
productCode: string,
unit: string
): Observable<{ quantity: string; availability: string }> {
const availabilityUrl = this.occEndpoints.buildUrl('product', {
scope: ProductScope.PRODUCT_AVAILABILITIES,
urlParams: {
productCode: productCode,
sapCode: unit,
},
});

return this.http.get(availabilityUrl).pipe(
take(1),
distinctUntilChanged(),
switchMap((response: any) => {
const availabilityUrl = this.occEndpoints.buildUrl('product', {
scope: ProductScope.PRODUCT_AVAILABILITIES,
urlParams: {
productCode: productCode,
sapCode: response.sapUnit.sapCode,
},
});

return this.http.get(availabilityUrl).pipe(
take(1),
distinctUntilChanged(),
switchMap((availabilities: any) => {
const quantity =
availabilities?.availabilityItems[0]?.unitAvailabilities[0]
?.quantity ?? '';
const availability =
availabilities?.availabilityItems[0]?.unitAvailabilities[0]
?.status ?? '';
return of(quantity, availability);
})
);
switchMap((availabilities: any) => {
const quantity =
availabilities?.availabilityItems[0]?.unitAvailabilities[0]
?.quantity ?? '';
const availability =
availabilities?.availabilityItems[0]?.unitAvailabilities[0]?.status ??
'';
return of({ quantity, availability });
})
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,5 +28,8 @@ export abstract class ProductAdapter {
*/
abstract loadMany?(products: ScopedProductData[]): ScopedProductData[];

abstract loadRealTimeStock(productCode: string): Observable<string>;
abstract loadRealTimeStock(
productCode: string,
unit: String
): Observable<{ quantity: string; availability: string }>;
}
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,9 @@ describe('ProductConnector', () => {
const adapter = TestBed.inject(ProductAdapter);

let result;
service.getRealTimeStock('333').subscribe((res) => (result = res));
service.getRealTimeStock('333', 'EA').subscribe((res) => (result = res));
expect(result).toBe('quantity333');
expect(adapter.loadRealTimeStock).toHaveBeenCalledWith('333');
expect(adapter.loadRealTimeStock).toHaveBeenCalledWith('333', 'EA');
});

it('getMany should call adapter', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,10 @@ export class ProductConnector {
return this.adapter.loadMany(products);
}

getRealTimeStock(productCode: string): Observable<string> {
return this.adapter.loadRealTimeStock(productCode);
getRealTimeStock(
productCode: string,
unit: string
): Observable<{ quantity: string; availability: string }> {
return this.adapter.loadRealTimeStock(productCode, unit);
}
}
26 changes: 17 additions & 9 deletions projects/core/src/product/facade/product.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ class MockProductLoadingService {
}

class MockProductConnector {
getRealTimeStock(_productCode: string) {
return of('10'); // Mocking the real-time stock data
getRealTimeStock(_productCode: string, _unit: string) {
return of('10', 'IN_STOCK');
}
}

Expand Down Expand Up @@ -151,12 +151,20 @@ describe('ProductService', () => {
expect(result).toBeTruthy();
});
});
describe('getRealTimeStockDatafromService(productCode)', () => {
it('should return real-time stock data for the given product code', async () => {
const stockData = await lastValueFrom(
service.getRealTimeStockDatafromService('testId')
);
expect(stockData).toEqual('10');
});
it('should call getRealTimeStock and return mock data', (done) => {
const mockProductCode = '12345';
const mockUnit = 'unit1';
const mockStockData = { quantity: '10', availability: 'inStock' };

spyOn(service, 'getRealTimeStockDatafromService').and.returnValue(
of(mockStockData)
);

service
.getRealTimeStockDatafromService(mockProductCode, mockUnit)
.subscribe((stockData) => {
expect(stockData).toEqual(mockStockData);
done();
});
});
});
17 changes: 11 additions & 6 deletions projects/core/src/product/facade/product.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
* SPDX-License-Identifier: Apache-2.0
*/

import { Injectable } from '@angular/core';
import { inject, Injectable } from '@angular/core';
import { select, Store } from '@ngrx/store';
import { Observable, of } from 'rxjs';
import { Product } from '../../model/product.model';
Expand All @@ -19,11 +19,13 @@ import { ProductSelectors } from '../store/selectors/index';
providedIn: 'root',
})
export class ProductService {
protected productConnector: ProductConnector;
constructor(
protected store: Store<StateWithProduct>,
protected productLoading: ProductLoadingService,
private productConnector: ProductConnector
) {}
protected productLoading: ProductLoadingService
) {
this.productConnector = inject(ProductConnector);
}

/**
* Returns the product observable. The product will be loaded
Expand Down Expand Up @@ -89,7 +91,10 @@ export class ProductService {
);
}

getRealTimeStockDatafromService(productCode: string): Observable<string> {
return this.productConnector.getRealTimeStock(productCode);
getRealTimeStockDatafromService(
productCode: string,
unit: string
): Observable<{ quantity: string; availability: string }> {
return this.productConnector.getRealTimeStock(productCode, unit);
}
}
Loading

0 comments on commit 493c9fb

Please sign in to comment.