import { EventEmitter, Injectable, Output } from '@angular/core'; import { HttpClient, HttpHeaders } from '@angular/common/http'; import { Observable, Subject, throwError as _throw } from 'rxjs'; import { ConfigService } from '@arrow/bom/config'; import { MessageBusService } from './message-bus/message-bus.service'; import { catchError, map, take, takeUntil, tap } from 'rxjs/operators'; import { ItemSelection } from './item.service'; import { AnalyticsService } from './analytics.service'; import { TranslateService } from '@ngx-translate/core'; import { BomNotificationService } from './bom-notification.service'; import { BomQuantity } from './bom/bom.interfaces'; import { SocketsService } from './sockets/sockets.service'; import { ApiSocketEmitAction } from './sockets/api.socket.emit.action.enum'; import { ApiSocketListenAction } from './sockets/api.socket.listen.action.enum'; import { PaginationParamsModel } from './pagination-params.model'; @Injectable() export class BomService { @Output() bomActiveQty: EventEmitter = new EventEmitter(); @Output() focusAskEngineerBtn: EventEmitter = new EventEmitter(); private bomId: string; private dataUrl: string; private dataUrlCore: string; private dataUrlNoVersion: string; private withCredentials: boolean; private defaultPageSize = 25; private pageNum = 1; private cloneBomCoreVersion: boolean = true; MY_ARROW_SE_PARTNER_ID = 'f6f723c2-d80c-11ea-ac66-0e46383d0623'; ARROW_SE_PARTNER_ID = 'b1331$d6b-cb6)e-11ea-9470-0e905(a95c930'; constructor( private http: HttpClient, private config: ConfigService, private _messageBus: MessageBusService, private _translate: TranslateService, private _bns: BomNotificationService, private _socketService: SocketsService, public analyticsService: AnalyticsService ) { this.dataUrl = `${this.config.apiUrl}`; this.dataUrlCore = this.config.apiCoreUrl; this.dataUrlNoVersion = `${this.config.apiUrlWithoutVersion}/bom`; } private _handleError(error: any): any { this._messageBus.to('ch:loader', { payload: false }); var errorMsg = error.hasOwnProperty('json') ? error.json().error : error ? error : 'Server error'; return _throw(errorMsg); } public onBomActiveQtyColumnChange(bomQuantity: BomQuantity) { this.bomActiveQty.emit(bomQuantity); } setDataUrl(bomId: string) { this.bomId = bomId; this.withCredentials = true; } setPageNumber(page: number) { if (!page || isNaN(page)) page = 1; // default to 1 if incorrect this.pageNum = page; } setPageSize(size: number) { this.defaultPageSize = size; } getData(filter: Object, sorting: Object): Observable { const url = `${this.dataUrl}/${this.bomId}/page/${this.pageNum}?perPage=${ this.defaultPageSize }&f=${JSON.stringify(filter)}&s=${JSON.stringify(sorting)}`; return ( this.http .get(url, { withCredentials: true }) // .map(response => response.json()) .pipe(catchError((error: any) => this._handleError(error))) ); } getStatus(): Observable { return ( this.http .get(`${this.dataUrl}/${this.bomId}/status`, { withCredentials: true }) // .map(response => response.json()) .pipe(catchError((error: any) => this._handleError(error))) ); } setMultiplier(multiplier: number) { const metadata = { multiplier }; const url = `${this.dataUrl}/${this.bomId}?metadata=${JSON.stringify( metadata )}`; return ( this.http .put(url, null, { withCredentials: true }) // .map(response => response.json()) .pipe(catchError((error: any) => this._handleError(error))) ); } // TODO: Remove this method? Is it used anywhere? saveRow( rowId: string, partName: string, manufacturer: string, quantity: number ): Observable { return ( this.http .put( `${this.dataUrl}/${this.bomId}/${rowId}?pn=${partName}&mfr=${manufacturer}&q=${quantity}`, null, { withCredentials: true } ) // .map(response => response.json()) .pipe(catchError((error: any) => this._handleError(error))) ); } deleteBom(bomId: string): Observable { return ( this.http .delete(`${this.dataUrl}/${bomId}`, { withCredentials: true }) // .map(response => response.json()) .pipe(catchError((error: any) => this._handleError(error))) ); } copyBom(bomId: string, newBomName: string): Observable { if(this.cloneBomCoreVersion){ const cloneRequest = { newName: newBomName }; const requestUrl = `${this.dataUrlCore}/api/boms/${this.bomId}`; return ( this.http .post(requestUrl, cloneRequest, { withCredentials: true }) // .map(response => response.json()) .pipe(catchError((error: any) => this._handleError(error))) ); } else{ return ( this.http .get(`${this.dataUrl}/${this.bomId}/copy?newName=${newBomName}`, { withCredentials: true }) // .map(response => response.json()) .pipe(catchError((error: any) => this._handleError(error))) ); } } createBom(bomName: string): Observable { //TODO: contract with BE to standardize origins const origin = this.config.getTarget() === 'myarrow' ? 'MYARROW' : 'ARROW'; const url = `${this.dataUrl}/new?n=${encodeURIComponent(bomName)}&re=${ this.config.region }&o=${origin}`; const requestOptions = { withCredentials: true }; return this.http .get(url, requestOptions) .pipe(catchError((error: any) => this._handleError(error))); } addPartsToBom(bomId: string, partNumbers: FormData): Observable { return this.http .put(`${this.dataUrl}/new/${bomId}`, partNumbers, { withCredentials: true }) .pipe(catchError((error: any) => this._handleError(error))); } requestQuote( bomId: string, email: string, selection: ItemSelection ): Observable { return this.http .post( `${this.dataUrl}/quote/${bomId}?email=${email}`, { selectedRows: selection }, { withCredentials: true } ) .pipe(catchError((error: any) => this._handleError(error))); } requestPartsQuote( bomId: string, rowIds: string[], currency: string, userInfo: any ): Observable { const options = { withCredentials: true }; return this.http .post( `${this.dataUrl}/quote/salesforce`, { bomId, rowIds, currency, userInfo }, options ) .pipe(catchError((error: any) => this._handleError(error))); } getUploadedFileById(fileId: string): Observable{ // const url = `${this.dataUrlNoVersion}/api/processed/${fileId}`; //Old .net framework version const url = `${this.dataUrlCore}/api/processedFile/${fileId}`; //.Net Core version const headers = new HttpHeaders({ Accept: 'application/json' }); const options = { headers: headers, withCredentials: true }; return this.http.get(url, options) .pipe(catchError((error: any) => this._handleError(error))); } mapBom( bomId: string, id: string, line: number, columns: any ): Observable { const headers = new HttpHeaders({ Accept: 'application/json' }); const options = { headers: headers, withCredentials: true }; return this.http .post( `${this.dataUrl}/process/${bomId}/${id}/columns?dataStarts=${line}`, columns, options ) .pipe(catchError((error: any) => this._handleError(error))); } /** * Sets the value of a comment on a specified row and column */ setColumnComment( rowId: number, mapId: number, value: string ): Observable { this._messageBus.to('ch:loader', { payload: true }); return this.http .post( `${this.dataUrl}/${this.bomId}/${rowId}?colInfo=[${JSON.stringify({ mapId, value })}]`, null, { withCredentials: true } ) .pipe( tap((response: any) => { if (response) { if (response.status.statusCode != 200) { this._handleError(response.status.statusMsg); } } this._messageBus.to('ch:loader', { payload: false }); }), catchError((error: any) => this._handleError(error)) ); } /** * Searches the user bom list for MPN search term */ globalSearch(searchTerm: string): Observable { return this.http .get(`${this.dataUrl}/user/search?searchTerm=${searchTerm}`, { withCredentials: true }) .pipe(catchError((error: any) => this._handleError(error))); } public getBomHistory(): Observable { return this.http .get(`${this.config.apiUrl}/${this.bomId}/changelog`, { withCredentials: true }) .pipe(catchError((error: any) => this._handleError(error))); } public getPartInfoHttp(skip = 0, limit = 200, unique = true) { const origin = this.config.getTarget(); const partnerId = origin === 'myarrow' ? this.MY_ARROW_SE_PARTNER_ID : this.ARROW_SE_PARTNER_ID; return this.http .get( `${this.config.apiUrlWithoutVersion}/bom/api/bom/${this.bomId}/part-info?skip=${skip}&limit=${limit}&unique=${unique}`, {withCredentials: true} ) .pipe( map((partInfo: any) => ({ //Maps to the SE widget expected format partnerId: partnerId, uploadedRowDTOList: partInfo.parts.map( (item: { partName: any; manufacturer: any }) => ({ uploadedMpn: item.partName, uploadedSupplier: item.manufacturer }) ) })), catchError((error: any) => this._handleError(error)) ); } public askEngineerMessage(message: string) { this.http .post( `${this.config.apiUrlWithoutVersion}/bom/api/boms/${this.bomId}/ask-engineer`, `"${message}"`, { withCredentials: true, headers: { 'Content-Type': 'application/json; charset=utf-8' } } ) .pipe(catchError(() => _throw(''))) .subscribe( () => { this._bns.notify( this._translate.instant('Ask-engineer-success'), 'primary' ); }, error => { this._bns .notify( this._translate.instant('Ask-engineer-error'), 'retry', 'warn', this._translate.instant('Ask-engineer-retry') ) .subscribe((runAction) => { if (runAction){ this.askEngineerMessage(message); } this.focusAskEngineerBtn.emit(); }); } ); } public updateQtyCol(paginationParamsModel: PaginationParamsModel, qtyAction: number, qtyId?: string, multiplier?: number) { this._socketService.emitAction(ApiSocketEmitAction.ALTER_METADATA, { bomId: this.bomId, bomQtyAction: qtyAction, quantityId: qtyId, multiplier: multiplier, pageNum: paginationParamsModel.currentPage, perPage: paginationParamsModel.itemsPerPage, filter: paginationParamsModel.filter, sorting: paginationParamsModel.sorting }); } public getBomQuantitiesInformation(): Observable { return this._socketService .observeAction(ApiSocketListenAction.BOM_QUANTITIES) .pipe(take(1)); } }