import { Component, EventEmitter, Input, OnChanges, Output, SimpleChanges } from '@angular/core'; import { Observable, of, Subscription, zip } from 'rxjs'; import { switchMap } from 'rxjs/operators'; import { BomItemService, BuyingOptionModel } from '@arrow/bom/core'; import { CurrencyConverterPipe } from '@arrow/bom/shared'; @Component({ selector: 'bom-part-price', templateUrl: './bom-part-price.component.html', styleUrls: ['./bom-part-price.component.scss'] }) export class BomPartPriceComponent implements OnChanges { @Input() item: any; @Input() rowId: string; @Input() currency: string; /* eslint-disable-next-line @angular-eslint/no-output-rename */ @Output('show-options') showOptions: EventEmitter = new EventEmitter(); hasMBO = false; isSinglePrice = false; hasUnallocated = false; lowestPrice: number; selectedPriceIndex: number = 0; inputTotal: number; displayedPriceRanges$: Observable; hasPriceInfo: boolean; // TODO: Remove dependency on BomITemService, delegate work to BomViewComopnent constructor( private _currencyConverter: CurrencyConverterPipe, private _bomItemService: BomItemService ) {} /** * On price changes updates singlePrice/ranges display, reload price ranges array and truncated array * update price ranges display. Needs to be on changes coz alternates get prices later and for now we don't * have any other price update notification (event) */ ngOnChanges(changes: SimpleChanges) { this.isSinglePrice = this._determineSinglePrice(this.item.priceRanges); this.hasMBO = this.item.buyingOptions ? this.item.buyingOptions.length > 1 : false; this.item.priceRanges = this._setPriceRanges( this.item.quantity, this.item.priceRanges ); this.displayedPriceRanges$ = zip( ...this.item.priceRanges.map(range => { return this._currencyConverter .transform(range.rangePrice, '1.2-5', false) .pipe( switchMap(price => of({ quantity: range.rangeLimit, price: price })) ); }) ); // Check for "unallocated qty" only on part rows (not alts) if (!this.item.isAlt) { const prevUnallocated = this.hasUnallocated; this.hasUnallocated = this._checkUnallocated( this.item.buyingOptions, this.item ); // Let any subscribers know that unallocated qty status has changed if (prevUnallocated !== this.hasUnallocated) this._bomItemService.updateUnallocatedBuyingOptions( this.rowId, this.hasUnallocated ); } this.lowestPrice = this._determineLowestPrice( this.item.buyingOptions, this.item ); } onShowOptions(): void { this.showOptions.emit(null); } /** * Filters the buying options to only selected prices, then reduces those selected prices to the lowest prices, * then returns lowest selected price value. * @param buyingOptions * @param item * @returns is lowest selected price */ private _determineLowestPrice(buyingOptions: any, item: any): number { if (!buyingOptions) return; const filteredPrices = buyingOptions.filter( (option: BuyingOptionModel) => option.selectedPrice && option.quantity > 0 ); let lowestPrice: number; if (filteredPrices.length) { lowestPrice = filteredPrices.reduce((acc: any, option: any) => acc.selectedPrice >= option.selectedPrice ? option : acc ).selectedPrice; } else if (item.priceRanges.length) { lowestPrice = item.priceRanges.reduce((acc: any, option: any) => acc.rangePrice >= option.rangePrice ? option : acc ).rangePrice; } return lowestPrice; } /** * Sets isSinglePrice to true/false to determine price ranges vs. single price display * Single price if only 1 price range 1+ (but displays price ranges if 1 range but min 10+ for example) * @returns is single price true/false */ private _determineSinglePrice(priceRanges: any): boolean { this.hasPriceInfo = priceRanges.length > 0; return ( priceRanges && priceRanges.length === 1 && priceRanges[0].rangeLimit === 1 ); } /** * Format price ranges array by adding extra selected price range property (isSelected) * Set the selectedPriceIndex property * @param itemQty - item's quantity (selected price row selected based on that) * @returns processed item.priceRanges array (or empty if no prices) */ private _setPriceRanges(itemQty: number, priceRanges: any): Array { if (!priceRanges) return []; return priceRanges.map((priceRange: any, index: number) => { const nextPriceRange = priceRanges[index + 1]; const isCurrentPriceRange = priceRange.rangeLimit <= itemQty && ((nextPriceRange && nextPriceRange.rangeLimit > itemQty) || !nextPriceRange); if (isCurrentPriceRange) this.selectedPriceIndex = index; return Object.assign({}, priceRange, { isSelected: isCurrentPriceRange }); }); } /** * check for un allocated parts * @param buyingOptions * @param item */ private _checkUnallocated(buyingOptions: any, item: any): boolean { if (!buyingOptions) return; this.inputTotal = buyingOptions.reduce( (acc: any, option: any) => option.selected && option.input ? acc + +option.input : acc, 0 ); return item.quantity > this.inputTotal; } }