All files / src/app/core/models/product product.helper.ts

100% Statements 49/49
92.85% Branches 39/42
100% Functions 24/24
100% Lines 43/43

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195180x                                         180x 180x 180x     180x                 4x 1x   7x                       4x 1x   7x                     19x 1x   18x 56x 9x 18x             103x       152x       76x             574x             999x             403x             125x                     36x 2x   34x                   16x 2x   57x 14x                     8x 1x   7x 19x 7x                             119x 2x     117x     117x 115x       2x             2x      
import { intersection } from 'lodash-es';
 
import { Attribute } from 'ish-core/models/attribute/attribute.model';
import { Image } from 'ish-core/models/image/image.model';
import { ProductView } from 'ish-core/models/product-view/product-view.model';
 
import {
  AllProductTypes,
  Product,
  ProductBundle,
  ProductRetailSet,
  VariationProduct,
  VariationProductMaster,
} from './product.model';
 
export interface SkuQuantityType {
  sku: string;
  quantity: number;
  unit?: string;
}
 
export enum ProductCompletenessLevel {
  Detail = 3,
  List = 2,
}
 
export class ProductHelper {
  /**
   * Get primary product image based on image type
   *
   * @param product   The Product for which to get the primary image
   * @param imageType The wanted ImageType
   * @returns         The primary product image of the given ImageType
   */
  static getPrimaryImage(product: ProductView, imageType: string): Image {
    if (!product?.images) {
      return;
    }
    return product.images.find(image => image.typeID === imageType && image.primaryImage);
  }
 
  /**
   * Get product image based on image type and image view
   *
   * @param product   The Product for which to get the image
   * @param imageType The wanted ImageType
   * @param imageView The wanted ImageView
   * @returns         The matching product image
   */
  static getImageByImageTypeAndImageView(product: ProductView, imageType: string, imageView: string): Image {
    if (!product?.images) {
      return;
    }
    return product.images.find(image => image.typeID === imageType && image.viewID === imageView);
  }
 
  /**
   * Get all product ImageView ids matching image type
   *
   * @param product   The Product for which to get the image types
   * @param imageType The wanted ImageType
   * @returns         Array of available ImageView ids
   */
  static getImageViewIDs(product: ProductView, imageType: string): string[] {
    if (!product?.images) {
      return [];
    }
    return product.images
      .filter(image => image.typeID === imageType)
      .sort((a, b) => (a.primaryImage ? -1 : b.primaryImage ? 1 : 0))
      .map(image => image.viewID);
  }
 
  /**
   * check if a given product has a sufficient completeness level
   */
  static isSufficientlyLoaded(product: Product | ProductView, completenessLevel: ProductCompletenessLevel): boolean {
    return !!product && product.completenessLevel >= completenessLevel;
  }
 
  static isFailedLoading(product: Product | ProductView): boolean {
    return !!product && !!product.failed;
  }
 
  static isReadyForDisplay(product: Product | ProductView, completenessLevel: ProductCompletenessLevel) {
    return ProductHelper.isSufficientlyLoaded(product, completenessLevel) || ProductHelper.isFailedLoading(product);
  }
 
  /**
   * Check if product is a retail set
   */
  static isRetailSet(product: Partial<AllProductTypes>): product is ProductRetailSet {
    return product && product.type === 'RetailSet';
  }
 
  /**
   * Check if product is a master product
   */
  static isMasterProduct(product: Partial<AllProductTypes>): product is VariationProductMaster & ProductView {
    return product && product.type === 'VariationProductMaster';
  }
 
  /**
   * Check if product is a variation product
   */
  static isVariationProduct(product: Partial<AllProductTypes>): product is VariationProduct & ProductView {
    return product && product.type === 'VariationProduct';
  }
 
  /**
   * Check if product is a product bundle
   */
  static isProductBundle(product: Partial<AllProductTypes>): product is ProductBundle {
    return product && product.type === 'Bundle';
  }
 
  /**
   * Get product attributes by attribute group id.
   *
   * @param product           The Product for which to get the attributes
   * @param attributeGroupId  The attribute group id of the attributes to get
   * @returns                 The product attributes of the attribute group (if any)
   */
  static getAttributesOfGroup(product: ProductView, attributeGroupId: string): Attribute[] {
    if (product?.attributeGroups?.[attributeGroupId]?.attributes?.length > 0) {
      return product.attributeGroups[attributeGroupId].attributes;
    }
    return;
  }
 
  /**
   * Determines the set of common attribute names for the compare products.
   *
   * @param products List of products to be compared
   * @returns        A set of the common attribute names
   */
  static getCommonAttributeNames(products: ProductView[]): string[] {
    if (!products?.length) {
      return [];
    }
    const result = products.filter(x => !!x).map(product => product.attributes?.map(x => x.name));
    return intersection(...result);
  }
 
  /**
   * Get a product with only specific attributes. All attributes that are common between the compare products are filtered out.
   *
   * @param product         The product that should be stripped of its common attributes
   * @param visibleProducts List of products to be compared
   * @returns               A Product with specific attributes only compared to the common attributes
   */
  static getProductWithoutCommonAttributes(product: ProductView, visibleProducts: ProductView[]): ProductView {
    if (!product?.sku || !visibleProducts?.length) {
      return;
    }
    const common = ProductHelper.getCommonAttributeNames(visibleProducts);
    const attributes = product.attributes?.filter(att => !common.includes(att.name));
    return { ...product, attributes };
  }
 
  /**
   * Updates current product information with new product information considering completeness levels and dynamic product information
   *
   * @param currentProduct  The already available product information
   * @param newProduct      The new product information to update the existing information with
   * @returns               The updated product information
   */
  static updateProductInformation(
    currentProduct: Partial<AllProductTypes>,
    newProduct: Partial<AllProductTypes>
  ): AllProductTypes {
    // if there is no new product information return the current product information
    if (!newProduct) {
      return currentProduct as AllProductTypes;
    }
    // set the current product information as base or construct an empty product stub
    let product = currentProduct || { completenessLevel: 0 };
    // update the complete product information if the newProduct information
    // has a higher or equal completeness level than the product information
    if (!product.completenessLevel || newProduct.completenessLevel >= product.completenessLevel) {
      return newProduct as AllProductTypes;
    }
    // if the newProduct information has a lower completeness level merge attributes and
    // always update dynamic product information with the new product information (e.g. availability)
    product = {
      ...newProduct,
      ...product,
      // list of product properties that should be updated
      available: newProduct.available ?? product.available,
      availableStock: newProduct.availableStock ?? product.availableStock,
    };
    return product as AllProductTypes;
  }
}