| /******************************************************************************* |
| * 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.ofbiz.product.price; |
| |
| import java.math.BigDecimal; |
| import java.sql.Timestamp; |
| import java.util.Collection; |
| import java.util.List; |
| import java.util.Locale; |
| import java.util.Map; |
| import java.util.TreeSet; |
| |
| import javolution.util.FastList; |
| import javolution.util.FastMap; |
| |
| import org.ofbiz.base.util.Debug; |
| import org.ofbiz.base.util.UtilDateTime; |
| import org.ofbiz.base.util.UtilGenerics; |
| import org.ofbiz.base.util.UtilMisc; |
| import org.ofbiz.base.util.UtilNumber; |
| import org.ofbiz.base.util.UtilProperties; |
| import org.ofbiz.base.util.UtilValidate; |
| import org.ofbiz.entity.Delegator; |
| import org.ofbiz.entity.GenericEntityException; |
| import org.ofbiz.entity.GenericValue; |
| import org.ofbiz.entity.condition.EntityCondition; |
| import org.ofbiz.entity.condition.EntityOperator; |
| import org.ofbiz.entity.util.EntityUtil; |
| import org.ofbiz.entity.util.EntityUtilProperties; |
| import org.ofbiz.product.product.ProductWorker; |
| import org.ofbiz.service.DispatchContext; |
| import org.ofbiz.service.GenericServiceException; |
| import org.ofbiz.service.LocalDispatcher; |
| import org.ofbiz.service.ServiceUtil; |
| |
| /** |
| * PriceServices - Workers and Services class for product price related functionality |
| */ |
| public class PriceServices { |
| |
| public static final String module = PriceServices.class.getName(); |
| public static final String resource = "ProductUiLabels"; |
| public static final BigDecimal ONE_BASE = BigDecimal.ONE; |
| public static final BigDecimal PERCENT_SCALE = new BigDecimal("100.000"); |
| |
| public static final int taxCalcScale = UtilNumber.getBigDecimalScale("salestax.calc.decimals"); |
| public static final int taxFinalScale = UtilNumber.getBigDecimalScale("salestax.final.decimals"); |
| public static final int taxRounding = UtilNumber.getBigDecimalRoundingMode("salestax.rounding"); |
| |
| /** |
| * <p>Calculates the price of a product from pricing rules given the following input, and of course access to the database:</p> |
| * <ul> |
| * <li>productId |
| * <li>partyId |
| * <li>prodCatalogId |
| * <li>webSiteId |
| * <li>productStoreId |
| * <li>productStoreGroupId |
| * <li>agreementId |
| * <li>quantity |
| * <li>currencyUomId |
| * <li>checkIncludeVat |
| * </ul> |
| */ |
| public static Map<String, Object> calculateProductPrice(DispatchContext dctx, Map<String, ? extends Object> context) { |
| // UtilTimer utilTimer = new UtilTimer(); |
| // utilTimer.timerString("Starting price calc", module); |
| // utilTimer.setLog(false); |
| |
| Delegator delegator = dctx.getDelegator(); |
| LocalDispatcher dispatcher = dctx.getDispatcher(); |
| Map<String, Object> result = FastMap.newInstance(); |
| Timestamp nowTimestamp = UtilDateTime.nowTimestamp(); |
| |
| GenericValue product = (GenericValue) context.get("product"); |
| String productId = product.getString("productId"); |
| String prodCatalogId = (String) context.get("prodCatalogId"); |
| String webSiteId = (String) context.get("webSiteId"); |
| String checkIncludeVat = (String) context.get("checkIncludeVat"); |
| String surveyResponseId = (String) context.get("surveyResponseId"); |
| Map<String, Object> customAttributes = UtilGenerics.checkMap(context.get("customAttributes")); |
| |
| String findAllQuantityPricesStr = (String) context.get("findAllQuantityPrices"); |
| boolean findAllQuantityPrices = "Y".equals(findAllQuantityPricesStr); |
| boolean optimizeForLargeRuleSet = "Y".equals(context.get("optimizeForLargeRuleSet")); |
| |
| String agreementId = (String) context.get("agreementId"); |
| |
| String productStoreId = (String) context.get("productStoreId"); |
| String productStoreGroupId = (String) context.get("productStoreGroupId"); |
| Locale locale = (Locale) context.get("locale"); |
| |
| GenericValue productStore = null; |
| try { |
| // we have a productStoreId, if the corresponding ProductStore.primaryStoreGroupId is not empty, use that |
| productStore = delegator.findOne("ProductStore", UtilMisc.toMap("productStoreId", productStoreId), true); |
| } catch (GenericEntityException e) { |
| Debug.logError(e, "Error getting product store info from the database while calculating price" + e.toString(), module); |
| return ServiceUtil.returnError(UtilProperties.getMessage(resource, |
| "ProductPriceCannotRetrieveProductStore", UtilMisc.toMap("errorString", e.toString()) , locale)); |
| } |
| if (UtilValidate.isEmpty(productStoreGroupId)) { |
| if (productStore != null) { |
| try { |
| if (UtilValidate.isNotEmpty(productStore.getString("primaryStoreGroupId"))) { |
| productStoreGroupId = productStore.getString("primaryStoreGroupId"); |
| } else { |
| // no ProductStore.primaryStoreGroupId, try ProductStoreGroupMember |
| List<GenericValue> productStoreGroupMemberList = delegator.findByAnd("ProductStoreGroupMember", UtilMisc.toMap("productStoreId", productStoreId), UtilMisc.toList("sequenceNum", "-fromDate"), true); |
| productStoreGroupMemberList = EntityUtil.filterByDate(productStoreGroupMemberList, true); |
| if (productStoreGroupMemberList.size() > 0) { |
| GenericValue productStoreGroupMember = EntityUtil.getFirst(productStoreGroupMemberList); |
| productStoreGroupId = productStoreGroupMember.getString("productStoreGroupId"); |
| } |
| } |
| } catch (GenericEntityException e) { |
| Debug.logError(e, "Error getting product store info from the database while calculating price" + e.toString(), module); |
| return ServiceUtil.returnError(UtilProperties.getMessage(resource, |
| "ProductPriceCannotRetrieveProductStore", UtilMisc.toMap("errorString", e.toString()) , locale)); |
| } |
| } |
| |
| // still empty, default to _NA_ |
| if (UtilValidate.isEmpty(productStoreGroupId)) { |
| productStoreGroupId = "_NA_"; |
| } |
| } |
| |
| // if currencyUomId is null get from properties file, if nothing there assume USD (USD: American Dollar) for now |
| String currencyDefaultUomId = (String) context.get("currencyUomId"); |
| String currencyUomIdTo = (String) context.get("currencyUomIdTo"); |
| if (UtilValidate.isEmpty(currencyDefaultUomId)) { |
| currencyDefaultUomId = EntityUtilProperties.getPropertyValue("general", "currency.uom.id.default", "USD", delegator); |
| } |
| |
| // productPricePurposeId is null assume "PURCHASE", which is equivalent to what prices were before the purpose concept |
| String productPricePurposeId = (String) context.get("productPricePurposeId"); |
| if (UtilValidate.isEmpty(productPricePurposeId)) { |
| productPricePurposeId = "PURCHASE"; |
| } |
| |
| // termUomId, for things like recurring prices specifies the term (time/frequency measure for example) of the recurrence |
| // if this is empty it will simply not be used to constrain the selection |
| String termUomId = (String) context.get("termUomId"); |
| |
| // if this product is variant, find the virtual product and apply checks to it as well |
| String virtualProductId = null; |
| if ("Y".equals(product.getString("isVariant"))) { |
| try { |
| virtualProductId = ProductWorker.getVariantVirtualId(product); |
| } catch (GenericEntityException e) { |
| Debug.logError(e, "Error getting virtual product id from the database while calculating price" + e.toString(), module); |
| return ServiceUtil.returnError(UtilProperties.getMessage(resource, |
| "ProductPriceCannotRetrieveVirtualProductId", UtilMisc.toMap("errorString", e.toString()) , locale)); |
| } |
| } |
| |
| // get prices for virtual product if one is found; get all ProductPrice entities for this productId and currencyUomId |
| List<GenericValue> virtualProductPrices = null; |
| if (virtualProductId != null) { |
| try { |
| virtualProductPrices = delegator.findByAnd("ProductPrice", UtilMisc.toMap("productId", virtualProductId, "currencyUomId", currencyDefaultUomId, "productStoreGroupId", productStoreGroupId), UtilMisc.toList("-fromDate"), true); |
| } catch (GenericEntityException e) { |
| Debug.logError(e, "An error occurred while getting the product prices", module); |
| } |
| virtualProductPrices = EntityUtil.filterByDate(virtualProductPrices, true); |
| } |
| |
| // NOTE: partyId CAN be null |
| String partyId = (String) context.get("partyId"); |
| if (UtilValidate.isEmpty(partyId) && context.get("userLogin") != null) { |
| GenericValue userLogin = (GenericValue) context.get("userLogin"); |
| partyId = userLogin.getString("partyId"); |
| } |
| |
| // check for auto-userlogin for price rules |
| if (UtilValidate.isEmpty(partyId) && context.get("autoUserLogin") != null) { |
| GenericValue userLogin = (GenericValue) context.get("autoUserLogin"); |
| partyId = userLogin.getString("partyId"); |
| } |
| |
| BigDecimal quantity = (BigDecimal) context.get("quantity"); |
| if (quantity == null) quantity = BigDecimal.ONE; |
| |
| BigDecimal amount = (BigDecimal) context.get("amount"); |
| |
| List<EntityCondition> productPriceEcList = FastList.newInstance(); |
| productPriceEcList.add(EntityCondition.makeCondition("productId", EntityOperator.EQUALS, productId)); |
| // this funny statement is for backward compatibility purposes; the productPricePurposeId is a new pk field on the ProductPrice entity and in order databases may not be populated, until the pk is updated and such; this will ease the transition somewhat |
| if ("PURCHASE".equals(productPricePurposeId)) { |
| productPriceEcList.add(EntityCondition.makeCondition( |
| EntityCondition.makeCondition("productPricePurposeId", EntityOperator.EQUALS, productPricePurposeId), |
| EntityOperator.OR, |
| EntityCondition.makeCondition("productPricePurposeId", EntityOperator.EQUALS, null))); |
| } else { |
| productPriceEcList.add(EntityCondition.makeCondition("productPricePurposeId", EntityOperator.EQUALS, productPricePurposeId)); |
| } |
| productPriceEcList.add(EntityCondition.makeCondition("currencyUomId", EntityOperator.EQUALS, currencyDefaultUomId)); |
| productPriceEcList.add(EntityCondition.makeCondition("productStoreGroupId", EntityOperator.EQUALS, productStoreGroupId)); |
| if (UtilValidate.isNotEmpty(termUomId)) { |
| productPriceEcList.add(EntityCondition.makeCondition("termUomId", EntityOperator.EQUALS, termUomId)); |
| } |
| EntityCondition productPriceEc = EntityCondition.makeCondition(productPriceEcList, EntityOperator.AND); |
| |
| // for prices, get all ProductPrice entities for this productId and currencyUomId |
| List<GenericValue> productPrices = null; |
| try { |
| productPrices = delegator.findList("ProductPrice", productPriceEc, null, UtilMisc.toList("-fromDate"), null, true); |
| } catch (GenericEntityException e) { |
| Debug.logError(e, "An error occurred while getting the product prices", module); |
| } |
| productPrices = EntityUtil.filterByDate(productPrices, true); |
| |
| // ===== get the prices we need: list, default, average cost, promo, min, max ===== |
| // if any of these prices is missing and this product is a variant, default to the corresponding price on the virtual product |
| GenericValue listPriceValue = getPriceValueForType("LIST_PRICE", productPrices, virtualProductPrices); |
| GenericValue defaultPriceValue = getPriceValueForType("DEFAULT_PRICE", productPrices, virtualProductPrices); |
| |
| // If there is an agreement between the company and the client, and there is |
| // a price for the product in it, it will override the default price of the |
| // ProductPrice entity. |
| if (UtilValidate.isNotEmpty(agreementId)) { |
| try { |
| List<GenericValue> agreementPrices = delegator.findByAnd("AgreementItemAndProductAppl", UtilMisc.toMap("agreementId", agreementId, "productId", productId, "currencyUomId", currencyDefaultUomId), null, false); |
| GenericValue agreementPriceValue = EntityUtil.getFirst(agreementPrices); |
| if (agreementPriceValue != null && agreementPriceValue.get("price") != null) { |
| defaultPriceValue = agreementPriceValue; |
| } |
| } catch (GenericEntityException e) { |
| Debug.logError(e, "Error getting agreement info from the database while calculating price" + e.toString(), module); |
| return ServiceUtil.returnError(UtilProperties.getMessage(resource, |
| "ProductPriceCannotRetrieveAgreementInfo", UtilMisc.toMap("errorString", e.toString()) , locale)); |
| } |
| } |
| |
| GenericValue competitivePriceValue = getPriceValueForType("COMPETITIVE_PRICE", productPrices, virtualProductPrices); |
| GenericValue averageCostValue = getPriceValueForType("AVERAGE_COST", productPrices, virtualProductPrices); |
| GenericValue promoPriceValue = getPriceValueForType("PROMO_PRICE", productPrices, virtualProductPrices); |
| GenericValue minimumPriceValue = getPriceValueForType("MINIMUM_PRICE", productPrices, virtualProductPrices); |
| GenericValue maximumPriceValue = getPriceValueForType("MAXIMUM_PRICE", productPrices, virtualProductPrices); |
| GenericValue wholesalePriceValue = getPriceValueForType("WHOLESALE_PRICE", productPrices, virtualProductPrices); |
| GenericValue specialPromoPriceValue = getPriceValueForType("SPECIAL_PROMO_PRICE", productPrices, virtualProductPrices); |
| |
| // now if this is a virtual product check each price type, if doesn't exist get from variant with lowest DEFAULT_PRICE |
| if ("Y".equals(product.getString("isVirtual"))) { |
| // only do this if there is no default price, consider the others optional for performance reasons |
| if (defaultPriceValue == null) { |
| // Debug.logInfo("Product isVirtual and there is no default price for ID " + productId + ", trying variant prices", module); |
| |
| //use the cache to find the variant with the lowest default price |
| try { |
| List<GenericValue> variantAssocList = EntityUtil.filterByDate(delegator.findByAnd("ProductAssoc", UtilMisc.toMap("productId", product.get("productId"), "productAssocTypeId", "PRODUCT_VARIANT"), UtilMisc.toList("-fromDate"), true)); |
| BigDecimal minDefaultPrice = null; |
| List<GenericValue> variantProductPrices = null; |
| String variantProductId = null; |
| for (GenericValue variantAssoc: variantAssocList) { |
| String curVariantProductId = variantAssoc.getString("productIdTo"); |
| List<GenericValue> curVariantPriceList = EntityUtil.filterByDate(delegator.findByAnd("ProductPrice", UtilMisc.toMap("productId", curVariantProductId), UtilMisc.toList("-fromDate"), true), nowTimestamp); |
| List<GenericValue> tempDefaultPriceList = EntityUtil.filterByAnd(curVariantPriceList, UtilMisc.toMap("productPriceTypeId", "DEFAULT_PRICE")); |
| GenericValue curDefaultPriceValue = EntityUtil.getFirst(tempDefaultPriceList); |
| if (curDefaultPriceValue != null) { |
| BigDecimal curDefaultPrice = curDefaultPriceValue.getBigDecimal("price"); |
| if (minDefaultPrice == null || curDefaultPrice.compareTo(minDefaultPrice) < 0) { |
| // check to see if the product is discontinued for sale before considering it the lowest price |
| GenericValue curVariantProduct = delegator.findOne("Product", UtilMisc.toMap("productId", curVariantProductId), true); |
| if (curVariantProduct != null) { |
| Timestamp salesDiscontinuationDate = curVariantProduct.getTimestamp("salesDiscontinuationDate"); |
| if (salesDiscontinuationDate == null || salesDiscontinuationDate.after(nowTimestamp)) { |
| minDefaultPrice = curDefaultPrice; |
| variantProductPrices = curVariantPriceList; |
| variantProductId = curVariantProductId; |
| // Debug.logInfo("Found new lowest price " + minDefaultPrice + " for variant with ID " + variantProductId, module); |
| } |
| } |
| } |
| } |
| } |
| |
| if (variantProductPrices != null) { |
| // we have some other options, give 'em a go... |
| if (listPriceValue == null) { |
| listPriceValue = getPriceValueForType("LIST_PRICE", variantProductPrices, null); |
| } |
| if (defaultPriceValue == null) { |
| defaultPriceValue = getPriceValueForType("DEFAULT_PRICE", variantProductPrices, null); |
| } |
| if (competitivePriceValue == null) { |
| competitivePriceValue = getPriceValueForType("COMPETITIVE_PRICE", variantProductPrices, null); |
| } |
| if (averageCostValue == null) { |
| averageCostValue = getPriceValueForType("AVERAGE_COST", variantProductPrices, null); |
| } |
| if (promoPriceValue == null) { |
| promoPriceValue = getPriceValueForType("PROMO_PRICE", variantProductPrices, null); |
| } |
| if (minimumPriceValue == null) { |
| minimumPriceValue = getPriceValueForType("MINIMUM_PRICE", variantProductPrices, null); |
| } |
| if (maximumPriceValue == null) { |
| maximumPriceValue = getPriceValueForType("MAXIMUM_PRICE", variantProductPrices, null); |
| } |
| if (wholesalePriceValue == null) { |
| wholesalePriceValue = getPriceValueForType("WHOLESALE_PRICE", variantProductPrices, null); |
| } |
| if (specialPromoPriceValue == null) { |
| specialPromoPriceValue = getPriceValueForType("SPECIAL_PROMO_PRICE", variantProductPrices, null); |
| } |
| } |
| } catch (GenericEntityException e) { |
| Debug.logError(e, "An error occurred while getting the product prices", module); |
| } |
| } |
| } |
| |
| //boolean validPromoPriceFound = false; |
| BigDecimal promoPrice = BigDecimal.ZERO; |
| if (promoPriceValue != null && promoPriceValue.get("price") != null) { |
| promoPrice = promoPriceValue.getBigDecimal("price"); |
| //validPromoPriceFound = true; |
| } |
| |
| //boolean validWholesalePriceFound = false; |
| BigDecimal wholesalePrice = BigDecimal.ZERO; |
| if (wholesalePriceValue != null && wholesalePriceValue.get("price") != null) { |
| wholesalePrice = wholesalePriceValue.getBigDecimal("price"); |
| //validWholesalePriceFound = true; |
| } |
| |
| boolean validPriceFound = false; |
| BigDecimal defaultPrice = BigDecimal.ZERO; |
| List<GenericValue> orderItemPriceInfos = FastList.newInstance(); |
| if (defaultPriceValue != null) { |
| // If a price calc formula (service) is specified, then use it to get the unit price |
| if ("ProductPrice".equals(defaultPriceValue.getEntityName()) && UtilValidate.isNotEmpty(defaultPriceValue.getString("customPriceCalcService"))) { |
| GenericValue customMethod = null; |
| try { |
| customMethod = defaultPriceValue.getRelatedOne("CustomMethod", false); |
| } catch (GenericEntityException gee) { |
| Debug.logError(gee, "An error occurred while getting the customPriceCalcService", module); |
| } |
| if (UtilValidate.isNotEmpty(customMethod) && UtilValidate.isNotEmpty(customMethod.getString("customMethodName"))) { |
| Map<String, Object> inMap = UtilMisc.toMap("userLogin", context.get("userLogin"), "product", product); |
| inMap.put("initialPrice", defaultPriceValue.getBigDecimal("price")); |
| inMap.put("currencyUomId", currencyDefaultUomId); |
| inMap.put("quantity", quantity); |
| inMap.put("amount", amount); |
| if (UtilValidate.isNotEmpty(surveyResponseId)) { |
| inMap.put("surveyResponseId", surveyResponseId); |
| } |
| if (UtilValidate.isNotEmpty(customAttributes)) { |
| inMap.put("customAttributes", customAttributes); |
| } |
| try { |
| Map<String, Object> outMap = dispatcher.runSync(customMethod.getString("customMethodName"), inMap); |
| if (!ServiceUtil.isError(outMap)) { |
| BigDecimal calculatedDefaultPrice = (BigDecimal)outMap.get("price"); |
| orderItemPriceInfos = UtilGenerics.checkList(outMap.get("orderItemPriceInfos")); |
| if (UtilValidate.isNotEmpty(calculatedDefaultPrice)) { |
| defaultPrice = calculatedDefaultPrice; |
| validPriceFound = true; |
| } |
| } |
| } catch (GenericServiceException gse) { |
| Debug.logError(gse, "An error occurred while running the customPriceCalcService [" + customMethod.getString("customMethodName") + "]", module); |
| } |
| } |
| } |
| if (!validPriceFound && defaultPriceValue.get("price") != null) { |
| defaultPrice = defaultPriceValue.getBigDecimal("price"); |
| validPriceFound = true; |
| } |
| } |
| |
| BigDecimal listPrice = listPriceValue != null ? listPriceValue.getBigDecimal("price") : null; |
| |
| if (listPrice == null) { |
| // no list price, use defaultPrice for the final price |
| |
| // ========= ensure calculated price is not below minSalePrice or above maxSalePrice ========= |
| BigDecimal maxSellPrice = maximumPriceValue != null ? maximumPriceValue.getBigDecimal("price") : null; |
| if (maxSellPrice != null && defaultPrice.compareTo(maxSellPrice) > 0) { |
| defaultPrice = maxSellPrice; |
| } |
| // min price second to override max price, safety net |
| BigDecimal minSellPrice = minimumPriceValue != null ? minimumPriceValue.getBigDecimal("price") : null; |
| if (minSellPrice != null && defaultPrice.compareTo(minSellPrice) < 0) { |
| defaultPrice = minSellPrice; |
| // since we have found a minimum price that has overriden a the defaultPrice, even if no valid one was found, we will consider it as if one had been... |
| validPriceFound = true; |
| } |
| |
| result.put("basePrice", defaultPrice); |
| result.put("price", defaultPrice); |
| result.put("defaultPrice", defaultPrice); |
| result.put("competitivePrice", competitivePriceValue != null ? competitivePriceValue.getBigDecimal("price") : null); |
| result.put("averageCost", averageCostValue != null ? averageCostValue.getBigDecimal("price") : null); |
| result.put("promoPrice", promoPriceValue != null ? promoPriceValue.getBigDecimal("price") : null); |
| result.put("specialPromoPrice", specialPromoPriceValue != null ? specialPromoPriceValue.getBigDecimal("price") : null); |
| result.put("validPriceFound", Boolean.valueOf(validPriceFound)); |
| result.put("isSale", Boolean.FALSE); |
| result.put("orderItemPriceInfos", orderItemPriceInfos); |
| |
| Map<String, Object> errorResult = addGeneralResults(result, competitivePriceValue, specialPromoPriceValue, productStore, |
| checkIncludeVat, currencyDefaultUomId, productId, quantity, partyId, dispatcher, locale); |
| if (errorResult != null) return errorResult; |
| } else { |
| try { |
| List<GenericValue> allProductPriceRules = makeProducePriceRuleList(delegator, optimizeForLargeRuleSet, productId, virtualProductId, prodCatalogId, productStoreGroupId, webSiteId, partyId, currencyDefaultUomId); |
| allProductPriceRules = EntityUtil.filterByDate(allProductPriceRules, true); |
| |
| List<GenericValue> quantityProductPriceRules = null; |
| List<GenericValue> nonQuantityProductPriceRules = null; |
| if (findAllQuantityPrices) { |
| // split into list with quantity conditions and list without, then iterate through each quantity cond one |
| quantityProductPriceRules = FastList.newInstance(); |
| nonQuantityProductPriceRules = FastList.newInstance(); |
| for (GenericValue productPriceRule: allProductPriceRules) { |
| List<GenericValue> productPriceCondList = delegator.findByAnd("ProductPriceCond", UtilMisc.toMap("productPriceRuleId", productPriceRule.get("productPriceRuleId")), null, true); |
| |
| boolean foundQuantityInputParam = false; |
| // only consider a rule if all conditions except the quantity condition are true |
| boolean allExceptQuantTrue = true; |
| for (GenericValue productPriceCond: productPriceCondList) { |
| if ("PRIP_QUANTITY".equals(productPriceCond.getString("inputParamEnumId"))) { |
| foundQuantityInputParam = true; |
| } else { |
| if (!checkPriceCondition(productPriceCond, productId, virtualProductId, prodCatalogId, productStoreGroupId, webSiteId, partyId, quantity, listPrice, currencyDefaultUomId, delegator, nowTimestamp)) { |
| allExceptQuantTrue = false; |
| } |
| } |
| } |
| |
| if (foundQuantityInputParam && allExceptQuantTrue) { |
| quantityProductPriceRules.add(productPriceRule); |
| } else { |
| nonQuantityProductPriceRules.add(productPriceRule); |
| } |
| } |
| } |
| |
| if (findAllQuantityPrices) { |
| List<Map<String, Object>> allQuantityPrices = FastList.newInstance(); |
| |
| // if findAllQuantityPrices then iterate through quantityProductPriceRules |
| // foreach create an entry in the out list and eval that rule and all nonQuantityProductPriceRules rather than a single rule |
| for (GenericValue quantityProductPriceRule: quantityProductPriceRules) { |
| List<GenericValue> ruleListToUse = FastList.newInstance(); |
| ruleListToUse.add(quantityProductPriceRule); |
| ruleListToUse.addAll(nonQuantityProductPriceRules); |
| |
| Map<String, Object> quantCalcResults = calcPriceResultFromRules(ruleListToUse, listPrice, defaultPrice, promoPrice, |
| wholesalePrice, maximumPriceValue, minimumPriceValue, validPriceFound, |
| averageCostValue, productId, virtualProductId, prodCatalogId, productStoreGroupId, |
| webSiteId, partyId, null, currencyDefaultUomId, delegator, nowTimestamp, locale); |
| Map<String, Object> quantErrorResult = addGeneralResults(quantCalcResults, competitivePriceValue, specialPromoPriceValue, productStore, |
| checkIncludeVat, currencyDefaultUomId, productId, quantity, partyId, dispatcher, locale); |
| if (quantErrorResult != null) return quantErrorResult; |
| |
| // also add the quantityProductPriceRule to the Map so it can be used for quantity break information |
| quantCalcResults.put("quantityProductPriceRule", quantityProductPriceRule); |
| |
| allQuantityPrices.add(quantCalcResults); |
| } |
| result.put("allQuantityPrices", allQuantityPrices); |
| |
| // use a quantity 1 to get the main price, then fill in the quantity break prices |
| Map<String, Object> calcResults = calcPriceResultFromRules(allProductPriceRules, listPrice, defaultPrice, promoPrice, |
| wholesalePrice, maximumPriceValue, minimumPriceValue, validPriceFound, |
| averageCostValue, productId, virtualProductId, prodCatalogId, productStoreGroupId, |
| webSiteId, partyId, BigDecimal.ONE, currencyDefaultUomId, delegator, nowTimestamp, locale); |
| result.putAll(calcResults); |
| // The orderItemPriceInfos out parameter requires a special treatment: |
| // the list of OrderItemPriceInfos generated by the price rule is appended to |
| // the existing orderItemPriceInfos list and the aggregated list is returned. |
| List<GenericValue> orderItemPriceInfosFromRule = UtilGenerics.checkList(calcResults.get("orderItemPriceInfos")); |
| if (UtilValidate.isNotEmpty(orderItemPriceInfosFromRule)) { |
| orderItemPriceInfos.addAll(orderItemPriceInfosFromRule); |
| } |
| result.put("orderItemPriceInfos", orderItemPriceInfos); |
| |
| Map<String, Object> errorResult = addGeneralResults(result, competitivePriceValue, specialPromoPriceValue, productStore, |
| checkIncludeVat, currencyDefaultUomId, productId, quantity, partyId, dispatcher, locale); |
| if (errorResult != null) return errorResult; |
| } else { |
| Map<String, Object> calcResults = calcPriceResultFromRules(allProductPriceRules, listPrice, defaultPrice, promoPrice, |
| wholesalePrice, maximumPriceValue, minimumPriceValue, validPriceFound, |
| averageCostValue, productId, virtualProductId, prodCatalogId, productStoreGroupId, |
| webSiteId, partyId, quantity, currencyDefaultUomId, delegator, nowTimestamp, locale); |
| result.putAll(calcResults); |
| // The orderItemPriceInfos out parameter requires a special treatment: |
| // the list of OrderItemPriceInfos generated by the price rule is appended to |
| // the existing orderItemPriceInfos list and the aggregated list is returned. |
| List<GenericValue> orderItemPriceInfosFromRule = UtilGenerics.checkList(calcResults.get("orderItemPriceInfos")); |
| if (UtilValidate.isNotEmpty(orderItemPriceInfosFromRule)) { |
| orderItemPriceInfos.addAll(orderItemPriceInfosFromRule); |
| } |
| result.put("orderItemPriceInfos", orderItemPriceInfos); |
| |
| Map<String, Object> errorResult = addGeneralResults(result, competitivePriceValue, specialPromoPriceValue, productStore, |
| checkIncludeVat, currencyDefaultUomId, productId, quantity, partyId, dispatcher, locale); |
| if (errorResult != null) return errorResult; |
| } |
| } catch (GenericEntityException e) { |
| Debug.logError(e, "Error getting rules from the database while calculating price", module); |
| return ServiceUtil.returnError(UtilProperties.getMessage(resource, |
| "ProductPriceCannotRetrievePriceRules", UtilMisc.toMap("errorString", e.toString()) , locale)); |
| } |
| } |
| |
| // Convert the value to the price currency, if required |
| if ("true".equals(UtilProperties.getPropertyValue("catalog.properties", "convertProductPriceCurrency"))) { |
| if (UtilValidate.isNotEmpty(currencyDefaultUomId) && UtilValidate.isNotEmpty(currencyUomIdTo) && !currencyDefaultUomId.equals(currencyUomIdTo)) { |
| if (UtilValidate.isNotEmpty(result)) { |
| Map<String, Object> convertPriceMap = FastMap.newInstance(); |
| for(Map.Entry<String, Object> entry : result.entrySet()) { |
| BigDecimal tempPrice = BigDecimal.ZERO; |
| if(entry.getKey() == "basePrice") |
| tempPrice = (BigDecimal) entry.getValue(); |
| else if (entry.getKey() == "price") |
| tempPrice = (BigDecimal) entry.getValue(); |
| else if (entry.getKey() == "defaultPrice") |
| tempPrice = (BigDecimal) entry.getValue(); |
| else if (entry.getKey() == "competitivePrice") |
| tempPrice = (BigDecimal) entry.getValue(); |
| else if (entry.getKey() == "averageCost") |
| tempPrice = (BigDecimal) entry.getValue(); |
| else if (entry.getKey() == "promoPrice") |
| tempPrice = (BigDecimal) entry.getValue(); |
| else if (entry.getKey() == "specialPromoPrice") |
| tempPrice = (BigDecimal) entry.getValue(); |
| else if (entry.getKey() == "listPrice") |
| tempPrice = (BigDecimal) entry.getValue(); |
| |
| if (tempPrice != null && tempPrice != BigDecimal.ZERO) { |
| Map<String, Object> priceResults = FastMap.newInstance(); |
| try { |
| priceResults = dispatcher.runSync("convertUom", UtilMisc.<String, Object> toMap("uomId", currencyDefaultUomId, "uomIdTo", currencyUomIdTo, |
| "originalValue", tempPrice, "defaultDecimalScale", Long.valueOf(2), "defaultRoundingMode", "HalfUp")); |
| if (ServiceUtil.isError(priceResults) || (priceResults.get("convertedValue") == null)) { |
| Debug.logWarning("Unable to convert " + entry.getKey() + " for product " + productId, module); |
| } |
| } catch (GenericServiceException e) { |
| Debug.logError(e, module); |
| } |
| convertPriceMap.put(entry.getKey(), priceResults.get("convertedValue")); |
| } else { |
| convertPriceMap.put(entry.getKey(), entry.getValue()); |
| } |
| } |
| if (UtilValidate.isNotEmpty(convertPriceMap)) { |
| convertPriceMap.put("currencyUsed", currencyUomIdTo); |
| result = convertPriceMap; |
| } |
| } |
| } |
| } |
| |
| // utilTimer.timerString("Finished price calc [productId=" + productId + "]", module); |
| return result; |
| } |
| |
| private static GenericValue getPriceValueForType(String productPriceTypeId, List<GenericValue> productPriceList, List<GenericValue> secondaryPriceList) { |
| List<GenericValue> filteredPrices = EntityUtil.filterByAnd(productPriceList, UtilMisc.toMap("productPriceTypeId", productPriceTypeId)); |
| GenericValue priceValue = EntityUtil.getFirst(filteredPrices); |
| if (filteredPrices != null && filteredPrices.size() > 1) { |
| if (Debug.infoOn()) Debug.logInfo("There is more than one " + productPriceTypeId + " with the currencyUomId " + priceValue.getString("currencyUomId") + " and productId " + priceValue.getString("productId") + ", using the latest found with price: " + priceValue.getBigDecimal("price"), module); |
| } |
| if (priceValue == null && secondaryPriceList != null) { |
| return getPriceValueForType(productPriceTypeId, secondaryPriceList, null); |
| } |
| return priceValue; |
| } |
| |
| public static Map<String, Object> addGeneralResults(Map<String, Object> result, GenericValue competitivePriceValue, GenericValue specialPromoPriceValue, GenericValue productStore, |
| String checkIncludeVat, String currencyUomId, String productId, BigDecimal quantity, String partyId, LocalDispatcher dispatcher, Locale locale) { |
| result.put("competitivePrice", competitivePriceValue != null ? competitivePriceValue.getBigDecimal("price") : null); |
| result.put("specialPromoPrice", specialPromoPriceValue != null ? specialPromoPriceValue.getBigDecimal("price") : null); |
| result.put("currencyUsed", currencyUomId); |
| |
| // okay, now we have the calculated price, see if we should add in tax and if so do it |
| if ("Y".equals(checkIncludeVat) && productStore != null && "Y".equals(productStore.getString("showPricesWithVatTax"))) { |
| Map<String, Object> calcTaxForDisplayContext = UtilMisc.toMap("productStoreId", productStore.get("productStoreId"), |
| "productId", productId, "quantity", quantity, |
| "basePrice", (BigDecimal) result.get("price")); |
| if (UtilValidate.isNotEmpty(partyId)) { |
| calcTaxForDisplayContext.put("billToPartyId", partyId); |
| } |
| |
| try { |
| Map<String, Object> calcTaxForDisplayResult = dispatcher.runSync("calcTaxForDisplay", calcTaxForDisplayContext); |
| if (ServiceUtil.isError(calcTaxForDisplayResult)) { |
| return ServiceUtil.returnError(UtilProperties.getMessage(resource, |
| "ProductPriceCannotCalculateVatTax", locale), null, null, calcTaxForDisplayResult); |
| } |
| // taxTotal, taxPercentage, priceWithTax |
| result.put("price", calcTaxForDisplayResult.get("priceWithTax")); |
| |
| // based on the taxPercentage calculate the other amounts, including: listPrice, defaultPrice, averageCost, promoPrice, competitivePrice |
| BigDecimal taxPercentage = (BigDecimal) calcTaxForDisplayResult.get("taxPercentage"); |
| BigDecimal taxMultiplier = ONE_BASE.add(taxPercentage.divide(PERCENT_SCALE, taxCalcScale)); |
| if (result.get("listPrice") != null) { |
| result.put("listPrice", ((BigDecimal) result.get("listPrice")).multiply(taxMultiplier).setScale(taxFinalScale, taxRounding)); |
| } |
| if (result.get("defaultPrice") != null) { |
| result.put("defaultPrice", ((BigDecimal) result.get("defaultPrice")).multiply(taxMultiplier).setScale(taxFinalScale, taxRounding)); |
| } |
| if (result.get("averageCost") != null) { |
| result.put("averageCost", ((BigDecimal) result.get("averageCost")).multiply(taxMultiplier).setScale(taxFinalScale, taxRounding)); |
| } |
| if (result.get("promoPrice") != null) { |
| result.put("promoPrice", ((BigDecimal) result.get("promoPrice")).multiply(taxMultiplier).setScale(taxFinalScale, taxRounding)); |
| } |
| if (result.get("competitivePrice") != null) { |
| result.put("competitivePrice", ((BigDecimal) result.get("competitivePrice")).multiply(taxMultiplier).setScale(taxFinalScale, taxRounding)); |
| } |
| } catch (GenericServiceException e) { |
| Debug.logError(e, "Error calculating VAT tax (with calcTaxForDisplay service): " + e.toString(), module); |
| return ServiceUtil.returnError(UtilProperties.getMessage(resource, |
| "ProductPriceCannotCalculateVatTax", locale)); |
| } |
| } |
| |
| return null; |
| } |
| |
| public static List<GenericValue> makeProducePriceRuleList(Delegator delegator, boolean optimizeForLargeRuleSet, String productId, String virtualProductId, String prodCatalogId, String productStoreGroupId, String webSiteId, String partyId, String currencyUomId) throws GenericEntityException { |
| List<GenericValue> productPriceRules = null; |
| |
| // At this point we have two options: optimize for large ruleset, or optimize for small ruleset |
| // NOTE: This only effects the way that the rules to be evaluated are selected. |
| // For large rule sets we can do a cached pre-filter to limit the rules that need to be evaled for a specific product. |
| // Genercally I don't think that rule sets will get that big though, so the default is optimize for smaller rule set. |
| if (optimizeForLargeRuleSet) { |
| // ========= find all rules that must be run for each input type; this is kind of like a pre-filter to slim down the rules to run ========= |
| // utilTimer.timerString("Before create rule id list", module); |
| TreeSet<String> productPriceRuleIds = new TreeSet<String>(); |
| |
| // ------- These are all of the conditions that DON'T depend on the current inputs ------- |
| |
| // by productCategoryId |
| // for we will always include any rules that go by category, shouldn't be too many to iterate through each time and will save on cache entries |
| // note that we always want to put the category, quantity, etc ones that find all rules with these conditions in separate cache lists so that they can be easily cleared |
| Collection<GenericValue> productCategoryIdConds = delegator.findByAnd("ProductPriceCond", UtilMisc.toMap("inputParamEnumId", "PRIP_PROD_CAT_ID"), null, true); |
| if (UtilValidate.isNotEmpty(productCategoryIdConds)) { |
| for (GenericValue productCategoryIdCond: productCategoryIdConds) { |
| productPriceRuleIds.add(productCategoryIdCond.getString("productPriceRuleId")); |
| } |
| } |
| |
| // by productFeatureId |
| Collection<GenericValue> productFeatureIdConds = delegator.findByAnd("ProductPriceCond", UtilMisc.toMap("inputParamEnumId", "PRIP_PROD_FEAT_ID"), null, true); |
| if (UtilValidate.isNotEmpty(productFeatureIdConds)) { |
| for (GenericValue productFeatureIdCond: productFeatureIdConds) { |
| productPriceRuleIds.add(productFeatureIdCond.getString("productPriceRuleId")); |
| } |
| } |
| |
| // by quantity -- should we really do this one, ie is it necessary? |
| // we could say that all rules with quantity on them must have one of these other values |
| // but, no we'll do it the other way, any that have a quantity will always get compared |
| Collection<GenericValue> quantityConds = delegator.findByAnd("ProductPriceCond", UtilMisc.toMap("inputParamEnumId", "PRIP_QUANTITY"), null, true); |
| if (UtilValidate.isNotEmpty(quantityConds)) { |
| for (GenericValue quantityCond: quantityConds) { |
| productPriceRuleIds.add(quantityCond.getString("productPriceRuleId")); |
| } |
| } |
| |
| // by roleTypeId |
| Collection<GenericValue> roleTypeIdConds = delegator.findByAnd("ProductPriceCond", UtilMisc.toMap("inputParamEnumId", "PRIP_ROLE_TYPE"), null, true); |
| if (UtilValidate.isNotEmpty(roleTypeIdConds)) { |
| for (GenericValue roleTypeIdCond: roleTypeIdConds) { |
| productPriceRuleIds.add(roleTypeIdCond.getString("productPriceRuleId")); |
| } |
| } |
| |
| // TODO, not supported yet: by groupPartyId |
| // TODO, not supported yet: by partyClassificationGroupId |
| // later: (by partyClassificationTypeId) |
| |
| // by listPrice |
| Collection<GenericValue> listPriceConds = delegator.findByAnd("ProductPriceCond", UtilMisc.toMap("inputParamEnumId", "PRIP_LIST_PRICE"), null, true); |
| if (UtilValidate.isNotEmpty(listPriceConds)) { |
| for (GenericValue listPriceCond: listPriceConds) { |
| productPriceRuleIds.add(listPriceCond.getString("productPriceRuleId")); |
| } |
| } |
| |
| // ------- These are all of them that DO depend on the current inputs ------- |
| |
| // by productId |
| Collection<GenericValue> productIdConds = delegator.findByAnd("ProductPriceCond", UtilMisc.toMap("inputParamEnumId", "PRIP_PRODUCT_ID", "condValue", productId), null, true); |
| if (UtilValidate.isNotEmpty(productIdConds)) { |
| for (GenericValue productIdCond: productIdConds) { |
| productPriceRuleIds.add(productIdCond.getString("productPriceRuleId")); |
| } |
| } |
| |
| // by virtualProductId, if not null |
| if (virtualProductId != null) { |
| Collection<GenericValue> virtualProductIdConds = delegator.findByAnd("ProductPriceCond", UtilMisc.toMap("inputParamEnumId", "PRIP_PRODUCT_ID", "condValue", virtualProductId), null, true); |
| if (UtilValidate.isNotEmpty(virtualProductIdConds)) { |
| for (GenericValue virtualProductIdCond: virtualProductIdConds) { |
| productPriceRuleIds.add(virtualProductIdCond.getString("productPriceRuleId")); |
| } |
| } |
| } |
| |
| // by prodCatalogId - which is optional in certain cases |
| if (UtilValidate.isNotEmpty(prodCatalogId)) { |
| Collection<GenericValue> prodCatalogIdConds = delegator.findByAnd("ProductPriceCond", UtilMisc.toMap("inputParamEnumId", "PRIP_PROD_CLG_ID", "condValue", prodCatalogId), null, true); |
| if (UtilValidate.isNotEmpty(prodCatalogIdConds)) { |
| for (GenericValue prodCatalogIdCond: prodCatalogIdConds) { |
| productPriceRuleIds.add(prodCatalogIdCond.getString("productPriceRuleId")); |
| } |
| } |
| } |
| |
| // by productStoreGroupId |
| if (UtilValidate.isNotEmpty(productStoreGroupId)) { |
| Collection<GenericValue> storeGroupConds = delegator.findByAnd("ProductPriceCond", UtilMisc.toMap("inputParamEnumId", "PRIP_PROD_SGRP_ID", "condValue", productStoreGroupId), null, true); |
| if (UtilValidate.isNotEmpty(storeGroupConds)) { |
| for (GenericValue storeGroupCond: storeGroupConds) { |
| productPriceRuleIds.add(storeGroupCond.getString("productPriceRuleId")); |
| } |
| } |
| } |
| |
| // by webSiteId |
| if (UtilValidate.isNotEmpty(webSiteId)) { |
| Collection<GenericValue> webSiteIdConds = delegator.findByAnd("ProductPriceCond", UtilMisc.toMap("inputParamEnumId", "PRIP_WEBSITE_ID", "condValue", webSiteId), null, true); |
| if (UtilValidate.isNotEmpty(webSiteIdConds)) { |
| for (GenericValue webSiteIdCond: webSiteIdConds) { |
| productPriceRuleIds.add(webSiteIdCond.getString("productPriceRuleId")); |
| } |
| } |
| } |
| |
| // by partyId |
| if (UtilValidate.isNotEmpty(partyId)) { |
| Collection<GenericValue> partyIdConds = delegator.findByAnd("ProductPriceCond", UtilMisc.toMap("inputParamEnumId", "PRIP_PARTY_ID", "condValue", partyId), null, true); |
| if (UtilValidate.isNotEmpty(partyIdConds)) { |
| for (GenericValue partyIdCond: partyIdConds) { |
| productPriceRuleIds.add(partyIdCond.getString("productPriceRuleId")); |
| } |
| } |
| } |
| |
| // by currencyUomId |
| Collection<GenericValue> currencyUomIdConds = delegator.findByAnd("ProductPriceCond", UtilMisc.toMap("inputParamEnumId", "PRIP_CURRENCY_UOMID", "condValue", currencyUomId), null, true); |
| if (UtilValidate.isNotEmpty(currencyUomIdConds)) { |
| for (GenericValue currencyUomIdCond: currencyUomIdConds) { |
| productPriceRuleIds.add(currencyUomIdCond.getString("productPriceRuleId")); |
| } |
| } |
| |
| productPriceRules = FastList.newInstance(); |
| for (String productPriceRuleId: productPriceRuleIds) { |
| GenericValue productPriceRule = delegator.findOne("ProductPriceRule", UtilMisc.toMap("productPriceRuleId", productPriceRuleId), true); |
| if (productPriceRule == null) continue; |
| productPriceRules.add(productPriceRule); |
| } |
| } else { |
| // this would be nice, but we can't cache this so easily... |
| // List pprExprs = UtilMisc.toList(EntityCondition.makeCondition("thruDate", EntityOperator.EQUALS, null), |
| // EntityCondition.makeCondition("thruDate", EntityOperator.GREATER_THAN, UtilDateTime.nowTimestamp())); |
| // productPriceRules = delegator.findByOr("ProductPriceRule", pprExprs); |
| |
| productPriceRules = delegator.findList("ProductPriceRule", null, null, null, null, true); |
| if (productPriceRules == null) productPriceRules = FastList.newInstance(); |
| } |
| |
| return productPriceRules; |
| } |
| |
| public static Map<String, Object> calcPriceResultFromRules(List<GenericValue> productPriceRules, BigDecimal listPrice, BigDecimal defaultPrice, BigDecimal promoPrice, |
| BigDecimal wholesalePrice, GenericValue maximumPriceValue, GenericValue minimumPriceValue, boolean validPriceFound, |
| GenericValue averageCostValue, String productId, String virtualProductId, String prodCatalogId, String productStoreGroupId, |
| String webSiteId, String partyId, BigDecimal quantity, String currencyUomId, Delegator delegator, Timestamp nowTimestamp, |
| Locale locale) throws GenericEntityException { |
| |
| Map<String, Object> calcResults = FastMap.newInstance(); |
| |
| List<GenericValue> orderItemPriceInfos = FastList.newInstance(); |
| boolean isSale = false; |
| |
| // ========= go through each price rule by id and eval all conditions ========= |
| // utilTimer.timerString("Before eval rules", module); |
| int totalConds = 0; |
| int totalActions = 0; |
| int totalRules = 0; |
| |
| // get some of the base values to calculate with |
| BigDecimal averageCost = (averageCostValue != null && averageCostValue.get("price") != null) ? averageCostValue.getBigDecimal("price") : listPrice; |
| BigDecimal margin = listPrice.subtract(averageCost); |
| |
| // calculate running sum based on listPrice and rules found |
| BigDecimal price = listPrice; |
| |
| for (GenericValue productPriceRule: productPriceRules) { |
| String productPriceRuleId = productPriceRule.getString("productPriceRuleId"); |
| |
| // check from/thru dates |
| java.sql.Timestamp fromDate = productPriceRule.getTimestamp("fromDate"); |
| java.sql.Timestamp thruDate = productPriceRule.getTimestamp("thruDate"); |
| |
| if (fromDate != null && fromDate.after(nowTimestamp)) { |
| // hasn't started yet |
| continue; |
| } |
| if (thruDate != null && thruDate.before(nowTimestamp)) { |
| // already expired |
| continue; |
| } |
| |
| // check all conditions |
| boolean allTrue = true; |
| StringBuilder condsDescription = new StringBuilder(); |
| List<GenericValue> productPriceConds = delegator.findByAnd("ProductPriceCond", UtilMisc.toMap("productPriceRuleId", productPriceRuleId), null, true); |
| for (GenericValue productPriceCond: productPriceConds) { |
| |
| totalConds++; |
| |
| if (!checkPriceCondition(productPriceCond, productId, virtualProductId, prodCatalogId, productStoreGroupId, webSiteId, partyId, quantity, listPrice, currencyUomId, delegator, nowTimestamp)) { |
| allTrue = false; |
| break; |
| } |
| |
| // add condsDescription string entry |
| condsDescription.append("["); |
| GenericValue inputParamEnum = productPriceCond.getRelatedOne("InputParamEnumeration", true); |
| |
| condsDescription.append(inputParamEnum.getString("enumCode")); |
| // condsDescription.append(":"); |
| GenericValue operatorEnum = productPriceCond.getRelatedOne("OperatorEnumeration", true); |
| |
| condsDescription.append(operatorEnum.getString("description")); |
| // condsDescription.append(":"); |
| condsDescription.append(productPriceCond.getString("condValue")); |
| condsDescription.append("] "); |
| } |
| |
| // add some info about the prices we are calculating from |
| condsDescription.append("[list:"); |
| condsDescription.append(listPrice); |
| condsDescription.append(";avgCost:"); |
| condsDescription.append(averageCost); |
| condsDescription.append(";margin:"); |
| condsDescription.append(margin); |
| condsDescription.append("] "); |
| |
| boolean foundFlatOverride = false; |
| |
| // if all true, perform all actions |
| if (allTrue) { |
| // check isSale |
| if ("Y".equals(productPriceRule.getString("isSale"))) { |
| isSale = true; |
| } |
| |
| List<GenericValue> productPriceActions = delegator.findByAnd("ProductPriceAction", UtilMisc.toMap("productPriceRuleId", productPriceRuleId), null, true); |
| for (GenericValue productPriceAction: productPriceActions) { |
| |
| totalActions++; |
| |
| // yeah, finally here, perform the action, ie, modify the price |
| BigDecimal modifyAmount = BigDecimal.ZERO; |
| |
| if ("PRICE_POD".equals(productPriceAction.getString("productPriceActionTypeId"))) { |
| if (productPriceAction.get("amount") != null) { |
| modifyAmount = defaultPrice.multiply(productPriceAction.getBigDecimal("amount").movePointLeft(2)); |
| price = defaultPrice; |
| } |
| } else if ("PRICE_POL".equals(productPriceAction.getString("productPriceActionTypeId"))) { |
| if (productPriceAction.get("amount") != null) { |
| modifyAmount = listPrice.multiply(productPriceAction.getBigDecimal("amount").movePointLeft(2)); |
| } |
| } else if ("PRICE_POAC".equals(productPriceAction.getString("productPriceActionTypeId"))) { |
| if (productPriceAction.get("amount") != null) { |
| modifyAmount = averageCost.multiply(productPriceAction.getBigDecimal("amount").movePointLeft(2)); |
| } |
| } else if ("PRICE_POM".equals(productPriceAction.getString("productPriceActionTypeId"))) { |
| if (productPriceAction.get("amount") != null) { |
| modifyAmount = margin.multiply(productPriceAction.getBigDecimal("amount").movePointLeft(2)); |
| } |
| } else if ("PRICE_POWHS".equals(productPriceAction.getString("productPriceActionTypeId"))) { |
| if (productPriceAction.get("amount") != null && wholesalePrice != null) { |
| modifyAmount = wholesalePrice.multiply(productPriceAction.getBigDecimal("amount").movePointLeft(2)); |
| } |
| } else if ("PRICE_FOL".equals(productPriceAction.getString("productPriceActionTypeId"))) { |
| if (productPriceAction.get("amount") != null) { |
| modifyAmount = productPriceAction.getBigDecimal("amount"); |
| } |
| } else if ("PRICE_FLAT".equals(productPriceAction.getString("productPriceActionTypeId"))) { |
| // this one is a bit different, break out of the loop because we now have our final price |
| foundFlatOverride = true; |
| if (productPriceAction.get("amount") != null) { |
| price = productPriceAction.getBigDecimal("amount"); |
| } else { |
| Debug.logInfo("ProductPriceAction had null amount, using default price: " + defaultPrice + " for product with id " + productId, module); |
| price = defaultPrice; |
| isSale = false; // reverse isSale flag, as this sale rule was actually not applied |
| } |
| } else if ("PRICE_PFLAT".equals(productPriceAction.getString("productPriceActionTypeId"))) { |
| // this one is a bit different too, break out of the loop because we now have our final price |
| foundFlatOverride = true; |
| price = promoPrice; |
| if (productPriceAction.get("amount") != null) { |
| price = price.add(productPriceAction.getBigDecimal("amount")); |
| } |
| if (price.compareTo(BigDecimal.ZERO) == 0) { |
| if (defaultPrice.compareTo(BigDecimal.ZERO) != 0) { |
| Debug.logInfo("PromoPrice and ProductPriceAction had null amount, using default price: " + defaultPrice + " for product with id " + productId, module); |
| price = defaultPrice; |
| } else if (listPrice.compareTo(BigDecimal.ZERO) != 0) { |
| Debug.logInfo("PromoPrice and ProductPriceAction had null amount and no default price was available, using list price: " + listPrice + " for product with id " + productId, module); |
| price = listPrice; |
| } else { |
| Debug.logError("PromoPrice and ProductPriceAction had null amount and no default or list price was available, so price is set to zero for product with id " + productId, module); |
| price = BigDecimal.ZERO; |
| } |
| isSale = false; // reverse isSale flag, as this sale rule was actually not applied |
| } |
| } else if ("PRICE_WFLAT".equals(productPriceAction.getString("productPriceActionTypeId"))) { |
| // same as promo price but using the wholesale price instead |
| foundFlatOverride = true; |
| price = wholesalePrice; |
| if (productPriceAction.get("amount") != null) { |
| price = price.add(productPriceAction.getBigDecimal("amount")); |
| } |
| if (price.compareTo(BigDecimal.ZERO) == 0) { |
| if (defaultPrice.compareTo(BigDecimal.ZERO) != 0) { |
| Debug.logInfo("WholesalePrice and ProductPriceAction had null amount, using default price: " + defaultPrice + " for product with id " + productId, module); |
| price = defaultPrice; |
| } else if (listPrice.compareTo(BigDecimal.ZERO) != 0) { |
| Debug.logInfo("WholesalePrice and ProductPriceAction had null amount and no default price was available, using list price: " + listPrice + " for product with id " + productId, module); |
| price = listPrice; |
| } else { |
| Debug.logError("WholesalePrice and ProductPriceAction had null amount and no default or list price was available, so price is set to zero for product with id " + productId, module); |
| price = BigDecimal.ZERO; |
| } |
| isSale = false; // reverse isSale flag, as this sale rule was actually not applied |
| } |
| } |
| |
| // add a orderItemPriceInfo element too, without orderId or orderItemId |
| StringBuilder priceInfoDescription = new StringBuilder(); |
| |
| |
| priceInfoDescription.append(condsDescription.toString()); |
| priceInfoDescription.append("["); |
| priceInfoDescription.append(UtilProperties.getMessage(resource, "ProductPriceConditionType", locale)); |
| priceInfoDescription.append(productPriceAction.getString("productPriceActionTypeId")); |
| priceInfoDescription.append("]"); |
| |
| GenericValue orderItemPriceInfo = delegator.makeValue("OrderItemPriceInfo"); |
| |
| orderItemPriceInfo.set("productPriceRuleId", productPriceAction.get("productPriceRuleId")); |
| orderItemPriceInfo.set("productPriceActionSeqId", productPriceAction.get("productPriceActionSeqId")); |
| orderItemPriceInfo.set("modifyAmount", modifyAmount); |
| orderItemPriceInfo.set("rateCode", productPriceAction.get("rateCode")); |
| // make sure description is <= than 250 chars |
| String priceInfoDescriptionString = priceInfoDescription.toString(); |
| |
| if (priceInfoDescriptionString.length() > 250) { |
| priceInfoDescriptionString = priceInfoDescriptionString.substring(0, 250); |
| } |
| orderItemPriceInfo.set("description", priceInfoDescriptionString); |
| orderItemPriceInfos.add(orderItemPriceInfo); |
| |
| if (foundFlatOverride) { |
| break; |
| } else { |
| price = price.add(modifyAmount); |
| } |
| } |
| } |
| |
| totalRules++; |
| |
| if (foundFlatOverride) { |
| break; |
| } |
| } |
| |
| if (Debug.verboseOn()) { |
| Debug.logVerbose("Unchecked Calculated price: " + price, module); |
| Debug.logVerbose("PriceInfo:", module); |
| for (GenericValue orderItemPriceInfo: orderItemPriceInfos) { |
| Debug.logVerbose(" --- " + orderItemPriceInfo.toString(), module); |
| } |
| } |
| |
| // if no actions were run on the list price, then use the default price |
| if (totalActions == 0) { |
| price = defaultPrice; |
| // here we will leave validPriceFound as it was originally set for the defaultPrice since that is what we are setting the price to... |
| } else { |
| // at least one price rule action was found, so we will consider it valid |
| validPriceFound = true; |
| } |
| |
| // ========= ensure calculated price is not below minSalePrice or above maxSalePrice ========= |
| BigDecimal maxSellPrice = maximumPriceValue != null ? maximumPriceValue.getBigDecimal("price") : null; |
| if (maxSellPrice != null && price.compareTo(maxSellPrice) > 0) { |
| price = maxSellPrice; |
| } |
| // min price second to override max price, safety net |
| BigDecimal minSellPrice = minimumPriceValue != null ? minimumPriceValue.getBigDecimal("price") : null; |
| if (minSellPrice != null && price.compareTo(minSellPrice) < 0) { |
| price = minSellPrice; |
| // since we have found a minimum price that has overriden a the defaultPrice, even if no valid one was found, we will consider it as if one had been... |
| validPriceFound = true; |
| } |
| |
| if (Debug.verboseOn()) Debug.logVerbose("Final Calculated price: " + price + ", rules: " + totalRules + ", conds: " + totalConds + ", actions: " + totalActions, module); |
| |
| calcResults.put("basePrice", price); |
| calcResults.put("price", price); |
| calcResults.put("listPrice", listPrice); |
| calcResults.put("defaultPrice", defaultPrice); |
| calcResults.put("averageCost", averageCost); |
| calcResults.put("orderItemPriceInfos", orderItemPriceInfos); |
| calcResults.put("isSale", Boolean.valueOf(isSale)); |
| calcResults.put("validPriceFound", Boolean.valueOf(validPriceFound)); |
| |
| return calcResults; |
| } |
| |
| public static boolean checkPriceCondition(GenericValue productPriceCond, String productId, String virtualProductId, String prodCatalogId, |
| String productStoreGroupId, String webSiteId, String partyId, BigDecimal quantity, BigDecimal listPrice, |
| String currencyUomId, Delegator delegator, Timestamp nowTimestamp) throws GenericEntityException { |
| if (Debug.verboseOn()) Debug.logVerbose("Checking price condition: " + productPriceCond, module); |
| int compare = 0; |
| |
| if ("PRIP_PRODUCT_ID".equals(productPriceCond.getString("inputParamEnumId"))) { |
| compare = productId.compareTo(productPriceCond.getString("condValue")); |
| } else if ("PRIP_PROD_CAT_ID".equals(productPriceCond.getString("inputParamEnumId"))) { |
| // if a ProductCategoryMember exists for this productId and the specified productCategoryId |
| String productCategoryId = productPriceCond.getString("condValue"); |
| List<GenericValue> productCategoryMembers = delegator.findByAnd("ProductCategoryMember", |
| UtilMisc.toMap("productId", productId, "productCategoryId", productCategoryId), null, true); |
| // and from/thru date within range |
| productCategoryMembers = EntityUtil.filterByDate(productCategoryMembers, nowTimestamp, null, null, true); |
| // then 0 (equals), otherwise 1 (not equals) |
| if (UtilValidate.isNotEmpty(productCategoryMembers)) { |
| compare = 0; |
| } else { |
| compare = 1; |
| } |
| |
| // if there is a virtualProductId, try that given that this one has failed |
| // NOTE: this is important becuase of the common scenario where a virtual product is a member of a category but the variants will typically NOT be |
| // NOTE: we may want to parameterize this in the future, ie with an indicator on the ProductPriceCond entity |
| if (compare == 1 && UtilValidate.isNotEmpty(virtualProductId)) { |
| List<GenericValue> virtualProductCategoryMembers = delegator.findByAnd("ProductCategoryMember", |
| UtilMisc.toMap("productId", virtualProductId, "productCategoryId", productCategoryId), null, true); |
| // and from/thru date within range |
| virtualProductCategoryMembers = EntityUtil.filterByDate(virtualProductCategoryMembers, nowTimestamp, null, null, true); |
| if (UtilValidate.isNotEmpty(virtualProductCategoryMembers)) { |
| // we found a member record? great, then this condition is satisfied |
| compare = 0; |
| } |
| } |
| } else if ("PRIP_PROD_FEAT_ID".equals(productPriceCond.getString("inputParamEnumId"))) { |
| // NOTE: DEJ20070130 don't retry this condition with the virtualProductId as well; this breaks various things you might want to do with price rules, like have different pricing for a variant products with a certain distinguishing feature |
| |
| // if a ProductFeatureAppl exists for this productId and the specified productFeatureId |
| String productFeatureId = productPriceCond.getString("condValue"); |
| List<GenericValue> productFeatureAppls = delegator.findByAnd("ProductFeatureAppl", |
| UtilMisc.toMap("productId", productId, "productFeatureId", productFeatureId), null, true); |
| // and from/thru date within range |
| productFeatureAppls = EntityUtil.filterByDate(productFeatureAppls, nowTimestamp, null, null, true); |
| // then 0 (equals), otherwise 1 (not equals) |
| if (UtilValidate.isNotEmpty(productFeatureAppls)) { |
| compare = 0; |
| } else { |
| compare = 1; |
| } |
| } else if ("PRIP_PROD_CLG_ID".equals(productPriceCond.getString("inputParamEnumId"))) { |
| if (UtilValidate.isNotEmpty(prodCatalogId)) { |
| compare = prodCatalogId.compareTo(productPriceCond.getString("condValue")); |
| } else { |
| // this shouldn't happen because if prodCatalogId is null no PRIP_PROD_CLG_ID prices will be in the list |
| compare = 1; |
| } |
| } else if ("PRIP_PROD_SGRP_ID".equals(productPriceCond.getString("inputParamEnumId"))) { |
| if (UtilValidate.isNotEmpty(productStoreGroupId)) { |
| compare = productStoreGroupId.compareTo(productPriceCond.getString("condValue")); |
| } else { |
| compare = 1; |
| } |
| } else if ("PRIP_WEBSITE_ID".equals(productPriceCond.getString("inputParamEnumId"))) { |
| if (UtilValidate.isNotEmpty(webSiteId)) { |
| compare = webSiteId.compareTo(productPriceCond.getString("condValue")); |
| } else { |
| compare = 1; |
| } |
| } else if ("PRIP_QUANTITY".equals(productPriceCond.getString("inputParamEnumId"))) { |
| if (quantity == null) { |
| // if no quantity is passed in, assume all quantity conditions pass |
| // NOTE: setting compare = 0 won't do the trick here because the condition won't always be or include and equal |
| return true; |
| } else { |
| compare = quantity.compareTo(new BigDecimal(productPriceCond.getString("condValue"))); |
| } |
| } else if ("PRIP_PARTY_ID".equals(productPriceCond.getString("inputParamEnumId"))) { |
| if (UtilValidate.isNotEmpty(partyId)) { |
| compare = partyId.compareTo(productPriceCond.getString("condValue")); |
| } else { |
| compare = 1; |
| } |
| } else if ("PRIP_PARTY_GRP_MEM".equals(productPriceCond.getString("inputParamEnumId"))) { |
| if (UtilValidate.isEmpty(partyId)) { |
| compare = 1; |
| } else { |
| String groupPartyId = productPriceCond.getString("condValue"); |
| if (partyId.equals(groupPartyId)) { |
| compare = 0; |
| } else { |
| // look for PartyRelationship with |
| // partyRelationshipTypeId=GROUP_ROLLUP, the partyIdTo is |
| // the group member, so the partyIdFrom is the groupPartyId |
| List<GenericValue> partyRelationshipList = delegator.findByAnd("PartyRelationship", UtilMisc.toMap("partyIdFrom", groupPartyId, "partyIdTo", partyId, "partyRelationshipTypeId", "GROUP_ROLLUP"), null, true); |
| // and from/thru date within range |
| partyRelationshipList = EntityUtil.filterByDate(partyRelationshipList, nowTimestamp, null, null, true); |
| // then 0 (equals), otherwise 1 (not equals) |
| if (UtilValidate.isNotEmpty(partyRelationshipList)) { |
| compare = 0; |
| } else { |
| compare = checkConditionPartyHierarchy(delegator, nowTimestamp, groupPartyId, partyId); |
| } |
| } |
| } |
| } else if ("PRIP_PARTY_CLASS".equals(productPriceCond.getString("inputParamEnumId"))) { |
| if (UtilValidate.isEmpty(partyId)) { |
| compare = 1; |
| } else { |
| String partyClassificationGroupId = productPriceCond.getString("condValue"); |
| // find any PartyClassification |
| List<GenericValue> partyClassificationList = delegator.findByAnd("PartyClassification", UtilMisc.toMap("partyId", partyId, "partyClassificationGroupId", partyClassificationGroupId), null, true); |
| // and from/thru date within range |
| partyClassificationList = EntityUtil.filterByDate(partyClassificationList, nowTimestamp, null, null, true); |
| // then 0 (equals), otherwise 1 (not equals) |
| if (UtilValidate.isNotEmpty(partyClassificationList)) { |
| compare = 0; |
| } else { |
| compare = 1; |
| } |
| } |
| } else if ("PRIP_ROLE_TYPE".equals(productPriceCond.getString("inputParamEnumId"))) { |
| if (partyId != null) { |
| // if a PartyRole exists for this partyId and the specified roleTypeId |
| GenericValue partyRole = delegator.findOne("PartyRole", |
| UtilMisc.toMap("partyId", partyId, "roleTypeId", productPriceCond.getString("condValue")), true); |
| |
| // then 0 (equals), otherwise 1 (not equals) |
| if (partyRole != null) { |
| compare = 0; |
| } else { |
| compare = 1; |
| } |
| } else { |
| compare = 1; |
| } |
| } else if ("PRIP_LIST_PRICE".equals(productPriceCond.getString("inputParamEnumId"))) { |
| BigDecimal listPriceValue = listPrice; |
| |
| compare = listPriceValue.compareTo(new BigDecimal(productPriceCond.getString("condValue"))); |
| } else if ("PRIP_CURRENCY_UOMID".equals(productPriceCond.getString("inputParamEnumId"))) { |
| compare = currencyUomId.compareTo(productPriceCond.getString("condValue")); |
| } else { |
| Debug.logWarning("An un-supported productPriceCond input parameter (lhs) was used: " + productPriceCond.getString("inputParamEnumId") + ", returning false, ie check failed", module); |
| return false; |
| } |
| |
| if (Debug.verboseOn()) Debug.logVerbose("Price Condition compare done, compare=" + compare, module); |
| |
| if ("PRC_EQ".equals(productPriceCond.getString("operatorEnumId"))) { |
| if (compare == 0) return true; |
| } else if ("PRC_NEQ".equals(productPriceCond.getString("operatorEnumId"))) { |
| if (compare != 0) return true; |
| } else if ("PRC_LT".equals(productPriceCond.getString("operatorEnumId"))) { |
| if (compare < 0) return true; |
| } else if ("PRC_LTE".equals(productPriceCond.getString("operatorEnumId"))) { |
| if (compare <= 0) return true; |
| } else if ("PRC_GT".equals(productPriceCond.getString("operatorEnumId"))) { |
| if (compare > 0) return true; |
| } else if ("PRC_GTE".equals(productPriceCond.getString("operatorEnumId"))) { |
| if (compare >= 0) return true; |
| } else { |
| Debug.logWarning("An un-supported productPriceCond condition was used: " + productPriceCond.getString("operatorEnumId") + ", returning false, ie check failed", module); |
| return false; |
| } |
| return false; |
| } |
| |
| private static int checkConditionPartyHierarchy(Delegator delegator, Timestamp nowTimestamp, String groupPartyId, String partyId) throws GenericEntityException{ |
| List<GenericValue> partyRelationshipList = delegator.findByAnd("PartyRelationship", UtilMisc.toMap("partyIdTo", partyId, "partyRelationshipTypeId", "GROUP_ROLLUP"), null, true); |
| partyRelationshipList = EntityUtil.filterByDate(partyRelationshipList, nowTimestamp, null, null, true); |
| for (GenericValue genericValue : partyRelationshipList) { |
| String partyIdFrom = (String)genericValue.get("partyIdFrom"); |
| if (partyIdFrom.equals(groupPartyId)) { |
| return 0; |
| } |
| if (0 == checkConditionPartyHierarchy(delegator, nowTimestamp, groupPartyId, partyIdFrom)) { |
| return 0; |
| } |
| } |
| |
| return 1; |
| } |
| |
| /** |
| * Calculates the purchase price of a product |
| */ |
| public static Map<String, Object> calculatePurchasePrice(DispatchContext dctx, Map<String, ? extends Object> context) { |
| Delegator delegator = dctx.getDelegator(); |
| LocalDispatcher dispatcher = dctx.getDispatcher(); |
| Map<String, Object> result = FastMap.newInstance(); |
| |
| List<GenericValue> orderItemPriceInfos = FastList.newInstance(); |
| boolean validPriceFound = false; |
| BigDecimal price = BigDecimal.ZERO; |
| |
| GenericValue product = (GenericValue)context.get("product"); |
| String productId = product.getString("productId"); |
| String currencyUomId = (String)context.get("currencyUomId"); |
| String partyId = (String)context.get("partyId"); |
| BigDecimal quantity = (BigDecimal)context.get("quantity"); |
| Locale locale = (Locale)context.get("locale"); |
| |
| // a) Get the Price from the Agreement* data model |
| // TODO: Implement this |
| |
| // b) If no price can be found, get the lastPrice from the SupplierProduct entity |
| if (!validPriceFound) { |
| Map<String, Object> priceContext = UtilMisc.toMap("currencyUomId", currencyUomId, "partyId", partyId, "productId", productId, "quantity", quantity); |
| List<GenericValue> productSuppliers = null; |
| try { |
| Map<String, Object> priceResult = dispatcher.runSync("getSuppliersForProduct", priceContext); |
| if (ServiceUtil.isError(priceResult)) { |
| String errMsg = ServiceUtil.getErrorMessage(priceResult); |
| Debug.logError(errMsg, module); |
| return ServiceUtil.returnError(errMsg); |
| } |
| productSuppliers = UtilGenerics.checkList(priceResult.get("supplierProducts")); |
| } catch (GenericServiceException gse) { |
| Debug.logError(gse, module); |
| return ServiceUtil.returnError(gse.getMessage()); |
| } |
| if (productSuppliers != null) { |
| for (GenericValue productSupplier: productSuppliers) { |
| if (!validPriceFound) { |
| price = ((BigDecimal)productSupplier.get("lastPrice")); |
| validPriceFound = true; |
| } |
| // add a orderItemPriceInfo element too, without orderId or orderItemId |
| StringBuilder priceInfoDescription = new StringBuilder(); |
| priceInfoDescription.append(UtilProperties.getMessage(resource, "ProductSupplier", locale)); |
| priceInfoDescription.append(" ["); |
| priceInfoDescription.append(UtilProperties.getMessage(resource, "ProductSupplierMinimumOrderQuantity", locale)); |
| priceInfoDescription.append(productSupplier.getBigDecimal("minimumOrderQuantity")); |
| priceInfoDescription.append(UtilProperties.getMessage(resource, "ProductSupplierLastPrice", locale)); |
| priceInfoDescription.append(productSupplier.getBigDecimal("lastPrice")); |
| priceInfoDescription.append("]"); |
| GenericValue orderItemPriceInfo = delegator.makeValue("OrderItemPriceInfo"); |
| //orderItemPriceInfo.set("productPriceRuleId", productPriceAction.get("productPriceRuleId")); |
| //orderItemPriceInfo.set("productPriceActionSeqId", productPriceAction.get("productPriceActionSeqId")); |
| //orderItemPriceInfo.set("modifyAmount", modifyAmount); |
| // make sure description is <= than 250 chars |
| String priceInfoDescriptionString = priceInfoDescription.toString(); |
| if (priceInfoDescriptionString.length() > 250) { |
| priceInfoDescriptionString = priceInfoDescriptionString.substring(0, 250); |
| } |
| orderItemPriceInfo.set("description", priceInfoDescriptionString); |
| orderItemPriceInfos.add(orderItemPriceInfo); |
| } |
| } |
| } |
| |
| // c) If no price can be found, get the averageCost from the ProductPrice entity |
| if (!validPriceFound) { |
| List<GenericValue> prices = null; |
| try { |
| prices = delegator.findByAnd("ProductPrice", UtilMisc.toMap("productId", productId, |
| "productPricePurposeId", "PURCHASE"), UtilMisc.toList("-fromDate"), false); |
| |
| // if no prices are found; find the prices of the parent product |
| if (UtilValidate.isEmpty(prices)) { |
| GenericValue parentProduct = ProductWorker.getParentProduct(productId, delegator); |
| if (parentProduct != null) { |
| String parentProductId = parentProduct.getString("productId"); |
| prices = delegator.findByAnd("ProductPrice", UtilMisc.toMap("productId", parentProductId, |
| "productPricePurposeId", "PURCHASE"), UtilMisc.toList("-fromDate"), false); |
| } |
| } |
| } catch (GenericEntityException e) { |
| Debug.logError(e, module); |
| return ServiceUtil.returnError(e.getMessage()); |
| } |
| |
| // filter out the old prices |
| prices = EntityUtil.filterByDate(prices); |
| |
| // first check for the AVERAGE_COST price type |
| List<GenericValue> pricesToUse = EntityUtil.filterByAnd(prices, UtilMisc.toMap("productPriceTypeId", "AVERAGE_COST")); |
| if (UtilValidate.isEmpty(pricesToUse)) { |
| // next go with default price |
| pricesToUse = EntityUtil.filterByAnd(prices, UtilMisc.toMap("productPriceTypeId", "DEFAULT_PRICE")); |
| if (UtilValidate.isEmpty(pricesToUse)) { |
| // finally use list price |
| pricesToUse = EntityUtil.filterByAnd(prices, UtilMisc.toMap("productPriceTypeId", "LIST_PRICE")); |
| } |
| } |
| |
| // use the most current price |
| GenericValue thisPrice = EntityUtil.getFirst(pricesToUse); |
| if (thisPrice != null) { |
| price = thisPrice.getBigDecimal("price"); |
| validPriceFound = true; |
| } |
| } |
| |
| result.put("price", price); |
| result.put("validPriceFound", Boolean.valueOf(validPriceFound)); |
| result.put("orderItemPriceInfos", orderItemPriceInfos); |
| return result; |
| } |
| } |