/******************************************************************************* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. *******************************************************************************/ package org.apache.ofbiz.product.product; import java.math.BigDecimal; import java.math.MathContext; import java.sql.Timestamp; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.LinkedList; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Set; import org.apache.ofbiz.base.util.Debug; import org.apache.ofbiz.base.util.GeneralException; import org.apache.ofbiz.base.util.UtilDateTime; import org.apache.ofbiz.base.util.UtilMisc; import org.apache.ofbiz.base.util.UtilValidate; import org.apache.ofbiz.common.geo.GeoWorker; import org.apache.ofbiz.entity.Delegator; import org.apache.ofbiz.entity.GenericEntityException; import org.apache.ofbiz.entity.GenericValue; import org.apache.ofbiz.entity.condition.EntityCondition; import org.apache.ofbiz.entity.util.EntityQuery; import org.apache.ofbiz.entity.util.EntityTypeUtil; import org.apache.ofbiz.entity.util.EntityUtil; import org.apache.ofbiz.product.config.ProductConfigWrapper; import org.apache.ofbiz.product.config.ProductConfigWrapper.ConfigOption; import org.apache.ofbiz.service.GenericServiceException; import org.apache.ofbiz.service.LocalDispatcher; import org.apache.ofbiz.service.ModelService; /** * Product Worker class to reduce code in JSPs. */ public final class ProductWorker { public static final String module = ProductWorker.class.getName(); private static final String resource = "ProductUiLabels"; private static final MathContext generalRounding = new MathContext(10); private ProductWorker () {} public static boolean shippingApplies(GenericValue product) { String errMsg = ""; if (product != null) { String productTypeId = product.getString("productTypeId"); if ("SERVICE".equals(productTypeId) || "SERVICE_PRODUCT".equals(productTypeId) || (ProductWorker.isDigital(product) && !ProductWorker.isPhysical(product))) { // don't charge shipping on services or digital goods return false; } Boolean chargeShipping = product.getBoolean("chargeShipping"); if (chargeShipping == null) { return true; } else { return chargeShipping.booleanValue(); } } else { throw new IllegalArgumentException(errMsg); } } public static boolean isBillableToAddress(GenericValue product, GenericValue postalAddress) { return isAllowedToAddress(product, postalAddress, "PG_PURCH_"); } public static boolean isShippableToAddress(GenericValue product, GenericValue postalAddress) { return isAllowedToAddress(product, postalAddress, "PG_SHIP_"); } private static boolean isAllowedToAddress(GenericValue product, GenericValue postalAddress, String productGeoPrefix) { if (product != null && postalAddress != null) { Delegator delegator = product.getDelegator(); List<GenericValue> productGeos = null; try { productGeos = product.getRelated("ProductGeo", null, null, false); } catch (GenericEntityException e) { Debug.logError(e, module); } List<GenericValue> excludeGeos = EntityUtil.filterByAnd(productGeos, UtilMisc.toMap("productGeoEnumId", productGeoPrefix + "EXCLUDE")); List<GenericValue> includeGeos = EntityUtil.filterByAnd(productGeos, UtilMisc.toMap("productGeoEnumId", productGeoPrefix + "INCLUDE")); if (UtilValidate.isEmpty(excludeGeos) && UtilValidate.isEmpty(includeGeos)) { // If no GEOs are configured the default is TRUE return true; } // exclusion for (GenericValue productGeo: excludeGeos) { List<GenericValue> excludeGeoGroup = GeoWorker.expandGeoGroup(productGeo.getString("geoId"), delegator); if (GeoWorker.containsGeo(excludeGeoGroup, postalAddress.getString("countryGeoId"), delegator) || GeoWorker.containsGeo(excludeGeoGroup, postalAddress.getString("stateProvinceGeoId"), delegator) || GeoWorker.containsGeo(excludeGeoGroup, postalAddress.getString("postalCodeGeoId"), delegator)) { return false; } } if (UtilValidate.isEmpty(includeGeos)) { // If no GEOs are configured the default is TRUE return true; } // inclusion for (GenericValue productGeo: includeGeos) { List<GenericValue> includeGeoGroup = GeoWorker.expandGeoGroup(productGeo.getString("geoId"), delegator); if (GeoWorker.containsGeo(includeGeoGroup, postalAddress.getString("countryGeoId"), delegator) || GeoWorker.containsGeo(includeGeoGroup, postalAddress.getString("stateProvinceGeoId"), delegator) || GeoWorker.containsGeo(includeGeoGroup, postalAddress.getString("postalCodeGeoId"), delegator)) { return true; } } } else { throw new IllegalArgumentException("product and postalAddress cannot be null."); } return false; } public static boolean isSerialized (Delegator delegator, String productId) { try { GenericValue product = EntityQuery.use(delegator).from("Product").where("productId", productId).cache().queryOne(); if (product != null) { return "SERIALIZED_INV_ITEM".equals(product.getString("inventoryItemTypeId")); } } catch (GenericEntityException e) { Debug.logWarning(e.getMessage(), module); } return false; } public static boolean taxApplies(GenericValue product) { String errMsg = ""; if (product != null) { Boolean taxable = product.getBoolean("taxable"); if (taxable == null) { return true; } else { return taxable.booleanValue(); } } else { throw new IllegalArgumentException(errMsg); } } public static String getInstanceAggregatedId(Delegator delegator, String instanceProductId) throws GenericEntityException { GenericValue instanceProduct = EntityQuery.use(delegator).from("Product").where("productId", instanceProductId).queryOne(); if (instanceProduct != null && EntityTypeUtil.hasParentType(delegator, "ProductType", "productTypeId", instanceProduct.getString("productTypeId"), "parentTypeId", "AGGREGATED")) { GenericValue productAssoc = EntityUtil.getFirst(EntityUtil.filterByDate(instanceProduct.getRelated("AssocProductAssoc", UtilMisc.toMap("productAssocTypeId", "PRODUCT_CONF"), null, false))); if (productAssoc != null) { return productAssoc.getString("productId"); } } return null; } public static String getAggregatedInstanceId(Delegator delegator, String aggregatedProductId, String configId) throws GenericEntityException { List<GenericValue> productAssocs = getAggregatedAssocs(delegator, aggregatedProductId); if (UtilValidate.isNotEmpty(productAssocs) && UtilValidate.isNotEmpty(configId)) { for (GenericValue productAssoc: productAssocs) { GenericValue product = productAssoc.getRelatedOne("AssocProduct", false); if (configId.equals(product.getString("configId"))) { return productAssoc.getString("productIdTo"); } } } return null; } public static List<GenericValue> getAggregatedAssocs(Delegator delegator, String aggregatedProductId) throws GenericEntityException { GenericValue aggregatedProduct = EntityQuery.use(delegator).from("Product").where("productId", aggregatedProductId).queryOne(); if (aggregatedProduct != null && ("AGGREGATED".equals(aggregatedProduct.getString("productTypeId")) || "AGGREGATED_SERVICE".equals(aggregatedProduct.getString("productTypeId")))) { List<GenericValue> productAssocs = EntityUtil.filterByDate(aggregatedProduct.getRelated("MainProductAssoc", UtilMisc.toMap("productAssocTypeId", "PRODUCT_CONF"), null, false)); return productAssocs; } return null; } public static String getVariantVirtualId(GenericValue variantProduct) throws GenericEntityException { List<GenericValue> productAssocs = getVariantVirtualAssocs(variantProduct); if (productAssocs == null) { return null; } GenericValue productAssoc = EntityUtil.getFirst(productAssocs); if (productAssoc != null) { return productAssoc.getString("productId"); } else { return null; } } public static List<GenericValue> getVariantVirtualAssocs(GenericValue variantProduct) throws GenericEntityException { if (variantProduct != null && "Y".equals(variantProduct.getString("isVariant"))) { List<GenericValue> productAssocs = EntityUtil.filterByDate(variantProduct.getRelated("AssocProductAssoc", UtilMisc.toMap("productAssocTypeId", "PRODUCT_VARIANT"), null, true)); return productAssocs; } return null; } /** * invokes the getInventoryAvailableByFacility service, returns true if specified quantity is available, else false * this is only used in the related method that uses a ProductConfigWrapper, until that is refactored into a service as well... */ private static boolean isProductInventoryAvailableByFacility(String productId, String inventoryFacilityId, BigDecimal quantity, LocalDispatcher dispatcher) { BigDecimal availableToPromise = null; try { Map<String, Object> result = dispatcher.runSync("getInventoryAvailableByFacility", UtilMisc.toMap("productId", productId, "facilityId", inventoryFacilityId)); availableToPromise = (BigDecimal) result.get("availableToPromiseTotal"); if (availableToPromise == null) { Debug.logWarning("The getInventoryAvailableByFacility service returned a null availableToPromise, the error message was:\n" + result.get(ModelService.ERROR_MESSAGE), module); return false; } } catch (GenericServiceException e) { Debug.logWarning(e, "Error invoking getInventoryAvailableByFacility service in isCatalogInventoryAvailable", module); return false; } // check to see if we got enough back... if (availableToPromise.compareTo(quantity) >= 0) { if (Debug.infoOn()) Debug.logInfo("Inventory IS available in facility with id " + inventoryFacilityId + " for product id " + productId + "; desired quantity is " + quantity + ", available quantity is " + availableToPromise, module); return true; } else { if (Debug.infoOn()) Debug.logInfo("Returning false because there is insufficient inventory available in facility with id " + inventoryFacilityId + " for product id " + productId + "; desired quantity is " + quantity + ", available quantity is " + availableToPromise, module); return false; } } /** * Invokes the getInventoryAvailableByFacility service, returns true if specified quantity is available for all the selected parts, else false. * Also, set the available flag for all the product configuration's options. **/ public static boolean isProductInventoryAvailableByFacility(ProductConfigWrapper productConfig, String inventoryFacilityId, BigDecimal quantity, LocalDispatcher dispatcher) { boolean available = true; List<ConfigOption> options = productConfig.getSelectedOptions(); for (ConfigOption ci: options) { List<GenericValue> products = ci.getComponents(); for (GenericValue product: products) { String productId = product.getString("productId"); BigDecimal cmpQuantity = product.getBigDecimal("quantity"); BigDecimal neededQty = BigDecimal.ZERO; if (cmpQuantity != null) { neededQty = quantity.multiply(cmpQuantity); } if (!isProductInventoryAvailableByFacility(productId, inventoryFacilityId, neededQty, dispatcher)) { ci.setAvailable(false); } } if (!ci.isAvailable()) { available = false; } } return available; } /** * Gets ProductFeature GenericValue for all distinguishing features of a variant product. * Distinguishing means all features that are selectable on the corresponding virtual product and standard on the variant plus all DISTINGUISHING_FEAT assoc type features on the variant. */ public static Set<GenericValue> getVariantDistinguishingFeatures(GenericValue variantProduct) throws GenericEntityException { if (variantProduct == null) { return new HashSet<GenericValue>(); } if (!"Y".equals(variantProduct.getString("isVariant"))) { throw new IllegalArgumentException("Cannot get distinguishing features for a product that is not a variant (ie isVariant!=Y)."); } Delegator delegator = variantProduct.getDelegator(); String virtualProductId = getVariantVirtualId(variantProduct); // find all selectable features on the virtual product that are also standard features on the variant Set<GenericValue> distFeatures = new HashSet<GenericValue>(); List<GenericValue> variantDistinguishingFeatures = EntityQuery.use(delegator).from("ProductFeatureAndAppl").where("productId", variantProduct.get("productId"), "productFeatureApplTypeId", "DISTINGUISHING_FEAT").cache(true).queryList(); for (GenericValue variantDistinguishingFeature: EntityUtil.filterByDate(variantDistinguishingFeatures)) { GenericValue dummyFeature = delegator.makeValue("ProductFeature"); dummyFeature.setAllFields(variantDistinguishingFeature, true, null, null); distFeatures.add(dummyFeature); } List<GenericValue> virtualSelectableFeatures = EntityQuery.use(delegator).from("ProductFeatureAndAppl").where("productId", virtualProductId, "productFeatureApplTypeId", "SELECTABLE_FEATURE").cache(true).queryList(); Set<String> virtualSelectableFeatureIds = new HashSet<String>(); for (GenericValue virtualSelectableFeature: EntityUtil.filterByDate(virtualSelectableFeatures)) { virtualSelectableFeatureIds.add(virtualSelectableFeature.getString("productFeatureId")); } List<GenericValue> variantStandardFeatures = EntityQuery.use(delegator).from("ProductFeatureAndAppl").where("productId", variantProduct.get("productId"), "productFeatureApplTypeId", "STANDARD_FEATURE").cache(true).queryList(); for (GenericValue variantStandardFeature: EntityUtil.filterByDate(variantStandardFeatures)) { if (virtualSelectableFeatureIds.contains(variantStandardFeature.get("productFeatureId"))) { GenericValue dummyFeature = delegator.makeValue("ProductFeature"); dummyFeature.setAllFields(variantStandardFeature, true, null, null); distFeatures.add(dummyFeature); } } return distFeatures; } /** * Get the name to show to the customer for GWP alternative options. * If the alternative is a variant, find the distinguishing features and show those instead of the name; if it is not a variant then show the PRODUCT_NAME content. */ public static String getGwpAlternativeOptionName(LocalDispatcher dispatcher, Delegator delegator, String alternativeOptionProductId, Locale locale) { try { GenericValue alternativeOptionProduct = EntityQuery.use(delegator).from("Product").where("productId", alternativeOptionProductId).cache().queryOne(); if (alternativeOptionProduct != null) { if ("Y".equals(alternativeOptionProduct.getString("isVariant"))) { Set<GenericValue> distFeatures = getVariantDistinguishingFeatures(alternativeOptionProduct); if (UtilValidate.isNotEmpty(distFeatures)) { StringBuilder nameBuf = new StringBuilder(); for (GenericValue productFeature: distFeatures) { if (nameBuf.length() > 0) { nameBuf.append(", "); } GenericValue productFeatureType = productFeature.getRelatedOne("ProductFeatureType", true); if (productFeatureType != null) { nameBuf.append(productFeatureType.get("description", locale)); nameBuf.append(":"); } nameBuf.append(productFeature.get("description", locale)); } return nameBuf.toString(); } } // got to here, default to PRODUCT_NAME String alternativeProductName = ProductContentWrapper.getProductContentAsText(alternativeOptionProduct, "PRODUCT_NAME", locale, dispatcher, "html"); return alternativeProductName; } } catch (GenericEntityException e) { Debug.logError(e, module); } catch (Exception e) { Debug.logError(e, module); } // finally fall back to the ID in square braces return "[" + alternativeOptionProductId + "]"; } /** * gets productFeatures given a productFeatureApplTypeId * @param delegator * @param productId * @param productFeatureApplTypeId - if null, returns ALL productFeatures, regardless of applType * @return List */ public static List<GenericValue> getProductFeaturesByApplTypeId(Delegator delegator, String productId, String productFeatureApplTypeId) { if (productId == null) { return null; } try { return getProductFeaturesByApplTypeId(EntityQuery.use(delegator).from("Product").where("productId", productId).queryOne(), productFeatureApplTypeId); } catch (GenericEntityException e) { Debug.logError(e, module); } return null; } public static List<GenericValue> getProductFeaturesByApplTypeId(GenericValue product, String productFeatureApplTypeId) { if (product == null) { return null; } List<GenericValue> features = null; try { List<GenericValue> productAppls; List<EntityCondition> condList = UtilMisc.toList( EntityCondition.makeCondition("productId", product.getString("productId")), EntityUtil.getFilterByDateExpr() ); if (productFeatureApplTypeId != null) { condList.add(EntityCondition.makeCondition("productFeatureApplTypeId", productFeatureApplTypeId)); } EntityCondition cond = EntityCondition.makeCondition(condList); productAppls = product.getDelegator().findList("ProductFeatureAppl", cond, null, null, null, false); features = EntityUtil.getRelated("ProductFeature", null, productAppls, false); features = EntityUtil.orderBy(features, UtilMisc.toList("description")); } catch (GenericEntityException e) { Debug.logError(e, module); features = new LinkedList<GenericValue>(); } return features; } public static String getProductVirtualVariantMethod(Delegator delegator, String productId) { GenericValue product = null; try { product = EntityQuery.use(delegator).from("Product").where("productId", productId).cache().queryOne(); } catch (GenericEntityException e) { Debug.logError(e, module); } if (product != null) { return product.getString("virtualVariantMethodEnum"); } else { return null; } } /** * * @param product * @return list featureType and related featuresIds, description and feature price for this product ordered by type and sequence */ public static List<List<Map<String,String>>> getSelectableProductFeaturesByTypesAndSeq(GenericValue product) { if (product == null) { return null; } List <List<Map<String,String>>> featureTypeFeatures = new LinkedList<List<Map<String,String>>>(); try { Delegator delegator = product.getDelegator(); List<GenericValue> featuresSorted = EntityQuery.use(delegator) .from("ProductFeatureAndAppl") .where("productId", product.getString("productId"), "productFeatureApplTypeId", "SELECTABLE_FEATURE") .orderBy("productFeatureTypeId", "sequenceNum") .cache(true) .queryList(); String oldType = null; List<Map<String,String>> featureList = new LinkedList<Map<String,String>>(); for (GenericValue productFeatureAppl: featuresSorted) { if (oldType == null || !oldType.equals(productFeatureAppl.getString("productFeatureTypeId"))) { // use first entry for type and description if (oldType != null) { featureTypeFeatures.add(featureList); featureList = new LinkedList<Map<String,String>>(); } GenericValue productFeatureType = EntityQuery.use(delegator).from("ProductFeatureType").where("productFeatureTypeId", productFeatureAppl.getString("productFeatureTypeId")).queryOne(); featureList.add(UtilMisc.<String, String>toMap("productFeatureTypeId", productFeatureAppl.getString("productFeatureTypeId"), "description", productFeatureType.getString("description"))); oldType = productFeatureAppl.getString("productFeatureTypeId"); } // fill other entries with featureId, description and default price and currency Map<String,String> featureData = UtilMisc.toMap("productFeatureId", productFeatureAppl.getString("productFeatureId")); if (UtilValidate.isNotEmpty(productFeatureAppl.get("description"))) { featureData.put("description", productFeatureAppl.getString("description")); } else { featureData.put("description", productFeatureAppl.getString("productFeatureId")); } List<GenericValue> productFeaturePrices = EntityQuery.use(delegator).from("ProductFeaturePrice") .where("productFeatureId", productFeatureAppl.getString("productFeatureId"), "productPriceTypeId", "DEFAULT_PRICE") .filterByDate() .queryList(); if (UtilValidate.isNotEmpty(productFeaturePrices)) { GenericValue productFeaturePrice = productFeaturePrices.get(0); if (UtilValidate.isNotEmpty(productFeaturePrice.get("price"))) { featureData.put("price", productFeaturePrice.getBigDecimal("price").toString()); featureData.put("currencyUomId", productFeaturePrice.getString("currencyUomId")); } } featureList.add(featureData); } if (oldType != null) { // last map featureTypeFeatures.add(featureList); } } catch (GenericEntityException e) { Debug.logError(e, module); } return featureTypeFeatures; } /** * For a given variant product, returns the list of features that would qualify it for * selection from the virtual product * @param variantProduct - the variant from which to derive the selection features * @return a List of ProductFeature GenericValues */ public static List<GenericValue> getVariantSelectionFeatures(GenericValue variantProduct) { if (!"Y".equals(variantProduct.getString("isVariant"))) { return null; } GenericValue virtualProduct = ProductWorker.getParentProduct(variantProduct.getString("productId"), variantProduct.getDelegator()); if (virtualProduct == null || !"Y".equals(virtualProduct.getString("productId"))) { return null; } // The selectable features from the virtual product List<GenericValue> selectableFeatures = ProductWorker.getProductFeaturesByApplTypeId(virtualProduct, "SELECTABLE_FEATURE"); // A list of distinct ProductFeatureTypes derived from the selectable features List<String> selectableTypes = EntityUtil.getFieldListFromEntityList(selectableFeatures, "productFeatureTypeId", true); // The standard features from the variant product List<GenericValue> standardFeatures = ProductWorker.getProductFeaturesByApplTypeId(variantProduct, "STANDARD_FEATURE"); List<GenericValue> result = new LinkedList<GenericValue>(); for (GenericValue standardFeature : standardFeatures) { // For each standard variant feature check it is also a virtual selectable feature and // if a feature of the same type hasn't already been added to the list if (selectableTypes.contains(standardFeature.getString("productFeatureTypeId")) && selectableFeatures.contains(standardFeature)) { result.add(standardFeature); selectableTypes.remove(standardFeature.getString("productFeatureTypeId")); } } return result; } public static Map<String, List<GenericValue>> getOptionalProductFeatures(Delegator delegator, String productId) { Map<String, List<GenericValue>> featureMap = new LinkedHashMap<String, List<GenericValue>>(); List<GenericValue> productFeatureAppls = null; try { productFeatureAppls = EntityQuery.use(delegator).from("ProductFeatureAndAppl").where("productId", productId, "productFeatureApplTypeId", "OPTIONAL_FEATURE").orderBy("productFeatureTypeId", "sequenceNum").queryList(); } catch (GenericEntityException e) { Debug.logError(e, module); } if (productFeatureAppls != null) { for (GenericValue appl: productFeatureAppls) { String featureType = appl.getString("productFeatureTypeId"); List<GenericValue> features = featureMap.get(featureType); if (features == null) { features = new LinkedList<GenericValue>(); } features.add(appl); featureMap.put(featureType, features); } } return featureMap; } // product calc methods public static BigDecimal calcOrderAdjustments(List<GenericValue> orderHeaderAdjustments, BigDecimal subTotal, boolean includeOther, boolean includeTax, boolean includeShipping) { BigDecimal adjTotal = BigDecimal.ZERO; if (UtilValidate.isNotEmpty(orderHeaderAdjustments)) { List<GenericValue> filteredAdjs = filterOrderAdjustments(orderHeaderAdjustments, includeOther, includeTax, includeShipping, false, false); for (GenericValue orderAdjustment: filteredAdjs) { adjTotal = adjTotal.add(calcOrderAdjustment(orderAdjustment, subTotal)); } } return adjTotal; } public static BigDecimal calcOrderAdjustment(GenericValue orderAdjustment, BigDecimal orderSubTotal) { BigDecimal adjustment = BigDecimal.ZERO; if (orderAdjustment.get("amount") != null) { adjustment = adjustment.add(orderAdjustment.getBigDecimal("amount")); } else if (orderAdjustment.get("sourcePercentage") != null) { adjustment = adjustment.add(orderAdjustment.getBigDecimal("sourcePercentage").multiply(orderSubTotal)); } return adjustment; } public static List<GenericValue> filterOrderAdjustments(List<GenericValue> adjustments, boolean includeOther, boolean includeTax, boolean includeShipping, boolean forTax, boolean forShipping) { List<GenericValue> newOrderAdjustmentsList = new LinkedList<GenericValue>(); if (UtilValidate.isNotEmpty(adjustments)) { for (GenericValue orderAdjustment: adjustments) { boolean includeAdjustment = false; if ("SALES_TAX".equals(orderAdjustment.getString("orderAdjustmentTypeId"))) { if (includeTax) includeAdjustment = true; } else if ("SHIPPING_CHARGES".equals(orderAdjustment.getString("orderAdjustmentTypeId"))) { if (includeShipping) includeAdjustment = true; } else { if (includeOther) includeAdjustment = true; } // default to yes, include for shipping; so only exclude if includeInShipping is N, or false; if Y or null or anything else it will be included if (forTax && "N".equals(orderAdjustment.getString("includeInTax"))) { includeAdjustment = false; } // default to yes, include for shipping; so only exclude if includeInShipping is N, or false; if Y or null or anything else it will be included if (forShipping && "N".equals(orderAdjustment.getString("includeInShipping"))) { includeAdjustment = false; } if (includeAdjustment) { newOrderAdjustmentsList.add(orderAdjustment); } } } return newOrderAdjustmentsList; } public static BigDecimal getAverageProductRating(Delegator delegator, String productId) { return getAverageProductRating(delegator, productId, null); } public static BigDecimal getAverageProductRating(Delegator delegator, String productId, String productStoreId) { GenericValue product = null; try { product = EntityQuery.use(delegator).from("Product").where("productId", productId).cache().queryOne(); } catch (GenericEntityException e) { Debug.logError(e, module); } return ProductWorker.getAverageProductRating(product, productStoreId); } public static BigDecimal getAverageProductRating(GenericValue product, String productStoreId) { return getAverageProductRating(product, null, productStoreId); } public static BigDecimal getAverageProductRating(GenericValue product, List<GenericValue> reviews, String productStoreId) { if (product == null) { Debug.logWarning("Invalid product entity passed; unable to obtain valid product rating", module); return BigDecimal.ZERO; } BigDecimal productRating = BigDecimal.ZERO; BigDecimal productEntityRating = product.getBigDecimal("productRating"); String entityFieldType = product.getString("ratingTypeEnum"); // null check if (productEntityRating == null) { productEntityRating = BigDecimal.ZERO; } if (entityFieldType == null) { entityFieldType = ""; } if ("PRDR_FLAT".equals(entityFieldType)) { productRating = productEntityRating; } else { // get the product rating from the ProductReview entity; limit by product store if ID is passed Map<String, String> reviewByAnd = UtilMisc.toMap("statusId", "PRR_APPROVED"); if (productStoreId != null) { reviewByAnd.put("productStoreId", productStoreId); } // lookup the reviews if we didn't pass them in if (reviews == null) { try { reviews = product.getRelated("ProductReview", reviewByAnd, UtilMisc.toList("-postedDateTime"), true); } catch (GenericEntityException e) { Debug.logError(e, module); } } // tally the average BigDecimal ratingTally = BigDecimal.ZERO; BigDecimal numRatings = BigDecimal.ZERO; if (reviews != null) { for (GenericValue productReview: reviews) { BigDecimal rating = productReview.getBigDecimal("productRating"); if (rating != null) { ratingTally = ratingTally.add(rating); numRatings = numRatings.add(BigDecimal.ONE); } } } if (ratingTally.compareTo(BigDecimal.ZERO) > 0 && numRatings.compareTo(BigDecimal.ZERO) > 0) { productRating = ratingTally.divide(numRatings, generalRounding); } if ("PRDR_MIN".equals(entityFieldType)) { // check for min if (productEntityRating.compareTo(productRating) > 0) { productRating = productEntityRating; } } else if ("PRDR_MAX".equals(entityFieldType)) { // check for max if (productRating.compareTo(productEntityRating) > 0) { productRating = productEntityRating; } } } return productRating; } public static List<GenericValue> getCurrentProductCategories(Delegator delegator, String productId) { GenericValue product = null; try { product = EntityQuery.use(delegator).from("Product").where("productId", productId).queryOne(); } catch (GenericEntityException e) { Debug.logError(e, module); } return getCurrentProductCategories(product); } public static List<GenericValue> getCurrentProductCategories(GenericValue product) { if (product == null) { return null; } List<GenericValue> categories = new LinkedList<GenericValue>(); try { List<GenericValue> categoryMembers = product.getRelated("ProductCategoryMember", null, null, false); categoryMembers = EntityUtil.filterByDate(categoryMembers); categories = EntityUtil.getRelated("ProductCategory", null, categoryMembers, false); } catch (GenericEntityException e) { Debug.logError(e, module); } return categories; } //get parent product public static GenericValue getParentProduct(String productId, Delegator delegator) { GenericValue _parentProduct = null; if (productId == null) { Debug.logWarning("Bad product id", module); } try { List<GenericValue> virtualProductAssocs = EntityQuery.use(delegator).from("ProductAssoc") .where("productIdTo", productId, "productAssocTypeId", "PRODUCT_VARIANT") .orderBy("-fromDate") .cache(true) .filterByDate() .queryList(); if (UtilValidate.isEmpty(virtualProductAssocs)) { //okay, not a variant, try a UNIQUE_ITEM virtualProductAssocs = EntityQuery.use(delegator).from("ProductAssoc") .where("productIdTo", productId, "productAssocTypeId", "UNIQUE_ITEM") .orderBy("-fromDate") .cache(true) .filterByDate() .queryList(); } if (UtilValidate.isNotEmpty(virtualProductAssocs)) { //found one, set this first as the parent product GenericValue productAssoc = EntityUtil.getFirst(virtualProductAssocs); _parentProduct = productAssoc.getRelatedOne("MainProduct", true); } } catch (GenericEntityException e) { throw new RuntimeException("Entity Engine error getting Parent Product (" + e.getMessage() + ")"); } return _parentProduct; } public static boolean isDigital(GenericValue product) { boolean isDigital = false; if (product != null) { GenericValue productType = null; try { productType = product.getRelatedOne("ProductType", true); } catch (GenericEntityException e) { Debug.logWarning(e.getMessage(), module); } String isDigitalValue = (productType != null? productType.getString("isDigital"): null); isDigital = isDigitalValue != null && "Y".equalsIgnoreCase(isDigitalValue); } return isDigital; } public static boolean isPhysical(GenericValue product) { boolean isPhysical = false; if (product != null) { GenericValue productType = null; try { productType = product.getRelatedOne("ProductType", true); } catch (GenericEntityException e) { Debug.logWarning(e.getMessage(), module); } String isPhysicalValue = (productType != null? productType.getString("isPhysical"): null); isPhysical = isPhysicalValue != null && "Y".equalsIgnoreCase(isPhysicalValue); } return isPhysical; } public static boolean isVirtual(Delegator delegator, String productI) { try { GenericValue product = EntityQuery.use(delegator).from("Product").where("productId", productI).cache().queryOne(); if (product != null) { return "Y".equals(product.getString("isVirtual")); } } catch (GenericEntityException e) { Debug.logWarning(e.getMessage(), module); } return false; } public static boolean isAmountRequired(Delegator delegator, String productI) { try { GenericValue product = EntityQuery.use(delegator).from("Product").where("productId", productI).cache().queryOne(); if (product != null) { return "Y".equals(product.getString("requireAmount")); } } catch (GenericEntityException e) { Debug.logWarning(e.getMessage(), module); } return false; } public static String getProductTypeId(Delegator delegator, String productId) { try { GenericValue product = EntityQuery.use(delegator).from("Product").where("productId", productId).cache().queryOne(); if (product != null) { return product.getString("productTypeId"); } } catch (GenericEntityException e) { Debug.logWarning(e.getMessage(), module); } return null; } /* * Returns the product's unit weight converted to the desired Uom. If the weight is null, * then a check is made for an associated virtual product to retrieve the weight from. If the * weight is still null then null is returned. If a weight is found and a desiredUomId has * been supplied and the product specifies a weightUomId then an attempt will be made to * convert the value otherwise the weight is returned as is. */ public static BigDecimal getProductWeight(GenericValue product, String desiredUomId, Delegator delegator, LocalDispatcher dispatcher) { BigDecimal weight = product.getBigDecimal("productWeight"); String weightUomId = product.getString("weightUomId"); if (weight == null) { GenericValue parentProduct = getParentProduct(product.getString("productId"), delegator); if (parentProduct != null) { weight = parentProduct.getBigDecimal("productWeight"); weightUomId = parentProduct.getString("weightUomId"); } } if (weight == null) { return null; } else { // attempt a conversion if necessary if (desiredUomId != null && product.get("weightUomId") != null && !desiredUomId.equals(product.get("weightUomId"))) { Map<String, Object> result = new HashMap<String, Object>(); try { result = dispatcher.runSync("convertUom", UtilMisc.<String, Object>toMap("uomId", weightUomId, "uomIdTo", desiredUomId, "originalValue", weight)); } catch (GenericServiceException e) { Debug.logError(e, module); } if (result.get(ModelService.RESPONSE_MESSAGE).equals(ModelService.RESPOND_SUCCESS) && result.get("convertedValue") != null) { weight = (BigDecimal) result.get("convertedValue"); } else { Debug.logError("Unsupported conversion from [" + weightUomId + "] to [" + desiredUomId + "]",module); return null; } } return weight; } } /** * Generic service to find product by id. * By default return the product find by productId * but you can pass searchProductFirst at false if you want search in goodIdentification before * or pass searchAllId at true to find all product with this id (product.productId and goodIdentification.idValue) * @param delegator the delegator * @param idToFind the product id to find * @param goodIdentificationTypeId the good identification type id to use * @param searchProductFirst search first by product id * @param searchAllId search all product ids * @return return the list of products founds * @throws GenericEntityException */ public static List<GenericValue> findProductsById(Delegator delegator, String idToFind, String goodIdentificationTypeId, boolean searchProductFirst, boolean searchAllId) throws GenericEntityException { if (Debug.verboseOn()) Debug.logVerbose("Analyze goodIdentification: entered id = " + idToFind + ", goodIdentificationTypeId = " + goodIdentificationTypeId, module); GenericValue product = null; List<GenericValue> productsFound = null; // 1) look if the idToFind given is a real productId if (searchProductFirst) { product = EntityQuery.use(delegator).from("Product").where("productId", idToFind).cache().queryOne(); } if (searchAllId || (searchProductFirst && UtilValidate.isEmpty(product))) { // 2) Retrieve product in GoodIdentification Map<String, String> conditions = UtilMisc.toMap("idValue", idToFind); if (UtilValidate.isNotEmpty(goodIdentificationTypeId)) { conditions.put("goodIdentificationTypeId", goodIdentificationTypeId); } productsFound = EntityQuery.use(delegator).from("GoodIdentificationAndProduct").where(conditions).orderBy("productId").cache(true).queryList(); } if (! searchProductFirst) { product = EntityQuery.use(delegator).from("Product").where("productId", idToFind).cache().queryOne(); } if (product != null) { if (UtilValidate.isNotEmpty(productsFound)) productsFound.add(product); else productsFound = UtilMisc.toList(product); } if (Debug.verboseOn()) Debug.logVerbose("Analyze goodIdentification: found product.productId = " + product + ", and list : " + productsFound, module); return productsFound; } public static List<GenericValue> findProductsById(Delegator delegator, String idToFind, String goodIdentificationTypeId) throws GenericEntityException { return findProductsById(delegator, idToFind, goodIdentificationTypeId, true, false); } public static String findProductId(Delegator delegator, String idToFind, String goodIdentificationTypeId) throws GenericEntityException { GenericValue product = findProduct(delegator, idToFind, goodIdentificationTypeId); if (product != null) { return product.getString("productId"); } else { return null; } } public static String findProductId(Delegator delegator, String idToFind) throws GenericEntityException { return findProductId(delegator, idToFind, null); } public static GenericValue findProduct(Delegator delegator, String idToFind, String goodIdentificationTypeId) throws GenericEntityException { List<GenericValue> products = findProductsById(delegator, idToFind, goodIdentificationTypeId); GenericValue product = EntityUtil.getFirst(products); return product; } public static List<GenericValue> findProducts(Delegator delegator, String idToFind, String goodIdentificationTypeId) throws GenericEntityException { List<GenericValue> productsByIds = findProductsById(delegator, idToFind, goodIdentificationTypeId); List<GenericValue> products = null; if (UtilValidate.isNotEmpty(productsByIds)) { for (GenericValue product : productsByIds) { GenericValue productToAdd = product; //retreive product GV if the actual genericValue came from viewEntity if (! "Product".equals(product.getEntityName())) { productToAdd = EntityQuery.use(delegator).from("Product").where("productId", product.get("productId")).cache().queryOne(); } if (UtilValidate.isEmpty(products)) { products = UtilMisc.toList(productToAdd); } else { products.add(productToAdd); } } } return products; } public static List<GenericValue> findProducts(Delegator delegator, String idToFind) throws GenericEntityException { return findProducts(delegator, idToFind, null); } public static GenericValue findProduct(Delegator delegator, String idToFind) throws GenericEntityException { return findProduct(delegator, idToFind, null); } public static boolean isSellable(Delegator delegator, String productId, Timestamp atTime) throws GenericEntityException { return isSellable(findProduct(delegator, productId), atTime); } public static boolean isSellable(Delegator delegator, String productId) throws GenericEntityException { return isSellable(findProduct(delegator, productId)); } public static boolean isSellable(GenericValue product) { return isSellable(product, UtilDateTime.nowTimestamp()); } public static boolean isSellable(GenericValue product, Timestamp atTime) { if (product != null) { Timestamp introDate = product.getTimestamp("introductionDate"); Timestamp discDate = product.getTimestamp("salesDiscontinuationDate"); if (introDate == null || introDate.before(atTime)) { if (discDate == null || discDate.after(atTime)) { return true; } } } return false; } public static Set<String> getRefurbishedProductIdSet(String productId, Delegator delegator) throws GenericEntityException { Set<String> productIdSet = new HashSet<String>(); // find associated refurb items, we want serial number for main item or any refurb items too List<GenericValue> refubProductAssocs = EntityQuery.use(delegator).from("ProductAssoc").where("productId", productId, "productAssocTypeId", "PRODUCT_REFURB").filterByDate().queryList(); for (GenericValue refubProductAssoc: refubProductAssocs) { productIdSet.add(refubProductAssoc.getString("productIdTo")); } // see if this is a refurb productId to, and find product(s) it is a refurb of List<GenericValue> refubProductToAssocs = EntityQuery.use(delegator).from("ProductAssoc").where("productIdTo", productId, "productAssocTypeId", "PRODUCT_REFURB").filterByDate().queryList(); for (GenericValue refubProductToAssoc: refubProductToAssocs) { productIdSet.add(refubProductToAssoc.getString("productId")); } return productIdSet; } public static String getVariantFromFeatureTree(String productId, List<String> selectedFeatures, Delegator delegator) { // all method code moved here from ShoppingCartEvents.addToCart event String variantProductId = null; try { for (String paramValue: selectedFeatures) { // find incompatibilities.. List<GenericValue> incompatibilityVariants = EntityQuery.use(delegator).from("ProductFeatureIactn") .where("productId", productId, "productFeatureIactnTypeId","FEATURE_IACTN_INCOMP").cache(true).queryList(); for (GenericValue incompatibilityVariant: incompatibilityVariants) { String featur = incompatibilityVariant.getString("productFeatureId"); if (paramValue.equals(featur)) { String featurTo = incompatibilityVariant.getString("productFeatureIdTo"); for (String paramValueTo: selectedFeatures) { if (featurTo.equals(paramValueTo)) { Debug.logWarning("Incompatible features", module); return null; } } } } // find dependencies.. List<GenericValue> dependenciesVariants = EntityQuery.use(delegator).from("ProductFeatureIactn") .where("productId", productId, "productFeatureIactnTypeId","FEATURE_IACTN_DEPEND").cache(true).queryList(); for (GenericValue dpVariant: dependenciesVariants) { String featur = dpVariant.getString("productFeatureId"); if (paramValue.equals(featur)) { String featurTo = dpVariant.getString("productFeatureIdTo"); boolean found = false; for (String paramValueTo: selectedFeatures) { if (featurTo.equals(paramValueTo)) { found = true; break; } } if (!found) { Debug.logWarning("Dependency features", module); return null; } } } } // find variant List<GenericValue> productAssocs = EntityQuery.use(delegator).from("ProductAssoc").where("productId", productId, "productAssocTypeId","PRODUCT_VARIANT").filterByDate().queryList(); boolean productFound = false; nextProd: for (GenericValue productAssoc: productAssocs) { for (String featureId: selectedFeatures) { List<GenericValue> pAppls = EntityQuery.use(delegator).from("ProductFeatureAppl").where("productId", productAssoc.getString("productIdTo"), "productFeatureId", featureId, "productFeatureApplTypeId","STANDARD_FEATURE").cache(true).queryList(); if (UtilValidate.isEmpty(pAppls)) { continue nextProd; } } productFound = true; variantProductId = productAssoc.getString("productIdTo"); break; } /** * 1. variant not found so create new variant product and use the virtual product as basis, new one is a variant type and not a virtual type. * adjust the prices according the selected features */ if (!productFound) { // copy product to be variant GenericValue product = EntityQuery.use(delegator).from("Product").where("productId", productId).queryOne(); product.put("isVariant", "Y"); product.put("isVirtual", "N"); product.put("productId", delegator.getNextSeqId("Product")); product.remove("virtualVariantMethodEnum"); // not relevant for a non virtual product. product.create(); // add the selected/standard features as 'standard features' to the 'ProductFeatureAppl' table GenericValue productFeatureAppl = delegator.makeValue("ProductFeatureAppl", UtilMisc.toMap("productId", product.getString("productId"), "productFeatureApplTypeId", "STANDARD_FEATURE")); productFeatureAppl.put("fromDate", UtilDateTime.nowTimestamp()); for (String productFeatureId: selectedFeatures) { productFeatureAppl.put("productFeatureId", productFeatureId); productFeatureAppl.create(); } //add standard features too List<GenericValue> stdFeaturesAppls = EntityQuery.use(delegator).from("ProductFeatureAppl").where("productId", productId, "productFeatureApplTypeId", "STANDARD_FEATURE").filterByDate().queryList(); for (GenericValue stdFeaturesAppl: stdFeaturesAppls) { stdFeaturesAppl.put("productId", product.getString("productId")); stdFeaturesAppl.create(); } /* 3. use the price of the virtual product(Entity:ProductPrice) as a basis and adjust according the prices in the feature price table. * take the default price from the vitual product, go to the productfeature table and retrieve all the prices for the difFerent features * add these to the price of the virtual product, store the result as the default price on the variant you created. */ List<GenericValue> productPrices = EntityQuery.use(delegator).from("ProductPrice").where("productId", productId).filterByDate().queryList(); for (GenericValue productPrice: productPrices) { for (String selectedFeaturedId: selectedFeatures) { List<GenericValue> productFeaturePrices = EntityQuery.use(delegator).from("ProductFeaturePrice") .where("productFeatureId", selectedFeaturedId, "productPriceTypeId", productPrice.getString("productPriceTypeId")) .filterByDate().queryList(); if (UtilValidate.isNotEmpty(productFeaturePrices)) { GenericValue productFeaturePrice = productFeaturePrices.get(0); if (productFeaturePrice != null) { productPrice.put("price", productPrice.getBigDecimal("price").add(productFeaturePrice.getBigDecimal("price"))); } } } if (productPrice.get("price") == null) { productPrice.put("price", productPrice.getBigDecimal("price")); } productPrice.put("productId", product.getString("productId")); productPrice.create(); } // add the product association GenericValue productAssoc = delegator.makeValue("ProductAssoc", UtilMisc.toMap("productId", productId, "productIdTo", product.getString("productId"), "productAssocTypeId", "PRODUCT_VARIANT")); productAssoc.put("fromDate", UtilDateTime.nowTimestamp()); productAssoc.create(); Debug.logInfo("set the productId to: " + product.getString("productId"), module); // copy the supplier List<GenericValue> supplierProducts = EntityQuery.use(delegator).from("SupplierProduct").where("productId", productId).cache(true).queryList(); for (GenericValue supplierProduct: supplierProducts) { supplierProduct = (GenericValue) supplierProduct.clone(); supplierProduct.set("productId", product.getString("productId")); supplierProduct.create(); } // copy the content List<GenericValue> productContents = EntityQuery.use(delegator).from("ProductContent").where("productId", productId).cache(true).queryList(); for (GenericValue productContent: productContents) { productContent = (GenericValue) productContent.clone(); productContent.set("productId", product.getString("productId")); productContent.create(); } // finally use the new productId to be added to the cart variantProductId = product.getString("productId"); // set to the new product } } catch (GenericEntityException e) { Debug.logError(e, module); } return variantProductId; } public static boolean isAlternativePacking(Delegator delegator, String productId, String virtualVariantId) { boolean isAlternativePacking = false; if(productId != null || virtualVariantId != null){ List<GenericValue> alternativePackingProds = null; try { List<EntityCondition> condList = new LinkedList<EntityCondition>(); if (UtilValidate.isNotEmpty(productId)) { condList.add(EntityCondition.makeCondition("productIdTo", productId)); } if (UtilValidate.isNotEmpty(virtualVariantId)) { condList.add(EntityCondition.makeCondition("productId", virtualVariantId)); } condList.add(EntityCondition.makeCondition("productAssocTypeId", "ALTERNATIVE_PACKAGE")); alternativePackingProds = EntityQuery.use(delegator).from("ProductAssoc").where(condList).cache(true).queryList(); if(UtilValidate.isNotEmpty(alternativePackingProds)) isAlternativePacking = true; } catch (GenericEntityException e) { Debug.logWarning(e, "Could not found alternative product: " + e.getMessage(), module); } } return isAlternativePacking; } public static String getOriginalProductId(Delegator delegator, String productId){ boolean isAlternativePacking = isAlternativePacking(delegator, null, productId); if (isAlternativePacking) { List<GenericValue> productAssocs = null; try { productAssocs = EntityQuery.use(delegator).from("ProductAssoc").where("productId", productId , "productAssocTypeId", "ALTERNATIVE_PACKAGE").filterByDate().queryList(); } catch (GenericEntityException e) { Debug.logError(e, module); } if (productAssocs != null) { GenericValue productAssoc = EntityUtil.getFirst(productAssocs); return productAssoc.getString("productIdTo"); } else { return null; } }else{ return null; } } /** * worker to test if product can be order with a decimal quantity * @param delegator : access to DB * @param productId : ref. of product * * @param productStoreId : ref. of store * @return true if it can be ordered by decimal quantity * @throws GenericEntityException to catch */ public static Boolean isDecimalQuantityOrderAllowed(Delegator delegator, String productId, String productStoreId) throws GenericEntityException{ //sometime productStoreId may be null (ie PO), then return default value which is TRUE if(UtilValidate.isEmpty(productStoreId)){ return Boolean.TRUE; } String allowDecimalStore = EntityQuery.use(delegator).from("ProductStore").where("productStoreId", productStoreId).cache(true).queryOne().getString("orderDecimalQuantity"); String allowDecimalProduct = EntityQuery.use(delegator).from("Product").where("productId", productId).cache(true).queryOne().getString("orderDecimalQuantity"); if("N".equals(allowDecimalProduct) || (UtilValidate.isEmpty(allowDecimalProduct) && "N".equals(allowDecimalStore))){ return Boolean.FALSE; } return Boolean.TRUE; } public static boolean isAggregateService(Delegator delegator, String aggregatedProductId) { try { GenericValue aggregatedProduct = EntityQuery.use(delegator).from("Product").where("productId", aggregatedProductId).cache().queryOne(); if (UtilValidate.isNotEmpty(aggregatedProduct) && "AGGREGATED_SERVICE".equals(aggregatedProduct.getString("productTypeId"))) { return true; } } catch (GenericEntityException e) { Debug.logWarning(e.getMessage(), module); } return false; } // Method to filter-out out of stock products public static List<GenericValue> filterOutOfStockProducts (List<GenericValue> productsToFilter, LocalDispatcher dispatcher, Delegator delegator) throws GeneralException { List<GenericValue> productsInStock = new ArrayList<GenericValue>(); if (UtilValidate.isNotEmpty(productsToFilter)) { for (GenericValue genericRecord : productsToFilter) { String productId = genericRecord.getString("productId"); GenericValue product = null; product = EntityQuery.use(delegator).from("Product").where("productId", productId).cache(true).queryOne(); Boolean isMarketingPackage = EntityTypeUtil.hasParentType(delegator, "ProductType", "productTypeId", product.getString("productTypeId"), "parentTypeId", "MARKETING_PKG"); if ( UtilValidate.isNotEmpty(isMarketingPackage) && isMarketingPackage) { Map<String, Object> resultOutput = new HashMap<String, Object>(); resultOutput = dispatcher.runSync("getMktgPackagesAvailable", UtilMisc.toMap("productId" ,productId)); Debug.logWarning("Error getting available marketing package.", module); BigDecimal availableInventory = (BigDecimal) resultOutput.get("availableToPromiseTotal"); if(availableInventory.compareTo(BigDecimal.ZERO) > 0) { productsInStock.add(genericRecord); } } else { List<GenericValue> facilities = EntityQuery.use(delegator).from("ProductFacility").where("productId", productId).queryList(); BigDecimal availableInventory = BigDecimal.ZERO; if (UtilValidate.isNotEmpty(facilities)) { for (GenericValue facility : facilities) { BigDecimal lastInventoryCount = facility.getBigDecimal("lastInventoryCount"); if (lastInventoryCount != null) { availableInventory = lastInventoryCount.add(availableInventory); } } if (availableInventory.compareTo(BigDecimal.ZERO) > 0) { productsInStock.add(genericRecord); } } } } } return productsInStock; } }