import { Injectable } from '@angular/core'; import { catchError, map, switchMap, take, tap } from 'rxjs/operators'; import { HttpClient } from '@angular/common/http'; import { LoggerService } from '@arrow/bom/logger'; import { ConfigService } from '@arrow/bom/config'; import { Router } from '@angular/router'; import { TranslateService } from '@ngx-translate/core'; import { Observable, of } from 'rxjs'; import { MOQModalComponent, MoqDialogData } from '@arrow/bom/shared'; import { WptDialog } from '@arrow/warpaint/dialog'; import { AnalyticsEvent, AnalyticsService, ApiSocketListenAction, cookieConfig, getCookie, setCookie, DialogService, ItemSelection, QueryParams, SocketsService, UserService, ApiSocketEmitAction, PaginationParamsModel } from '@arrow/bom/core'; import { AddedDialogComponent } from './added-dialog/added-dialog.component'; import { CartRequest } from './models/cart-request.model'; @Injectable() export class Cart2Service { private AUTO_ROUND_EVENT:string = 'Auto Round MOQ'; private _eventNameWAnalyticEvent: Map = new Map([ ['Buy Selected', AnalyticsEvent.ADD_TO_CART_BOM_SELECTED], ['Buy All', AnalyticsEvent.ADD_TO_CART_BOM_ALL], ['Add to cart', AnalyticsEvent.ADD_TO_CART_BOM] ]); constructor( private http: HttpClient, private dialog: DialogService, private wptDialog: WptDialog, private logger: LoggerService, private config: ConfigService, private translate: TranslateService, private analyticsService: AnalyticsService, private sockets: SocketsService, private router: Router, private userService: UserService ) {} public add( bomId: string, selectionObj: ItemSelection, totalPartCount: number, analyticsEvent?: string ): Observable { const selectionStr: string = new QueryParams(selectionObj).toString(); const endpointURL = `${this.config.apiUrlWithoutVersion}/bom/api/bom/${bomId}/moq-parts?${selectionStr}`; let selectedPartCount = selectionObj.rowIds.length; if (selectionObj.selectionInverted) selectedPartCount = totalPartCount - selectionObj.rowIds.length; if (!selectionObj.selectionInverted && !selectionObj.rowIds.length) selectedPartCount = totalPartCount; let cartRequest = { bomId, originalRowsSelection: selectionObj, autoRoundedParts: null } return this.http .get(endpointURL, { withCredentials: true }) // Get parts that do not meet MOQ .pipe( switchMap((list: any[]) => { if (list.length) { return this.wptDialog .open(MOQModalComponent, { // Ensure modal window content is scrollable on smaller screen resolutions (i.e. 320px) panelClass: 'bom-autoround-moq-cdk-overlay-pane', // See _normalize.scss data: { isAutoRound: false, items: list, totalParts: selectedPartCount } }) // Get user accepted quantity increases .afterClosed() .pipe( map(autoRoundedParts => { if(!autoRoundedParts){ return null; } cartRequest.autoRoundedParts = autoRoundedParts; return cartRequest; }) ); } else { return of(cartRequest); } }), switchMap((cartRequest: CartRequest) => { if (!cartRequest) { return of(cartRequest); } return this._getCartId().pipe( switchMap((cartId: string) => { // Get cart id return this.http.post( `${this.config.apiUrlWithoutVersion}/bom/api/cart/${cartId}/items`, cartRequest, { withCredentials: true } ); }) ); }), switchMap((response: any) => { if (!response) { return of(response); } this._trackAnalytics(analyticsEvent, response.partsInformation); return this.wptDialog .open(AddedDialogComponent, { // Show parts added to cart modal data: response }) .beforeClosed() .pipe( tap((viewCart: boolean) => { if (viewCart) { if (this.config.getTarget() === 'arrowcom') { return window.open( this.config.arrowUrl + this.config.getRoute('cart'), '_blank' ); } return this.router.navigate([this.config.getRoute('cart')]); } }) ); }), catchError((error: any) => this._handleError(error)) ); } public autoRound( bomId: string, selectionObj: ItemSelection, totalPartCount: number, paginationParams: PaginationParamsModel, analyticsEvent?: string, ): Observable { const selectionStr: string = new QueryParams(selectionObj).toString(); const endpointURL = `${this.config.apiUrlWithoutVersion}/bom/api/bom/${bomId}/moq-parts?${selectionStr}`; let selectedPartCount = selectionObj.rowIds.length; selectedPartCount = totalPartCount; return this.http .get(endpointURL, { withCredentials: true }) // Get parts that do not meet MOQ .pipe( switchMap((list: any[]) => { return this.wptDialog .open(MOQModalComponent, { // Ensure modal window content is scrollable on smaller screen resolutions (i.e. 320px) panelClass: 'bom-autoround-moq-cdk-overlay-pane', // See _normalize.scss data: { isAutoRound: true, items: list, totalParts: selectedPartCount } }) // Get user accepted quantity increases .afterClosed() .pipe( map(moqParts => { if (!moqParts) return !!moqParts; this.sockets.emitAction(ApiSocketEmitAction.BOM_AUTO_ROUND, { bomId: bomId, autoRoundParts: moqParts, paginationRequest: paginationParams }); // this analytics event may require more work in the future this._trackAnalytics(analyticsEvent); return {bomId: bomId, autoRoundParts: moqParts}; }) ); }), catchError((error: any) => this._handleError(error)) ); } public getPurchasablePartsList( bomId: string, selection: ItemSelection ): Observable { return this.http .post( `${this.config.apiUrl}/${bomId}/checkout`, { selectedRows: selection }, { withCredentials: true } ) .pipe(catchError((error: any) => this._handleError(error))); } public requestQuoteCount(): Observable { return this._getQuoteId().pipe( switchMap((cartId: string) => { return this.http .get( `${this.config.apiUrlWithoutVersion}/bom/api/quote/${cartId}/items/count`, { withCredentials: true } ) .pipe(catchError((error: any) => this._handleError(error))); }) ); } public requestCartCountMyArrow(): Observable { return this._getCartId().pipe( switchMap((cartId: string) => { return this.http .get( `${this.config.apiUrlWithoutVersion}/bom/api/cart/${cartId}/items/count`, { withCredentials: true } ) .pipe(catchError((error: any) => this._handleError(error))); }) ); } private _handleError(error: Error): any { this.dialog.alert( this.translate.instant('Error'), error.message || this.translate.instant('Unknown-Cart-Error') ); this.logger.warn(error); return null; } private _getCartId(): Observable { if (this.config.isArrowcom()) { if (this.userService.isLoggedIn()) { return of('current'); } const cartId = getCookie(cookieConfig.cartida); if (cartId) { return of(cartId); } return this.http .post(`${this.config.apiUrlWithoutVersion}/bom/api/cart`, { withCredentials: true }) .pipe( map((data: any) => { setCookie(cookieConfig.cartida, data.cartId); return data.cartId; }) ); } return this.http .get(`${this.config.apiUrlWithoutVersion}/bom/api/cart`, { withCredentials: true }) .pipe(map((data: any) => data.cartId)); } private _getQuoteId(): Observable { //get quote id for myarrow return this.http .get(`${this.config.apiUrlWithoutVersion}/bom/api/quote`, { withCredentials: true }) .pipe(map((data: any) => data.cartId)); } private _trackAnalytics(eventType, partsAdded?): void { if (eventType === 'Add to cart' || eventType === this.AUTO_ROUND_EVENT) { this.analyticsService.googleTrackEvent(AnalyticsEvent.CLICK, { category: 'BOM', action: eventType, label: 1, value: 1 }); } // Analytics Event for arrowcom with custom dimensions if (partsAdded && partsAdded.length > 0 && this.config.isArrowcom()) { const eventName = this.getCartEventType(eventType); this.analyticsService.googleTrack(eventName, partsAdded); } else if (eventType !== this.AUTO_ROUND_EVENT){ this.sockets .observeAction(ApiSocketListenAction.ADD_TO_CART_METRICS) .pipe(take(1)) .subscribe(analyticsData => { const missedStock = analyticsData.PotentialPartPrice > 0 ? analyticsData.PotentialPartPrice - analyticsData.AvailablePartPrice : '0'; this.analyticsService.googleTrackEvent(AnalyticsEvent.CLICK, { category: 'BOM', action: eventType, label: analyticsData.NumberOfLinesAdded ? analyticsData.NumberOfLinesAdded : '0', value: analyticsData.NumberOfLinesAdded ? analyticsData.NumberOfLinesAdded : '0', bomAvailablePartPrice: analyticsData.AvailablePartPrice ? analyticsData.AvailablePartPrice : '0', bomPotentialPartPrice: analyticsData.PotentialPartPrice ? analyticsData.PotentialPartPrice : '0', bomMissedStock: analyticsData ? missedStock : '0', bomNumberNonpurchaseableItemsAttempted: analyticsData.NonPurchasableCount ? analyticsData.NonPurchasableCount : '0' }); }); } } private getCartEventType(cartEventName: string): AnalyticsEvent{ if(!cartEventName) throw new Error("eventName cant be null or undefined"); const analyticEvent = this._eventNameWAnalyticEvent.get(cartEventName); if(!analyticEvent) throw new Error(`There is no match for ${cartEventName} in AnalyticsEvent`); return analyticEvent; } }