import { BuyingOptionModel } from './buying-option.model'; import { BomCustomColum, BomListColumn } from './common-models/list-models'; import { RowBomQuantityWithAutoRoundData } from './common-models/quantity.models'; import * as moment from 'moment'; export interface ListItemConstructor { // Contains the "data" either for regular or alt rows data: any; rowId?: string; isAlt?: boolean; // How many columns for this row (used to generate empty cells for alts) customColumnCount?: number; fixedColumnSlots?: Map; } export class ListItemModel { // Id // no: number; partId: number; rowId: string; isAlt: boolean; index: number = 0; hasPriceInfo: boolean = false; // When this item loads - should the tool auto scroll to it? // (used when adding parts [to scroll to the first added part]) scrollToOnInit: boolean = false; partName: string; originalName: string; manufacturer: string; originalManufacturer: string; image: string; imageName: string; rohs: boolean; description: string; originalDescription: string; detailLink: string; datasheet: string; datasheetId: number; category: string; partStatus = ''; targetPrice:number; // Match status matchStatus: number; fullPart: string; previousMatch: any = {}; isMatched: boolean; // Alternatives alternates: Array = []; previousPart: ListItemModel; altPrices: boolean = false; hiddenAlts: boolean = true; // Quantity multiplier: number = 1; singleBomQuantity: number; quantity: number; quantities: RowBomQuantityWithAutoRoundData[]; // Pricing and stock/shipping info isPriceRange: boolean; isPriceError: boolean; priceRanges: Array; buyingOptions: BuyingOptionModel[] = []; buyable: boolean; minQuantity: number = 1; increment: number = -1; packaging: string = ''; stock: number = 0; bufferStock: number; factoryStock: number; shipIn: number = -1; isObsolete: boolean; priceSourceLoading: boolean; hasMBO: boolean; sourcedByMessage: string; pipeline: any[] = []; leadTimes: Set = new Set(); // Using set to prevent duplicate lead times hasTariff: boolean = false; rankedSourceId: any; hasArrowReelSelected: boolean = false; // Stores the total cost of the row totalPrice: number = 0; // How many parts are considered in the row total cost totalCount: number = -1; // Cart info addItemToCartLoading: boolean = false; inCart: number = 0; inCartOther: number = 0; cartPartIds: Array = []; canAddToCart: boolean = false; // Extra properties partSeo: string; mfrSeo: string; sourcePartId: string = ''; sourceCode: string = ''; source: string; customColumns: Array = []; fixedColumnsMap: Map = new Map(); positionOnPage: number = -1; isLoading: boolean = true; tracked: boolean = false; isDeleted: boolean = false; isUploadedPart: boolean; //tiered Pricing hasTieredPriceGroup: boolean = false; // Quote properties isQuotePart: boolean = false; isQuoteDisabled: boolean = false; quotePartDisabledMsg: string = ''; public selectedCPN: string = ''; //Grading difference: string = ''; matchType: string = ''; grade: string = ''; private readonly PIPELINE_MAX_SIZE = 5; constructor(params: ListItemConstructor) { if (params.rowId) { this.rowId = params.rowId; } if (!params.data) return; if (!params.isAlt) { this.updateRowInfo( params.data.partInfo, params.data.input, params.data.rowQuantities ); if (params.data.alternates.length > 0) this.updateAlternates(params.data.alternates); this.updatePreviousPart(params.data.previousPartInfo); this.customColumns = [ ...this._getCustomColumnData( params.data.customerFields ), ...this._getFixedColumnData( params.data.customerFields )]; this.scrollToOnInit = !!params.data.scrollToOnInit; } else { this.isAlt = true; this.priceSourceLoading = true; this.customColumns = new Array(params.customColumnCount); this.fixedColumnsMap = params.fixedColumnSlots; this._fillAltRowInfo(params.data); } this.isUploadedPart = params.data.isUploadedPart; } public updatePreviousPart (PreviousData: any) { this.previousPart = PreviousData } public updateAlternates(altData: any[]): void { const emptyFixedColsMap: Map = new Map(); this.fixedColumnsMap.forEach((value, key, map) => { emptyFixedColsMap.set(key, value); }); this.alternates = altData.map( (alt: any) => new ListItemModel({ data: alt, isAlt: true, customColumnCount: this.customColumns.length, fixedColumnSlots: emptyFixedColsMap }) ); this.altPrices = false; } public getQuantityColumn(quantityId:string): RowBomQuantityWithAutoRoundData { if(!this.quantities) return null; return this.quantities.find(x => x.id == quantityId); } public getbuyingOptions( priceSources: Array, stockInfoGroups: Array, source: string, availableStock: number ): Array { if (availableStock !== null && availableStock != undefined) { this.stock = availableStock; } const selectedOptions = stockInfoGroups[source].buyingInfo.selectedOptions; const buyingOptions = priceSources.map( priceSource => new BuyingOptionModel(priceSource, selectedOptions) ); const alternates = stockInfoGroups[source].alternateSources; buyingOptions.forEach(buyingOption => { if (!buyingOption.input) { const alternate = alternates.find( _alternate => _alternate.sourcePartId === buyingOption.sourcePartId && _alternate.sourceCode === buyingOption.sourceCode ); if (alternate && alternate.quantityRequested) { buyingOption.input = alternate.quantityRequested; } } }); const allSources = buyingOptions.reduce((accumulator, bOption) => { return accumulator.concat(bOption.sourceCode); }, []); this.sourcedByMessage = this.getSourcedByMessage(allSources); return buyingOptions; } /** * For each cart entry try to match with this item partId, if also the sourcePartId matches, add to InCart qty & update * cartPartId value * @param cartEntries cart contents array */ public updateCartInfoForItem(cartEntries: Array) { let inCart = 0; cartEntries.forEach(entry => { if (this.partId === parseInt(entry.catalogPartNo, 10)) { if (!this.hasMBO) { // Current price bracket // If sourcePartId unknown (probably out stock), display in cart amount for current price bracket if ( !this.sourcePartId.length || this.sourcePartId === entry.catalogSourcePartId ) { inCart += entry.quantity; } } else { const existIndex = this.buyingOptions.findIndex( bo => bo.sourcePartId === entry.catalogSourcePartId ); if (existIndex >= 0) { inCart += entry.quantity; } } } }); this.inCart = inCart; // update buying options with cart info if (this.buyingOptions) { for (let ii = 0; this.buyingOptions.length > ii; ii++) { const bo = this.buyingOptions[ii]; bo.addCartInfo(cartEntries); const existIndex = this.cartPartIds.findIndex(e => { if (!bo.cartPartId) { return false; } // Making sure that values like 'null' or // 'undefined' don't end up incorrectly matching if (typeof e === 'string' && e.length > 0 && e === bo.cartPartId) { return true; } else { return false; } }); // If the entry wasn't already found, then after verifying this // won't be blank data, add it to the array if (existIndex === -1 && bo.hasValidCartPartId()) { this.cartPartIds.push(bo.cartPartId); } } } // updateCartInfoForItem() happens at the end of loading of item, recheck if item is buyable this.buyable = this._isBuyable(); } /** * Set isLoading state - item loading * @param state true/false */ public setItemModelLoading(state: boolean) { this.isLoading = state; } /** * Set priceSourceLoading state - is price loading (even if item has loaded) * @param state true/false */ public setPriceItemModelLoading(state: boolean) { this.priceSourceLoading = state; } /** * Updates the part row using just fields that may have been updated * * @param rowId * @param pInfo * @param pInput */ public updateRowInfo( pInfo: any, pInput: any, rowQuantities?: RowBomQuantityWithAutoRoundData[] ): void { this.partId = pInfo.partId; this.image = pInfo.smallImage; this.imageName = this._getImageName(this.image); this.rohs = pInfo.rohs; // rohs for alternatives? this.description = pInfo.description; // If no part/mfr seo - it's a non-arrow part. Don't build link if (pInfo.partSeo && pInfo.mfrSeo) this.detailLink = `${pInfo.partSeo}/${pInfo.mfrSeo}`; this.datasheet = pInfo.datasheet; this.category = pInfo.category; this.partStatus = pInfo.partStatus; this.datasheetId = pInfo.datasheetId; // Basic part data this.partName = pInput.used.part; this.originalName = pInput.original.part; this.manufacturer = pInput.used.mfr; this.targetPrice = pInput.used.targetPrice; this.originalManufacturer = pInput.original.mfr; if (rowQuantities) { this.quantities = rowQuantities; } this.originalDescription = pInput.original.desc; // Match status this.matchStatus = this._getMatchStatus({ partInfo: pInfo, input: pInput }); this.fullPart = pInfo.fullPart; this.previousMatch = this._getPreviousMatch(pInput, pInfo.fullPart); this.isMatched = !!pInfo.fullPart; this.isObsolete = pInfo.partStatus === 'Obsolete'; this.partSeo = pInfo.partSeo; this.mfrSeo = pInfo.mfrSeo; } /** * Updates the part row using just the pricing info * @param pPrice part rows "stockInfoGroups" property */ public updateRowPricing(stockInfoGroups: any, priceSources: any, rowQuantities: RowBomQuantityWithAutoRoundData[], isUploadedPart: boolean): void { if (rowQuantities) { this.quantities = rowQuantities; } this.isUploadedPart = isUploadedPart; if(!stockInfoGroups || !priceSources){ return; } this.hasPriceInfo = true; this.multiplier = stockInfoGroups.multiplied.multiplier; this.source = this.multiplier > 1 ? 'multiplied' : 'original'; this.totalCount = stockInfoGroups[this.source].buyingInfo.totalCount; this.totalPrice = stockInfoGroups[this.source].buyingInfo.totalPrice; this.buyingOptions = this.getbuyingOptions( priceSources, stockInfoGroups, this.source, stockInfoGroups.original?.buyingInfo?.availableStock ); this._setCanAddToCartStatus(); this.setHasArrowReelSelected(); this.hasTariff = this.buyingOptions.some(bo => bo.hasTariff); this.hasMBO = this.buyingOptions.length > 1; this.singleBomQuantity = stockInfoGroups.multiplied && stockInfoGroups.multiplied.buyingInfo ? stockInfoGroups.multiplied.buyingInfo.totalCount : this.singleBomQuantity; this.quantity = this.singleBomQuantity; this.priceRanges = this._getPriceRanges(this.buyingOptions); this.hasTieredPriceGroup = this._hasTieredPricing(); this.rankedSourceId = stockInfoGroups[this.source].buyingInfo.rankedSourceIds[0]; this.selectedCPN = stockInfoGroups.appliedCPN ? stockInfoGroups.appliedCPN.cpn : ''; if (this.hasMBO) { this.shipIn = this._getEarliestShipIn(); this._setLeadTimes(); this.minQuantity = this._getMinBuyingQuantity(); } else if (this.buyingOptions.length === 1) { this.minQuantity = this.buyingOptions[0]['minimum']; this.increment = this.buyingOptions[0]['increments']; this.packaging = this.buyingOptions[0]['packaging']; this.shipIn = this.buyingOptions[0]['shipIn']; this._setLeadTimes(); this.sourcePartId = this.buyingOptions[0]['sourcePartId']; this.sourceCode = this.buyingOptions[0]['sourceCode']; } this._refreshQuoteStatus(); this._setPipeline(); this.buyable = this._isBuyable(); } setHasArrowReelSelected() { if (this.buyingOptions && this.buyingOptions.length > 0) { this.hasArrowReelSelected = this.buyingOptions.some(bo => bo.isArrowReel); } } public refreshAfterBuyingOptionsChanges() { this.priceRanges = this._getPriceRanges(this.buyingOptions); this.hasMBO = this.buyingOptions.length > 1; this.buyable = this._isBuyable(); } /** * Fills in pricing information for when the list item is an "alternate" part * @param originalStockInfoGroup * @param priceSources */ public fillAltPriceInfo( originalStockInfoGroup: any, priceSources: any ): void { this.source = 'original'; this.totalCount = originalStockInfoGroup.original.buyingInfo.totalCount; this.totalPrice = originalStockInfoGroup.original.buyingInfo.totalPrice; this.buyingOptions = this.getbuyingOptions( priceSources, originalStockInfoGroup, this.source, originalStockInfoGroup.original?.buyingInfo?.availableStock ); this.hasTariff = this.buyingOptions.some(bo => bo.hasTariff); this.hasMBO = this.buyingOptions.length > 1; this.priceRanges = this._getPriceRanges(this.buyingOptions); if (this.hasMBO) { this.shipIn = this._getEarliestShipIn(); this._setLeadTimes(); this.minQuantity = this._getMinBuyingQuantity(); } else if (this.buyingOptions.length > 0) { this.minQuantity = this.buyingOptions[0]['minimum']; this.increment = this.buyingOptions[0]['increments']; this.packaging = this.buyingOptions[0]['packaging']; this.shipIn = this.buyingOptions[0]['shipIn']; this._setLeadTimes(); this.sourcePartId = this.buyingOptions[0]['sourcePartId']; this.sourceCode = this.buyingOptions[0]['sourceCode']; } this._setPipeline(); this.priceSourceLoading = false; } /** * Returns the current price of the row. Based on AC in WPT-14585 */ public getCurrentPrice(): number { let currentPrice = 0; let selectedPrice: number; // Single Price source if (this.buyingOptions.length === 1) { selectedPrice = this.buyingOptions[0].selectedPrice; if (selectedPrice !== undefined) { currentPrice = selectedPrice; } else if (this.buyingOptions[0].priceRanges) { currentPrice = this._getLowestPriceFromRange( this.buyingOptions[0].priceRanges ); } // Multiple price sources } else if (this.buyingOptions.length > 1) { // Filter out any NON selected items const selectedOptions = this.buyingOptions.filter( (bo: BuyingOptionModel) => { // Get all used buying options, but take out those that are locked // down at 0 (since those are still considered "selected") return bo.selected && bo.input > 0; } ); // None selected - get overall lowest price if (selectedOptions.length === 0) { const allPriceRanges = this.buyingOptions.map(bo => bo.priceRanges); currentPrice = this._getLowestPriceFromMultipleRanges(allPriceRanges); // Single selected - get current or lowest price for just this buying option } else if (selectedOptions.length === 1) { selectedPrice = selectedOptions[0].selectedPrice; if (selectedPrice !== undefined) { currentPrice = selectedPrice; } else if (selectedOptions[0].priceRanges) { currentPrice = this._getLowestPriceFromRange( selectedOptions[0].priceRanges ); } // Multiple selected } else if (selectedOptions.length > 1) { let lowestPrice = selectedOptions[0].selectedPrice; selectedOptions.forEach( bo => (lowestPrice = Math.min(lowestPrice, bo.selectedPrice)) ); currentPrice = lowestPrice; } } else { // no buying options // send 0 } return currentPrice; } // Clears out a row before fresh data starts flowing in // Used when changing a row fundamentally (selecting an alternate, or editing the part name) public resetAllProperties(): void { // Basic part data this.partName = ''; this.originalName = ''; this.manufacturer = ''; this.originalManufacturer = ''; this.image = ''; this.imageName = ''; this.rohs = false; this.description = ''; this.originalDescription = ''; this.detailLink = ''; this.datasheet = ''; // Match status this.matchStatus = undefined; this.fullPart = ''; this.previousMatch = {}; this.isMatched = false; // Alternatives if (this.alternates) this.alternates.length = 0; this.altPrices = false; this.hiddenAlts = true; // Quantity this.multiplier = 1; this.singleBomQuantity = undefined; this.quantity = undefined; // Pricing and stock/shipping info this.isPriceRange = false; this.isPriceError = false; if (this.priceRanges) this.priceRanges.length = 0; if (this.pipeline) this.pipeline.length = 0; this.leadTimes.clear(); if (this.buyingOptions) this.buyingOptions.length = 0; this.buyable = false; this.minQuantity = 1; this.increment = -1; this.packaging = ''; this.stock = 0; this.shipIn = -1; this.isObsolete = false; //this.priceSourceLoading = false; this.hasMBO = false; this.sourcedByMessage = ''; this.hasPriceInfo = false; this.isQuotePart = false; this.isQuoteDisabled = false; this.quotePartDisabledMsg = ''; this.totalPrice = 0; this.totalCount = -1; this.hasTariff = false; // Cart info //this.addItemToCartLoading = false; this.inCart = 0; this.inCartOther = 0; if (this.cartPartIds) this.cartPartIds.length = 0; this.canAddToCart = false; // Extra properties this.partSeo = ''; this.mfrSeo = ''; this.sourcePartId = ''; this.sourceCode = ''; this.source = ''; // Don't clear custom columns, as they shouldn't be changing // and new custom column info is NOT sent in the partRowInfo message //this.customColumns = []; this.tracked = false; this.isDeleted = false; } // TODO do we actually need this private _getMatchStatus(row: any) { if (!!row.partInfo.fullPart && !row.input.autoReplaced) { return 1; // matched, not autoreplaced } else if (row.input.used.part) { return -1; // rematched } else { return 0; // not found } } /** * Collects all prices across all buying options into a single array * @param buyingOptions Array of buying options * @returns Array containing prices */ private _getPriceRanges( buyingOptions: Array ): Array> { const priceRanges: Array = []; buyingOptions.forEach(option => priceRanges.push(...option.priceRanges)); return priceRanges; } /** * TODO needs to validate or check if we need to create tiered pricing model and map this. * * @returns true/false if theres any tiered pricing range */ private _hasTieredPricing(): boolean { const tieredRanges: Array = []; this.buyingOptions.forEach(range => { if (range.tieredPriceGroup) { tieredRanges.push(range.tieredPriceGroup); } }); return tieredRanges.length > 0; } /** * Set image name to the last item on the image url. * @param imageUrl Image Url * @returns String of string after the last / (forward slash) */ private _getImageName(imageUrl: string): string { let urlSplit: Array; if (!!imageUrl) urlSplit = imageUrl.split('/'); return !!imageUrl ? urlSplit[urlSplit.length - 1] : undefined; } /** * Depending on the match status and previous matches/user input, determines previous partName * to be displayed in grey next to the part name * @param value quantity input focus true/false */ private _getPreviousMatch(input: any, fullPart: string): any { const currentPartName = fullPart ? fullPart.toLowerCase() : ''; const previousPart = input.original.part; const previousPartName = previousPart ? previousPart.toLowerCase() : ''; const beforePrevious = input.previousOriginal.part; const beforePreviousName = beforePrevious ? beforePrevious.toLowerCase() : ''; // If it's a matched part if (!!fullPart) { // If exact match (user input part name == matched part name), no point displaying the same name, // so check for previously matched part & display if present and different to current part name if ( previousPartName === currentPartName && beforePrevious && beforePreviousName !== currentPartName ) { return { partName: beforePreviousName, manufacturer: input.previousOriginal.mfr, match: true }; // If autoreplaced and user input name different to matched name - display user input } else if (previousPartName !== currentPartName) { return { partName: previousPart, manufacturer: input.original.mfr, match: false }; } // TODO I'm not sure aboout this - don't know whether it should be previous original (makes sense when changing name) // or original (makes sense when selecting alternatives) // // Display previous valid match if only for updated unmatched rows (updated row !== initial match in mapping) // Initially mapped unmatched items display original in the part name) // Check if original !== previousOriginal, coz history is also kept for quantity changes } else { if (beforePrevious && beforePreviousName !== previousPartName) { return { partName: beforePreviousName, manufacturer: input.previousOriginal.mfr, match: true }; } } return {}; } private _getEarliestShipIn(): number { let lowest = this.buyingOptions[0].shipIn; this.buyingOptions.forEach( (option: BuyingOptionModel) => (lowest = Math.min(lowest, option.shipIn)) ); return lowest; } private _setLeadTimes(): void { this.buyingOptions.forEach((bo: BuyingOptionModel) => this.leadTimes.add(bo.leadTime) ); } // /** // * Return the minimum Order Quantity across all buying options // * @returns {number} MOQ // */ private _getMinBuyingQuantity(): number { let minimum = this.buyingOptions[0].minimum; this.buyingOptions.forEach( (option: BuyingOptionModel) => (minimum = Math.min(minimum, option.minimum)) ); return minimum; } private _fillAltRowInfo(data: any): void { this.datasheet = data.datasheet; this.datasheetId = data.datasheetId; this.description = data.description; this.manufacturer = data.mfrName; this.partId = data.partId; this.partName = data.partName; this.image = data.smallImage; this.isMatched = true; this.isObsolete = data.partStatus === 'Obsolete'; this.rohs = data.rohs; this.matchStatus = 1; this.isLoading = false; this.difference = data.difference; this.matchType = data.matchType; this.grade = data.grade; this.partStatus = data.partStatus; // If no part/mfr seo - it's a non-arrow part. Don't build link if (data.partSeo && data.mfrSeo) this.detailLink = `${data.partSeo}/${data.mfrSeo}`; } private _setPipeline() { this.pipeline = []; var timezoneOffset = new Date().getTimezoneOffset(); if (this.buyingOptions && this.buyingOptions.length > 0) { // For each buying option, scan through its pipeline (this.buyingOptions as BuyingOptionModel[]).forEach(bo => { // If this BO has pipeline details, merge them into the 'this.pipeline' array if (bo.pipeLineDetails && bo.pipeLineDetails.length > 0) { // For each 'pipe line detail' item check if the date already exists, if // it does, just add the qty to the existing date bo.pipeLineDetails.forEach(pld => { const existingPld = this.pipeline.find( item => item.pipeLineDate.getTime() === moment.utc(pld.pipeLineDate).add(timezoneOffset, 'minutes').toDate().getTime() ); if (existingPld) { existingPld.pipeLineQty += pld.pipeLineQty; } else { // Ensure ALL dates in this.pipeline are of the Date type (BE sends us a string) // also converting the date to avoid date issues due to the timezone offset pld.pipeLineDate = moment.utc(pld.pipeLineDate).add(timezoneOffset, 'minutes').toDate() pld.pipeLineMonth = pld.pipeLineDate.getMonth(); this.pipeline.push(pld); } }); // Order array by pipeLineDate this.pipeline.sort((a, b) => { return a.pipeLineDate - b.pipeLineDate; }); // Limit displayed pipeline (to temporarily deal with row height issue) // WPT-13320 will help resolve the issue, and this code should then be removed (S. Berezhnoy 10/16/2017) if (this.pipeline.length > this.PIPELINE_MAX_SIZE) { this.pipeline = this.pipeline.slice(0, this.PIPELINE_MAX_SIZE - 1); } } }); } } private _getLowestPriceFromRange(ranges: Array): number { let lowestPrice = 0; if (ranges.length) { lowestPrice = ranges[0].rangePrice; ranges.forEach(range => { lowestPrice = Math.min(lowestPrice, range.rangePrice); }); } return lowestPrice; } private _getLowestPriceFromMultipleRanges( multRanges: Array> ): number { let lowestPrice = 0; if (multRanges.length && multRanges[0].length) { lowestPrice = multRanges[0][0].rangePrice; multRanges.forEach(range => { lowestPrice = Math.min( lowestPrice, this._getLowestPriceFromRange(range) ); }); } return lowestPrice; } /** * sets the 'canAddToCart' status for the whole row, based on buying options * If ANY of the buying options can be added to cart, * then this "whole row" can be added to cart */ private _setCanAddToCartStatus(): void { this.canAddToCart = !!this.buyingOptions.find(bo => bo.canAddToCart); } private _refreshQuoteStatus(): void { if (!this.isAlt && !!this.matchStatus && !this.canAddToCart) { this.isQuotePart = true; const msg = { adjustQty: 'Please adjust the quantity to meet Minimum/Increment Requirements', cantQuote: 'Sorry we are unable to quote this part' }; // AC in WPT-15031 if (this.partId === 0) { // Scenario 1 this.isQuoteDisabled = true; this.quotePartDisabledMsg = msg.cantQuote; } else if ( this.buyingOptions.length === 1 && this.buyingOptions[0].region !== 'NAC' ) { // Since since buying option parts don't have `selected` set to true, // we don't even look at that just checking the region this.isQuoteDisabled = true; this.quotePartDisabledMsg = msg.cantQuote; } else if (this.buyingOptions.length > 1) { // Scenario 7 and 8 const nacSelections = this.buyingOptions.filter( o => o.region === 'NAC' && o.selected ); if (nacSelections.length === 1) { this.isQuoteDisabled = false; } else { this.isQuoteDisabled = true; this.quotePartDisabledMsg = msg.cantQuote; } } else if ( (!this.hasMBO && this.quantity < this.minQuantity) || this.quantity % this.increment > 0 ) { // Scenario 2, 3 and 4 (automatically passes since ) this.isQuoteDisabled = true; this.quotePartDisabledMsg = msg.adjustQty; } else { this.isQuoteDisabled = false; } } else { this.isQuotePart = false; } } private _getCustomColumnData(data: any[]): any[] { if (!data) return []; return data.filter(col => col.mapId < 0); } private _getFixedColumnData(data: any[]): BomCustomColum[] { let fixedColumnData: BomCustomColum[] = []; if (!data) return fixedColumnData; const fixedColumnIds = BomListColumn.getFixedColumnIds(); data.filter(item => { if(fixedColumnIds.indexOf(item.mapId) !== -1){ fixedColumnData.push({mapId: item.mapId, value: item.value, key: item.key }) } }); return fixedColumnData; } private _isBuyable(): boolean { return !!this.buyingOptions.find(x => x.canAddToCart); } private getSourcedByMessage(allSources: string[]): string { let returnedSourceMessage: string = null; const aePetscheKey = 'AEP'; // Are any sources A.E. Petsche? const doesAePetscheExist = allSources.indexOf(aePetscheKey) > -1; if (doesAePetscheExist) { // Are ALL sources A.E. Petsche? const areAllAePetsche = allSources.every((sourceCode: any) => { return sourceCode === aePetscheKey; }); if (areAllAePetsche) { returnedSourceMessage = aePetscheKey; } else { returnedSourceMessage = 'multiple'; } } return returnedSourceMessage; } }