/* * 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.order.shoppingcart; import java.math.BigDecimal; import java.math.MathContext; import java.math.RoundingMode; import java.sql.Timestamp; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedHashSet; 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.UtilFormatOut; import org.apache.ofbiz.base.util.UtilGenerics; import org.apache.ofbiz.base.util.UtilMisc; import org.apache.ofbiz.base.util.UtilProperties; import org.apache.ofbiz.base.util.UtilValidate; import org.apache.ofbiz.entity.Delegator; import org.apache.ofbiz.entity.DelegatorFactory; import org.apache.ofbiz.entity.GenericEntityException; import org.apache.ofbiz.entity.GenericPK; import org.apache.ofbiz.entity.GenericValue; import org.apache.ofbiz.entity.condition.EntityCondition; import org.apache.ofbiz.entity.condition.EntityExpr; import org.apache.ofbiz.entity.condition.EntityOperator; import org.apache.ofbiz.entity.util.EntityQuery; import org.apache.ofbiz.entity.util.EntityUtil; import org.apache.ofbiz.entity.util.EntityUtilProperties; import org.apache.ofbiz.order.order.OrderReadHelper; import org.apache.ofbiz.order.shoppingcart.ShoppingCart.ShoppingCartItemGroup; import org.apache.ofbiz.order.shoppingcart.product.ProductPromoWorker; import org.apache.ofbiz.order.shoppinglist.ShoppingListEvents; import org.apache.ofbiz.product.catalog.CatalogWorker; import org.apache.ofbiz.product.category.CategoryWorker; import org.apache.ofbiz.product.config.ProductConfigWorker; import org.apache.ofbiz.product.config.ProductConfigWrapper; import org.apache.ofbiz.product.product.ProductContentWrapper; import org.apache.ofbiz.product.product.ProductWorker; import org.apache.ofbiz.product.store.ProductStoreWorker; import org.apache.ofbiz.service.GenericServiceException; import org.apache.ofbiz.service.LocalDispatcher; import org.apache.ofbiz.service.ModelService; import org.apache.ofbiz.service.ServiceUtil; /** * <p><b>Title:</b> ShoppingCartItem.java * <p><b>Description:</b> Shopping cart item object. */ @SuppressWarnings("serial") public class ShoppingCartItem implements java.io.Serializable { public static String module = ShoppingCartItem.class.getName(); public static final String resource = "OrderUiLabels"; public static final String resource_error = "OrderErrorUiLabels"; public static String[] attributeNames = { "shoppingListId", "shoppingListItemSeqId", "surveyResponses", "itemDesiredDeliveryDate", "itemComment", "fromInventoryItemId"}; public static final MathContext generalRounding = new MathContext(10); private transient Delegator delegator = null; /** the actual or variant product */ private transient GenericValue _product = null; /** the virtual product if _product is a variant */ private transient GenericValue _parentProduct = null; private String delegatorName = null; private String prodCatalogId = null; private String productId = null; private String supplierProductId = null; private String parentProductId = null; private String externalId = null; /** ends up in orderItemTypeId */ private String itemType = null; private ShoppingCart.ShoppingCartItemGroup itemGroup = null; private String productCategoryId = null; private String itemDescription = null; /** for reservations: date start*/ private Timestamp reservStart = null; /** for reservations: length */ private BigDecimal reservLength = BigDecimal.ZERO; /** for reservations: number of persons using */ private BigDecimal reservPersons = BigDecimal.ZERO; private String accommodationMapId = null; private String accommodationSpotId = null; private BigDecimal quantity = BigDecimal.ZERO; private BigDecimal basePrice = BigDecimal.ZERO; private BigDecimal displayPrice = null; private BigDecimal recurringBasePrice = null; private BigDecimal recurringDisplayPrice = null; /** comes from price calc, used for special promo price promotion action */ private BigDecimal specialPromoPrice = null; /** for reservations: extra % 2nd person */ private BigDecimal reserv2ndPPPerc = BigDecimal.ZERO; /** for reservations: extra % Nth person */ private BigDecimal reservNthPPPerc = BigDecimal.ZERO; private BigDecimal listPrice = BigDecimal.ZERO; /** flag to know if the price have been modified */ private boolean isModifiedPrice = false; private BigDecimal selectedAmount = BigDecimal.ZERO; private String requirementId = null; private String quoteId = null; private String quoteItemSeqId = null; // The following three optional fields are used to collect information for the OrderItemAssoc entity private String associatedOrderId = null; // the order Id, if any, to which the given item is associated (typically a sales order item can be associated to a purchase order item, for example in drop shipments) private String associatedOrderItemSeqId = null; // the order item Id, if any, to which the given item is associated private String orderItemAssocTypeId = "PURCHASE_ORDER"; // the type of association between this item and an external item; by default, for backward compatibility, a PURCHASE association is used (i.e. the extarnal order is a sales order and this item is a purchase order item created to fulfill the sales order item private String statusId = null; private Map<String, String> orderItemAttributes = null; private Map<String, Object> attributes = null; private String orderItemSeqId = null; private Locale locale = null; private Timestamp shipBeforeDate = null; private Timestamp shipAfterDate = null; private Timestamp estimatedShipDate = null; private Timestamp cancelBackOrderDate = null; private Map<String, String> contactMechIdsMap = new HashMap<String, String>(); private List<GenericValue> orderItemPriceInfos = null; private List<GenericValue> itemAdjustments = new LinkedList<GenericValue>(); private boolean isPromo = false; private BigDecimal promoQuantityUsed = BigDecimal.ZERO; private Map<GenericPK, BigDecimal> quantityUsedPerPromoCandidate = new HashMap<GenericPK, BigDecimal>(); private Map<GenericPK, BigDecimal> quantityUsedPerPromoFailed = new HashMap<GenericPK, BigDecimal>(); private Map<GenericPK, BigDecimal> quantityUsedPerPromoActual = new HashMap<GenericPK, BigDecimal>(); private Map<String, GenericValue> additionalProductFeatureAndAppls = new HashMap<String, GenericValue>(); private List<String> alternativeOptionProductIds = null; private ProductConfigWrapper configWrapper = null; private List<GenericValue> featuresForSupplier = new LinkedList<GenericValue>(); /** * Makes a ShoppingCartItem for a purchase order item and adds it to the cart. * NOTE: This method will get the product entity and check to make sure it can be purchased. * * @param cartLocation The location to place this item; null will place at the end * @param productId The primary key of the product being added * @param quantity The quantity to add * @param additionalProductFeatureAndAppls Product feature/appls map * @param attributes All unique attributes for this item (NOT features) * @param prodCatalogId The catalog this item was added from * @param configWrapper The product configuration wrapper (null if the product is not configurable) * @param dispatcher LocalDispatcher object for doing promotions, etc * @param cart The parent shopping cart object this item will belong to * @param supplierProduct GenericValue of SupplierProduct entity, containing product description and prices * @param shipBeforeDate Request that the shipment be made before this date * @param shipAfterDate Request that the shipment be made after this date * @param cancelBackOrderDate The date which if crossed causes order cancellation * @return a new ShoppingCartItem object * @throws CartItemModifyException */ public static ShoppingCartItem makePurchaseOrderItem(Integer cartLocation, String productId, BigDecimal selectedAmount, BigDecimal quantity, Map<String, GenericValue> additionalProductFeatureAndAppls, Map<String, Object> attributes, String prodCatalogId, ProductConfigWrapper configWrapper, String itemType, ShoppingCart.ShoppingCartItemGroup itemGroup, LocalDispatcher dispatcher, ShoppingCart cart, GenericValue supplierProduct, Timestamp shipBeforeDate, Timestamp shipAfterDate, Timestamp cancelBackOrderDate) throws CartItemModifyException, ItemNotFoundException { Delegator delegator = cart.getDelegator(); GenericValue product = null; try { product = EntityQuery.use(delegator).from("Product").where("productId", productId).cache().queryOne(); } catch (GenericEntityException e) { Debug.logWarning(e.toString(), module); } if (product == null) { Map<String, Object> messageMap = UtilMisc.<String, Object>toMap("productId", productId); String excMsg = UtilProperties.getMessage(resource_error, "item.product_not_found", messageMap , cart.getLocale()); Debug.logWarning(excMsg, module); throw new ItemNotFoundException(excMsg); } ShoppingCartItem newItem = new ShoppingCartItem(product, additionalProductFeatureAndAppls, attributes, prodCatalogId, configWrapper, cart.getLocale(), itemType, itemGroup, null); // check to see if product is virtual if ("Y".equals(product.getString("isVirtual"))) { Map<String, Object> messageMap = UtilMisc.<String, Object>toMap("productName", product.getString("productName"), "productId", product.getString("productId")); String excMsg = UtilProperties.getMessage(resource_error, "item.cannot_add_product_virtual", messageMap , cart.getLocale()); Debug.logWarning(excMsg, module); throw new CartItemModifyException(excMsg); } // check to see if the product is fully configured if ("AGGREGATED".equals(product.getString("productTypeId")) || "AGGREGATED_SERVICE".equals(product.getString("productTypeId"))) { if (configWrapper == null || !configWrapper.isCompleted()) { Map<String, Object> messageMap = UtilMisc.<String, Object>toMap("productName", product.getString("productName"), "productId", product.getString("productId")); String excMsg = UtilProperties.getMessage(resource_error, "item.cannot_add_product_not_configured_correctly", messageMap , cart.getLocale()); Debug.logWarning(excMsg, module); throw new CartItemModifyException(excMsg); } } // add to cart before setting quantity so that we can get order total, etc if (cartLocation == null) { cart.addItemToEnd(newItem); } else { cart.addItem(cartLocation.intValue(), newItem); } if (selectedAmount != null) { newItem.setSelectedAmount(selectedAmount); } // set the ship before/after/dates and cancel back order date. this needs to happen before setQuantity because setQuantity causes the ship group's dates to be // checked versus the cart item's newItem.setShipBeforeDate(shipBeforeDate != null ? shipBeforeDate : cart.getDefaultShipBeforeDate()); newItem.setShipAfterDate(shipAfterDate != null ? shipAfterDate : cart.getDefaultShipAfterDate()); newItem.setCancelBackOrderDate(cancelBackOrderDate != null ? cancelBackOrderDate : cart.getCancelBackOrderDate()); try { newItem.setQuantity(quantity, dispatcher, cart, true, false); cart.setItemShipGroupQty(newItem, quantity, 0); } catch (CartItemModifyException e) { cart.removeCartItem(cart.getItemIndex(newItem), dispatcher); cart.clearItemShipInfo(newItem); cart.removeEmptyCartItems(); throw e; } // specific for purchase orders - description is set to supplierProductId + supplierProductName, price set to lastPrice of SupplierProduct // if supplierProduct has no supplierProductName, use the regular supplierProductId if (supplierProduct != null) { newItem.setSupplierProductId(supplierProduct.getString("supplierProductId")); newItem.setName(getPurchaseOrderItemDescription(product, supplierProduct, cart.getLocale(), dispatcher)); newItem.setBasePrice(supplierProduct.getBigDecimal("lastPrice")); } else { newItem.setName(product.getString("internalName")); } return newItem; } /** * Makes a ShoppingCartItem and adds it to the cart. * NOTE: This method will get the product entity and check to make sure it can be purchased. * * @param cartLocation The location to place this item; null will place at the end * @param productId The primary key of the product being added * @param selectedAmount Optional. Defaults to 0.0. If a selectedAmount is needed (complements the quantity value), pass it in here. * @param quantity Required. The quantity to add. * @param unitPrice Optional. Defaults to 0.0, which causes calculation of price. * @param reservStart Optional. The start of the reservation. * @param reservLength Optional. The length of the reservation. * @param reservPersons Optional. The number of persons taking advantage of the reservation. * @param shipBeforeDate Optional. The date to ship the order by. * @param shipAfterDate Optional. Wait until this date to ship. * @param additionalProductFeatureAndAppls Optional. Product feature/appls map. * @param attributes Optional. All unique attributes for this item (NOT features). * @param prodCatalogId Optional, but strongly recommended. The catalog this item was added from. * @param configWrapper Optional. The product configuration wrapper (null if the product is not configurable). * @param itemType Optional. Specifies the type of cart item, corresponds to an OrderItemType and should be a valid orderItemTypeId. * @param itemGroup Optional. Specifies which item group in the cart this should belong to, if item groups are needed/desired. * @param dispatcher Required (for price calculation, promos, etc). LocalDispatcher object for doing promotions, etc. * @param cart Required. The parent shopping cart object this item will belong to. * @param triggerExternalOpsBool Optional. Defaults to true. Trigger external operations (like promotions and such)? * @param triggerPriceRulesBool Optional. Defaults to true. Trigger the price rules to calculate the price for this item? * * @return a new ShoppingCartItem object * @throws CartItemModifyException */ public static ShoppingCartItem makeItem(Integer cartLocation, String productId, BigDecimal selectedAmount, BigDecimal quantity, BigDecimal unitPrice, Timestamp reservStart, BigDecimal reservLength, BigDecimal reservPersons, Timestamp shipBeforeDate, Timestamp shipAfterDate, Map<String, GenericValue> additionalProductFeatureAndAppls, Map<String, Object> attributes, String prodCatalogId, ProductConfigWrapper configWrapper, String itemType, ShoppingCart.ShoppingCartItemGroup itemGroup, LocalDispatcher dispatcher, ShoppingCart cart, Boolean triggerExternalOpsBool, Boolean triggerPriceRulesBool, String parentProductId, Boolean skipInventoryChecks, Boolean skipProductChecks) throws CartItemModifyException, ItemNotFoundException { return makeItem(cartLocation,productId,selectedAmount,quantity,unitPrice, reservStart,reservLength,reservPersons,null,null,shipBeforeDate,shipAfterDate, additionalProductFeatureAndAppls,attributes,prodCatalogId,configWrapper, itemType,itemGroup,dispatcher,cart,triggerExternalOpsBool,triggerPriceRulesBool, parentProductId,skipInventoryChecks,skipProductChecks); } /** * Makes a ShoppingCartItem and adds it to the cart. * @param accommodationMapId Optional. reservations add into workeffort * @param accommodationSpotId Optional. reservations add into workeffort */ public static ShoppingCartItem makeItem(Integer cartLocation, String productId, BigDecimal selectedAmount, BigDecimal quantity, BigDecimal unitPrice, Timestamp reservStart, BigDecimal reservLength, BigDecimal reservPersons,String accommodationMapId,String accommodationSpotId, Timestamp shipBeforeDate, Timestamp shipAfterDate, Map<String, GenericValue> additionalProductFeatureAndAppls, Map<String, Object> attributes, String prodCatalogId, ProductConfigWrapper configWrapper, String itemType, ShoppingCart.ShoppingCartItemGroup itemGroup, LocalDispatcher dispatcher, ShoppingCart cart, Boolean triggerExternalOpsBool, Boolean triggerPriceRulesBool, String parentProductId, Boolean skipInventoryChecks, Boolean skipProductChecks) throws CartItemModifyException, ItemNotFoundException { Delegator delegator = cart.getDelegator(); GenericValue product = findProduct(delegator, skipProductChecks.booleanValue(), prodCatalogId, productId, cart.getLocale()); GenericValue parentProduct = null; if (parentProductId != null) { try { parentProduct = EntityQuery.use(delegator).from("Product").where("productId", parentProductId).cache().queryOne(); } catch (GenericEntityException e) { Debug.logWarning(e.toString(), module); } } return makeItem(cartLocation, product, selectedAmount, quantity, unitPrice, reservStart, reservLength, reservPersons, accommodationMapId, accommodationSpotId, shipBeforeDate, shipAfterDate, additionalProductFeatureAndAppls, attributes, prodCatalogId, configWrapper, itemType, itemGroup, dispatcher, cart, triggerExternalOpsBool, triggerPriceRulesBool, parentProduct, skipInventoryChecks, skipProductChecks); } /** * Makes a ShoppingCartItem and adds it to the cart. * WARNING: This method does not check if the product is in a purchase category. * rental fields were added. * * @param cartLocation The location to place this item; null will place at the end * @param product The product entity relating to the product being added * @param selectedAmount Optional. Defaults to 0.0. If a selectedAmount is needed (complements the quantity value), pass it in here. * @param quantity Required. The quantity to add. * @param unitPrice Optional. Defaults to 0.0, which causes calculation of price. * @param reservStart Optional. The start of the reservation. * @param reservLength Optional. The length of the reservation. * @param reservPersons Optional. The number of persons taking advantage of the reservation. * @param shipBeforeDate Optional. The date to ship the order by. * @param shipAfterDate Optional. Wait until this date to ship. * @param additionalProductFeatureAndAppls Optional. Product feature/appls map. * @param attributes Optional. All unique attributes for this item (NOT features). * @param prodCatalogId Optional, but strongly recommended. The catalog this item was added from. * @param configWrapper Optional. The product configuration wrapper (null if the product is not configurable). * @param itemType Optional. Specifies the type of cart item, corresponds to an OrderItemType and should be a valid orderItemTypeId. * @param itemGroup Optional. Specifies which item group in the cart this should belong to, if item groups are needed/desired. * @param dispatcher Required (for price calculation, promos, etc). LocalDispatcher object for doing promotions, etc. * @param cart Required. The parent shopping cart object this item will belong to. * @param triggerExternalOpsBool Optional. Defaults to true. Trigger external operations (like promotions and such)? * @param triggerPriceRulesBool Optional. Defaults to true. Trigger the price rules to calculate the price for this item? * * @return a new ShoppingCartItem object * @throws CartItemModifyException */ public static ShoppingCartItem makeItem(Integer cartLocation, GenericValue product, BigDecimal selectedAmount, BigDecimal quantity, BigDecimal unitPrice, Timestamp reservStart, BigDecimal reservLength, BigDecimal reservPersons, Timestamp shipBeforeDate, Timestamp shipAfterDate, Map<String, GenericValue> additionalProductFeatureAndAppls, Map<String, Object> attributes, String prodCatalogId, ProductConfigWrapper configWrapper, String itemType, ShoppingCart.ShoppingCartItemGroup itemGroup, LocalDispatcher dispatcher, ShoppingCart cart, Boolean triggerExternalOpsBool, Boolean triggerPriceRulesBool, GenericValue parentProduct, Boolean skipInventoryChecks, Boolean skipProductChecks) throws CartItemModifyException { return makeItem(cartLocation,product,selectedAmount, quantity,unitPrice,reservStart,reservLength,reservPersons, null,null,shipBeforeDate,shipAfterDate,additionalProductFeatureAndAppls,attributes, prodCatalogId,configWrapper,itemType,itemGroup,dispatcher,cart, triggerExternalOpsBool,triggerPriceRulesBool,parentProduct,skipInventoryChecks,skipProductChecks); } /** * Makes a ShoppingCartItem and adds it to the cart. * @param accommodationMapId Optional. reservations add into workeffort * @param accommodationSpotId Optional. reservations add into workeffort */ public static ShoppingCartItem makeItem(Integer cartLocation, GenericValue product, BigDecimal selectedAmount, BigDecimal quantity, BigDecimal unitPrice, Timestamp reservStart, BigDecimal reservLength, BigDecimal reservPersons, String accommodationMapId,String accommodationSpotId, Timestamp shipBeforeDate, Timestamp shipAfterDate, Map<String, GenericValue> additionalProductFeatureAndAppls, Map<String, Object> attributes, String prodCatalogId, ProductConfigWrapper configWrapper, String itemType, ShoppingCart.ShoppingCartItemGroup itemGroup, LocalDispatcher dispatcher, ShoppingCart cart, Boolean triggerExternalOpsBool, Boolean triggerPriceRulesBool, GenericValue parentProduct, Boolean skipInventoryChecks, Boolean skipProductChecks) throws CartItemModifyException { ShoppingCartItem newItem = new ShoppingCartItem(product, additionalProductFeatureAndAppls, attributes, prodCatalogId, configWrapper, cart.getLocale(), itemType, itemGroup, parentProduct); selectedAmount = selectedAmount == null ? BigDecimal.ZERO : selectedAmount; unitPrice = unitPrice == null ? BigDecimal.ZERO : unitPrice; reservLength = reservLength == null ? BigDecimal.ZERO : reservLength; reservPersons = reservPersons == null ? BigDecimal.ZERO : reservPersons; boolean triggerPriceRules = triggerPriceRulesBool == null ? true : triggerPriceRulesBool.booleanValue(); boolean triggerExternalOps = triggerExternalOpsBool == null ? true : triggerExternalOpsBool.booleanValue(); // check to see if product is virtual if ("Y".equals(product.getString("isVirtual"))) { Map<String, Object> messageMap = UtilMisc.<String, Object>toMap("productName", product.getString("productName"), "productId", product.getString("productId")); String excMsg = UtilProperties.getMessage(resource_error, "item.cannot_add_product_virtual", messageMap , cart.getLocale()); Debug.logWarning(excMsg, module); throw new CartItemModifyException(excMsg); } java.sql.Timestamp nowTimestamp = UtilDateTime.nowTimestamp(); if (!skipProductChecks.booleanValue()) { isValidCartProduct(configWrapper, product, nowTimestamp, cart.getLocale()); } // check to see if the product is a rental item if ("ASSET_USAGE".equals(product.getString("productTypeId")) || "ASSET_USAGE_OUT_IN".equals(product.getString("productTypeId"))) { if (reservStart == null) { String excMsg = UtilProperties.getMessage(resource_error, "item.missing_reservation_starting_date", cart.getLocale()); throw new CartItemModifyException(excMsg); } if (reservStart.before(UtilDateTime.nowTimestamp())) { String excMsg = UtilProperties.getMessage(resource_error, "item.reservation_from_tomorrow", cart.getLocale()); throw new CartItemModifyException(excMsg); } newItem.setReservStart(reservStart); if (reservLength.compareTo(BigDecimal.ONE) < 0) { String excMsg = UtilProperties.getMessage(resource_error, "item.number_of_days", cart.getLocale()); throw new CartItemModifyException(excMsg); } newItem.setReservLength(reservLength); if (product.get("reservMaxPersons") != null) { BigDecimal reservMaxPersons = product.getBigDecimal("reservMaxPersons"); if (reservMaxPersons.compareTo(reservPersons) < 0) { Map<String, Object> messageMap = UtilMisc.<String, Object>toMap("reservMaxPersons", product.getString("reservMaxPersons"), "reservPersons", reservPersons); String excMsg = UtilProperties.getMessage(resource_error, "item.maximum_number_of_person_renting", messageMap, cart.getLocale()); Debug.logInfo(excMsg,module); throw new CartItemModifyException(excMsg); } } newItem.setReservPersons(reservPersons); if (product.get("reserv2ndPPPerc") != null) newItem.setReserv2ndPPPerc(product.getBigDecimal("reserv2ndPPPerc")); if (product.get("reservNthPPPerc") != null) newItem.setReservNthPPPerc(product.getBigDecimal("reservNthPPPerc")); if ((accommodationMapId != null) && (accommodationSpotId != null)) { newItem.setAccommodationId(accommodationMapId,accommodationSpotId); } // check to see if the related fixed asset is available for rent String isAvailable = checkAvailability(product.getString("productId"), quantity, reservStart, reservLength, cart); if (isAvailable.compareTo("OK") != 0) { Map<String, Object> messageMap = UtilMisc.<String, Object>toMap("productId", product.getString("productId"), "availableMessage", isAvailable); String excMsg = UtilProperties.getMessage(resource_error, "item.product_not_available", messageMap, cart.getLocale()); Debug.logInfo(excMsg, module); throw new CartItemModifyException(isAvailable); } } // set the ship before and after dates (defaults to cart ship before/after dates) newItem.setShipBeforeDate(shipBeforeDate != null ? shipBeforeDate : cart.getDefaultShipBeforeDate()); newItem.setShipAfterDate(shipAfterDate != null ? shipAfterDate : cart.getDefaultShipAfterDate()); // set the product unit price as base price // if triggerPriceRules is true this price will be overriden newItem.setBasePrice(unitPrice); // add to cart before setting quantity so that we can get order total, etc if (cartLocation == null) { cart.addItemToEnd(newItem); } else { cart.addItem(cartLocation.intValue(), newItem); } // We have to set the selectedAmount before calling setQuantity because // selectedAmount changes the item's base price (used in the updatePrice // method called inside the setQuantity method) if (selectedAmount.compareTo(BigDecimal.ZERO) > 0) { newItem.setSelectedAmount(selectedAmount); } try { newItem.setQuantity(quantity, dispatcher, cart, triggerExternalOps, true, triggerPriceRules, skipInventoryChecks.booleanValue()); } catch (CartItemModifyException e) { Debug.logWarning(e.getMessage(), module); cart.removeCartItem(cart.getItemIndex(newItem), dispatcher); cart.clearItemShipInfo(newItem); cart.removeEmptyCartItems(); throw e; } return newItem; } public static GenericValue findProduct(Delegator delegator, boolean skipProductChecks, String prodCatalogId, String productId, Locale locale) throws CartItemModifyException, ItemNotFoundException { GenericValue product; try { product = EntityQuery.use(delegator).from("Product").where("productId", productId).cache().queryOne(); // first see if there is a purchase allow category and if this product is in it or not String purchaseProductCategoryId = CatalogWorker.getCatalogPurchaseAllowCategoryId(delegator, prodCatalogId); if (!skipProductChecks && product != null && purchaseProductCategoryId != null) { if (!CategoryWorker.isProductInCategory(delegator, product.getString("productId"), purchaseProductCategoryId)) { // a Purchase allow productCategoryId was found, but the product is not in the category, axe it... Debug.logWarning("Product [" + productId + "] is not in the purchase allow category [" + purchaseProductCategoryId + "] and cannot be purchased", module); product = null; } } } catch (GenericEntityException e) { Debug.logWarning(e.toString(), module); product = null; } if (product == null) { Map<String, Object> messageMap = UtilMisc.<String, Object>toMap("productId", productId); String excMsg = UtilProperties.getMessage(resource_error, "item.product_not_found", messageMap , locale); Debug.logWarning(excMsg, module); throw new ItemNotFoundException(excMsg); } return product; } public static void isValidCartProduct(ProductConfigWrapper configWrapper, GenericValue product, Timestamp nowTimestamp, Locale locale) throws CartItemModifyException { // check to see if introductionDate hasn't passed yet if (product.get("introductionDate") != null && nowTimestamp.before(product.getTimestamp("introductionDate"))) { Map<String, Object> messageMap = UtilMisc.<String, Object>toMap("productName", product.getString("productName"), "productId", product.getString("productId")); String excMsg = UtilProperties.getMessage(resource_error, "item.cannot_add_product_not_yet_available", messageMap , locale); Debug.logWarning(excMsg, module); throw new CartItemModifyException(excMsg); } // check to see if salesDiscontinuationDate has passed if (product.get("salesDiscontinuationDate") != null && nowTimestamp.after(product.getTimestamp("salesDiscontinuationDate"))) { Map<String, Object> messageMap = UtilMisc.<String, Object>toMap("productName", product.getString("productName"), "productId", product.getString("productId")); String excMsg = UtilProperties.getMessage(resource_error, "item.cannot_add_product_no_longer_available", messageMap , locale); Debug.logWarning(excMsg, module); throw new CartItemModifyException(excMsg); } // check to see if the product is fully configured if ("AGGREGATED".equals(product.getString("productTypeId"))|| "AGGREGATED_SERVICE".equals(product.getString("productTypeId"))) { if (configWrapper == null || !configWrapper.isCompleted()) { Map<String, Object> messageMap = UtilMisc.<String, Object>toMap("productName", product.getString("productName"), "productId", product.getString("productId")); String excMsg = UtilProperties.getMessage(resource_error, "item.cannot_add_product_not_configured_correctly", messageMap , locale); Debug.logWarning(excMsg, module); throw new CartItemModifyException(excMsg); } } } /** * Makes a non-product ShoppingCartItem and adds it to the cart. * NOTE: This is only for non-product items; items without a product entity (work items, bulk items, etc) * * @param cartLocation The location to place this item; null will place at the end * @param itemType The OrderItemTypeId for the item being added * @param itemDescription The optional description of the item * @param productCategoryId The optional category the product *will* go in * @param basePrice The price for this item * @param selectedAmount * @param quantity The quantity to add * @param attributes All unique attributes for this item (NOT features) * @param prodCatalogId The catalog this item was added from * @param dispatcher LocalDispatcher object for doing promotions, etc * @param cart The parent shopping cart object this item will belong to * @param triggerExternalOpsBool Indicates if we should run external operations (promotions, auto-save, etc) * @return a new ShoppingCartItem object * @throws CartItemModifyException */ public static ShoppingCartItem makeItem(Integer cartLocation, String itemType, String itemDescription, String productCategoryId, BigDecimal basePrice, BigDecimal selectedAmount, BigDecimal quantity, Map<String, Object> attributes, String prodCatalogId, ShoppingCart.ShoppingCartItemGroup itemGroup, LocalDispatcher dispatcher, ShoppingCart cart, Boolean triggerExternalOpsBool) throws CartItemModifyException { Delegator delegator = cart.getDelegator(); ShoppingCartItem newItem = new ShoppingCartItem(delegator, itemType, itemDescription, productCategoryId, basePrice, attributes, prodCatalogId, cart.getLocale(), itemGroup); // add to cart before setting quantity so that we can get order total, etc if (cartLocation == null) { cart.addItemToEnd(newItem); } else { cart.addItem(cartLocation.intValue(), newItem); } boolean triggerExternalOps = triggerExternalOpsBool == null ? true : triggerExternalOpsBool.booleanValue(); try { newItem.setQuantity(quantity, dispatcher, cart, triggerExternalOps); } catch (CartItemModifyException e) { cart.removeEmptyCartItems(); throw e; } if (selectedAmount != null) { newItem.setSelectedAmount(selectedAmount); } return newItem; } /** Clone an item. */ public ShoppingCartItem(ShoppingCartItem item) { this.delegator = item.getDelegator(); try { this._product = item.getProduct(); } catch (IllegalStateException e) { this._product = null; } try { this._parentProduct = item.getParentProduct(); } catch (IllegalStateException e) { this._parentProduct = null; } this.delegatorName = item.delegatorName; this.prodCatalogId = item.getProdCatalogId(); this.productId = item.getProductId(); this.supplierProductId = item.getSupplierProductId(); this.parentProductId = item.getParentProductId(); this.externalId = item.getExternalId(); this.itemType = item.getItemType(); this.itemGroup = item.getItemGroup(); this.productCategoryId = item.getProductCategoryId(); this.itemDescription = item.itemDescription; this.reservStart = item.getReservStart(); this.reservLength = item.getReservLength(); this.reservPersons = item.getReservPersons(); this.accommodationMapId = item.getAccommodationMapId(); this.accommodationSpotId = item.getAccommodationSpotId(); this.quantity = item.getQuantity(); this.setBasePrice(item.getBasePrice()); this.setDisplayPrice(item.getDisplayPrice()); this.setRecurringBasePrice(item.getRecurringBasePrice()); this.setRecurringDisplayPrice(item.getRecurringDisplayPrice()); this.setSpecialPromoPrice(item.getSpecialPromoPrice()); this.reserv2ndPPPerc = item.getReserv2ndPPPerc(); this.reservNthPPPerc = item.getReservNthPPPerc(); this.listPrice = item.getListPrice(); this.setIsModifiedPrice(item.getIsModifiedPrice()); this.selectedAmount = item.getSelectedAmount(); this.requirementId = item.getRequirementId(); this.quoteId = item.getQuoteId(); this.quoteItemSeqId = item.getQuoteItemSeqId(); this.associatedOrderId = item.getAssociatedOrderId(); this.associatedOrderItemSeqId = item.getAssociatedOrderItemSeqId(); this.orderItemAssocTypeId = item.getOrderItemAssocTypeId(); this.setStatusId(item.getStatusId()); if (UtilValidate.isEmpty(item.getOrderItemAttributes())) { this.orderItemAttributes = new HashMap<String, String>(); this.orderItemAttributes.putAll(item.getOrderItemAttributes()); } this.attributes = item.getAttributes() == null ? new HashMap<String, Object>() : new HashMap<String, Object>(item.getAttributes()); this.setOrderItemSeqId(item.getOrderItemSeqId()); this.locale = item.locale; this.setShipBeforeDate(item.getShipBeforeDate()); this.setShipAfterDate(item.getShipAfterDate()); this.setEstimatedShipDate(item.getEstimatedShipDate()); this.setCancelBackOrderDate(item.getCancelBackOrderDate()); this.contactMechIdsMap = item.getOrderItemContactMechIds() == null ? null : new HashMap<String, String>(item.getOrderItemContactMechIds()); this.orderItemPriceInfos = item.getOrderItemPriceInfos() == null ? null : new LinkedList<GenericValue>(item.getOrderItemPriceInfos()); this.itemAdjustments.addAll(item.getAdjustments()); this.isPromo = item.getIsPromo(); this.promoQuantityUsed = item.promoQuantityUsed; this.quantityUsedPerPromoCandidate = new HashMap<GenericPK, BigDecimal>(item.quantityUsedPerPromoCandidate); this.quantityUsedPerPromoFailed = new HashMap<GenericPK, BigDecimal>(item.quantityUsedPerPromoFailed); this.quantityUsedPerPromoActual = new HashMap<GenericPK, BigDecimal>(item.quantityUsedPerPromoActual); this.additionalProductFeatureAndAppls = item.getAdditionalProductFeatureAndAppls() == null ? null : new HashMap<String, GenericValue>(item.getAdditionalProductFeatureAndAppls()); if (item.getAlternativeOptionProductIds() != null) { List<String> tempAlternativeOptionProductIds = new LinkedList<String>(); tempAlternativeOptionProductIds.addAll(item.getAlternativeOptionProductIds()); this.setAlternativeOptionProductIds(tempAlternativeOptionProductIds); } if (item.configWrapper != null) { this.configWrapper = new ProductConfigWrapper(item.configWrapper); } this.featuresForSupplier.addAll(item.featuresForSupplier); } /** Cannot create shopping cart item with no parameters */ protected ShoppingCartItem() {} /** Creates new ShoppingCartItem object. * @deprecated Use {@link #ShoppingCartItem(GenericValue, Map, Map, String, Locale, String, ShoppingCartItemGroup, LocalDispatcher)} instead*/ protected ShoppingCartItem(GenericValue product, Map<String, GenericValue> additionalProductFeatureAndAppls, Map<String, Object> attributes, String prodCatalogId, Locale locale, String itemType, ShoppingCart.ShoppingCartItemGroup itemGroup) { this(product, additionalProductFeatureAndAppls, attributes, prodCatalogId, locale, itemType, itemGroup, null); } /** Creates new ShoppingCartItem object. * @param dispatcher TODO*/ protected ShoppingCartItem(GenericValue product, Map<String, GenericValue> additionalProductFeatureAndAppls, Map<String, Object> attributes, String prodCatalogId, Locale locale, String itemType, ShoppingCart.ShoppingCartItemGroup itemGroup, LocalDispatcher dispatcher) { this(product, additionalProductFeatureAndAppls, attributes, prodCatalogId, null, locale, itemType, itemGroup, null); if (product != null) { String productName = ProductContentWrapper.getProductContentAsText(product, "PRODUCT_NAME", this.locale, dispatcher, "html"); // if the productName is null or empty, see if there is an associated virtual product and get the productName of that product if (UtilValidate.isEmpty(productName)) { GenericValue parentProduct = this.getParentProduct(); if (parentProduct != null) { productName = ProductContentWrapper.getProductContentAsText(parentProduct, "PRODUCT_NAME", this.locale, dispatcher, "html"); } } if (productName == null) { this.itemDescription= ""; } else { this.itemDescription= productName; } } } /** Creates new ShoppingCartItem object. */ protected ShoppingCartItem(GenericValue product, Map<String, GenericValue> additionalProductFeatureAndAppls, Map<String, Object> attributes, String prodCatalogId, ProductConfigWrapper configWrapper, Locale locale, String itemType, ShoppingCart.ShoppingCartItemGroup itemGroup, GenericValue parentProduct) { this._product = product; this.productId = _product.getString("productId"); this._parentProduct = parentProduct; if (parentProduct != null) this.parentProductId = _parentProduct.getString("productId"); if (UtilValidate.isEmpty(itemType)) { if (UtilValidate.isNotEmpty(_product.getString("productTypeId"))) { if ("ASSET_USAGE".equals(_product.getString("productTypeId"))) { this.itemType = "RENTAL_ORDER_ITEM"; // will create additional workeffort/asset usage records } else if ("ASSET_USAGE_OUT_IN".equals(_product.getString("productTypeId"))) { this.itemType = "RENTAL_ORDER_ITEM"; } else { this.itemType = "PRODUCT_ORDER_ITEM"; } } else { // NOTE DEJ20100111: it seems safe to assume here that because a product is passed in that even if the product has no type this type of item still applies; thanks to whoever wrote the previous code, that's a couple of hours tracking this down that I wouldn't have minded doing something else with... :) this.itemType = "PRODUCT_ORDER_ITEM"; } } else { this.itemType = itemType; } this.itemGroup = itemGroup; this.prodCatalogId = prodCatalogId; this.attributes = (attributes == null ? new HashMap<String, Object>() : attributes); this.delegator = _product.getDelegator(); this.delegatorName = _product.getDelegator().getDelegatorName(); this.addAllProductFeatureAndAppls(additionalProductFeatureAndAppls); this.locale = locale; if (UtilValidate.isNotEmpty(configWrapper)) { this.configWrapper = configWrapper; if (UtilValidate.isEmpty(configWrapper.getConfigId())) { //new product configuration. Persist it ProductConfigWorker.storeProductConfigWrapper(configWrapper, getDelegator()); } } } /** Creates new ShopingCartItem object. */ protected ShoppingCartItem(Delegator delegator, String itemTypeId, String description, String categoryId, BigDecimal basePrice, Map<String, Object> attributes, String prodCatalogId, Locale locale, ShoppingCart.ShoppingCartItemGroup itemGroup) { this.delegator = delegator; this.itemType = itemTypeId; this.itemGroup = itemGroup; this.itemDescription = description; this.productCategoryId = categoryId; if (basePrice != null) { this.setBasePrice(basePrice); this.setDisplayPrice(basePrice); } this.attributes = (attributes == null ? new HashMap<String, Object>() : attributes); this.prodCatalogId = prodCatalogId; this.delegatorName = delegator.getDelegatorName(); this.locale = locale; } public String getProdCatalogId() { return this.prodCatalogId; } public void setExternalId(String externalId) { this.externalId = externalId; } public String getExternalId() { return this.externalId; } /** Sets the user selected amount */ public void setSelectedAmount(BigDecimal selectedAmount) { this.selectedAmount = selectedAmount; } /** Returns the user selected amount */ public BigDecimal getSelectedAmount() { return this.selectedAmount; } /** Sets the base price for the item; use with caution */ public void setBasePrice(BigDecimal basePrice) { this.basePrice = basePrice; } /** Sets the display price for the item; use with caution */ public void setDisplayPrice(BigDecimal displayPrice) { this.displayPrice = displayPrice; } /** Sets the base price for the item; use with caution */ public void setRecurringBasePrice(BigDecimal recurringBasePrice) { this.recurringBasePrice = recurringBasePrice; } /** Sets the display price for the item; use with caution */ public void setRecurringDisplayPrice(BigDecimal recurringDisplayPrice) { this.recurringDisplayPrice = recurringDisplayPrice; } public void setSpecialPromoPrice(BigDecimal specialPromoPrice) { this.specialPromoPrice = specialPromoPrice; } /** Sets the extra % for second person */ public void setReserv2ndPPPerc(BigDecimal reserv2ndPPPerc) { this.reserv2ndPPPerc = reserv2ndPPPerc; } /** Sets the extra % for third and following person */ public void setReservNthPPPerc(BigDecimal reservNthPPPerc) { this.reservNthPPPerc = reservNthPPPerc; } /** Sets the reservation start date */ public void setReservStart(Timestamp reservStart) { this.reservStart = reservStart; } /** Sets the reservation length */ public void setReservLength(BigDecimal reservLength) { this.reservLength = reservLength; } /** Sets number of persons using the reservation */ public void setReservPersons(BigDecimal reservPersons) { this.reservPersons = reservPersons; } /** Sets accommodationId using the reservation */ public void setAccommodationId(String accommodationMapId,String accommodationSpotId) { this.accommodationMapId = accommodationMapId; this.accommodationSpotId = accommodationSpotId; } /** Sets the quantity for the item and validates the change in quantity, etc */ public void setQuantity(BigDecimal quantity, LocalDispatcher dispatcher, ShoppingCart cart) throws CartItemModifyException { this.setQuantity(quantity, dispatcher, cart, true); } /** Sets the quantity for the item and validates the change in quantity, etc */ public void setQuantity(BigDecimal quantity, LocalDispatcher dispatcher, ShoppingCart cart, boolean triggerExternalOps) throws CartItemModifyException { this.setQuantity(quantity, dispatcher, cart, triggerExternalOps, true); } /** Sets the quantity for the item and validates the change in quantity, etc */ public void setQuantity(BigDecimal quantity, LocalDispatcher dispatcher, ShoppingCart cart, boolean triggerExternalOps, boolean resetShipGroup) throws CartItemModifyException { this.setQuantity(quantity, dispatcher, cart, triggerExternalOps, resetShipGroup, true, false); } /** Sets the quantity for the item and validates the change in quantity, etc */ public void setQuantity(BigDecimal quantity, LocalDispatcher dispatcher, ShoppingCart cart, boolean triggerExternalOps, boolean resetShipGroup, boolean updateProductPrice) throws CartItemModifyException { this.setQuantity(quantity, dispatcher, cart, triggerExternalOps, resetShipGroup, updateProductPrice, false); } /** returns "OK" when the product can be booked or returns a string with the dates the related fixed Asset is not available */ public static String checkAvailability(String productId, BigDecimal quantity, Timestamp reservStart, BigDecimal reservLength, ShoppingCart cart) { Delegator delegator = cart.getDelegator(); // find related fixedAsset List<GenericValue> selFixedAssetProduct = null; GenericValue fixedAssetProduct = null; try { selFixedAssetProduct = EntityQuery.use(delegator).from("FixedAssetProduct").where("productId", productId, "fixedAssetProductTypeId", "FAPT_USE").filterByDate(UtilDateTime.nowTimestamp(), "fromDate", "thruDate").queryList(); } catch (GenericEntityException e) { Map<String, Object> messageMap = UtilMisc.<String, Object>toMap("productId", productId); String msg = UtilProperties.getMessage(resource_error, "item.cannot_find_Fixed_Asset", messageMap , cart.getLocale()); return msg; } if (UtilValidate.isNotEmpty(selFixedAssetProduct)) { Iterator<GenericValue> firstOne = selFixedAssetProduct.iterator(); fixedAssetProduct = firstOne.next(); } else { Map<String, Object> messageMap = UtilMisc.<String, Object>toMap("productId", productId); String msg = UtilProperties.getMessage(resource_error, "item.cannot_find_Fixed_Asset", messageMap , cart.getLocale()); return msg; } // find the fixed asset itself GenericValue fixedAsset = null; try { fixedAsset = fixedAssetProduct.getRelatedOne("FixedAsset", false); } catch (GenericEntityException e) { Map<String, Object> messageMap = UtilMisc.<String, Object>toMap("fixedAssetId", fixedAssetProduct.getString("fixedAssetId")); String msg = UtilProperties.getMessage(resource_error, "item.fixed_Asset_not_found", messageMap , cart.getLocale()); return msg; } if (fixedAsset == null) { Map<String, Object> messageMap = UtilMisc.<String, Object>toMap("fixedAssetId", fixedAssetProduct.getString("fixedAssetId")); String msg = UtilProperties.getMessage(resource_error, "item.fixed_Asset_not_found", messageMap , cart.getLocale()); return msg; } // see if this fixed asset has a calendar, when no create one and attach to fixed asset // DEJ20050725 this isn't being used anywhere, commenting out for now and not assigning from the getRelatedOne: GenericValue techDataCalendar = null; GenericValue techDataCalendar = null; try { techDataCalendar = fixedAsset.getRelatedOne("TechDataCalendar", false); } catch (GenericEntityException e) { Debug.logWarning(e, module); } if (techDataCalendar == null) { // no calendar ok, when not more that total capacity if (fixedAsset.getBigDecimal("productionCapacity").compareTo(quantity) >= 0) { String msg = UtilProperties.getMessage(resource_error, "item.availableOk", cart.getLocale()); return msg; } else { Map<String, Object> messageMap = UtilMisc.<String, Object>toMap("quantityReq", quantity, "quantityAvail", fixedAsset.getString("productionCapacity")); String msg = UtilProperties.getMessage(resource_error, "item.availableQnt", messageMap , cart.getLocale()); return msg; } } // now find all the dates and check the availabilty for each date // please note that calendarId is the same for (TechData)Calendar, CalendarExcDay and CalendarExWeek long dayCount = 0; String resultMessage = ""; while (BigDecimal.valueOf(dayCount).compareTo(reservLength) < 0) { GenericValue techDataCalendarExcDay = null; // find an existing Day exception record Timestamp exceptionDateStartTime = new Timestamp((reservStart.getTime() + (dayCount++ * 86400000))); try { techDataCalendarExcDay = EntityQuery.use(delegator).from("TechDataCalendarExcDay").where("calendarId", fixedAsset.get("calendarId"), "exceptionDateStartTime", exceptionDateStartTime).queryOne(); } catch (GenericEntityException e) { Debug.logWarning(e, module); } if (techDataCalendarExcDay == null) { if (fixedAsset.get("productionCapacity") != null && fixedAsset.getBigDecimal("productionCapacity").compareTo(quantity) < 0) resultMessage = resultMessage.concat(exceptionDateStartTime.toString().substring(0, 10) + ", "); } else { // see if we can get the number of assets available // first try techDataCalendarExcDay(exceptionCapacity) and then FixedAsset(productionCapacity) // if still zero, do not check availability BigDecimal exceptionCapacity = BigDecimal.ZERO; if (techDataCalendarExcDay.get("exceptionCapacity") != null) exceptionCapacity = techDataCalendarExcDay.getBigDecimal("exceptionCapacity"); if (exceptionCapacity.compareTo(BigDecimal.ZERO) == 0 && fixedAsset.get("productionCapacity") != null) exceptionCapacity = fixedAsset.getBigDecimal("productionCapacity"); if (exceptionCapacity.compareTo(BigDecimal.ZERO) != 0) { BigDecimal usedCapacity = BigDecimal.ZERO; if (techDataCalendarExcDay.get("usedCapacity") != null) usedCapacity = techDataCalendarExcDay.getBigDecimal("usedCapacity"); if (exceptionCapacity.compareTo(quantity.add(usedCapacity)) < 0) { resultMessage = resultMessage.concat(exceptionDateStartTime.toString().substring(0, 10) + ", "); Debug.logInfo("No rental fixed Asset available: " + exceptionCapacity + " already used: " + usedCapacity + " Requested now: " + quantity, module); } } } } if (resultMessage.compareTo("") == 0) { String msg = UtilProperties.getMessage(resource_error, "item.availableOk", cart.getLocale()); return msg; } else { Map<String, Object> messageMap = UtilMisc.<String, Object>toMap("resultMessage", resultMessage); String msg = UtilProperties.getMessage(resource_error, "item.notAvailable", messageMap, cart.getLocale()); return msg; } } protected boolean isInventoryAvailableOrNotRequired(BigDecimal quantity, String productStoreId, LocalDispatcher dispatcher) throws CartItemModifyException { boolean inventoryAvailable = true; try { Map<String, Object> invReqResult = dispatcher.runSync("isStoreInventoryAvailableOrNotRequired", UtilMisc.<String, Object>toMap("productStoreId", productStoreId, "productId", productId, "product", this.getProduct(), "quantity", quantity)); if (ServiceUtil.isError(invReqResult)) { Debug.logError("Error calling isStoreInventoryAvailableOrNotRequired service, result is: " + invReqResult, module); throw new CartItemModifyException((String) invReqResult.get(ModelService.ERROR_MESSAGE)); } inventoryAvailable = "Y".equals(invReqResult.get("availableOrNotRequired")); } catch (GenericServiceException e) { String errMsg = "Fatal error calling inventory checking services: " + e.toString(); Debug.logError(e, errMsg, module); throw new CartItemModifyException(errMsg); } return inventoryAvailable; } protected void setQuantity(BigDecimal quantity, LocalDispatcher dispatcher, ShoppingCart cart, boolean triggerExternalOps, boolean resetShipGroup, boolean updateProductPrice, boolean skipInventoryChecks) throws CartItemModifyException { if (this.quantity.compareTo(quantity) == 0) { return; } if (this.isPromo) { Map<String, Object> messageMap = UtilMisc.<String, Object>toMap("productName", this.getName(dispatcher), "productId", productId); String excMsg = UtilProperties.getMessage(resource, "OrderCannotChangeQuantityInPromotion", messageMap , cart.getLocale()); throw new CartItemModifyException(excMsg); } // needed for inventory checking and auto-save String productStoreId = cart.getProductStoreId(); if (!skipInventoryChecks && !"PURCHASE_ORDER".equals(cart.getOrderType())) { // check inventory if new quantity is greater than old quantity; don't worry about inventory getting pulled out from under, that will be handled at checkout time if (_product != null && quantity.compareTo(this.quantity) > 0) { if (!isInventoryAvailableOrNotRequired(quantity, productStoreId, dispatcher)) { Map<String, Object> messageMap = UtilMisc.<String, Object>toMap("requestedQuantity", UtilFormatOut.formatQuantity(quantity.doubleValue()), "productName",this.getName(dispatcher), "productId", productId); String excMsg = UtilProperties.getMessage(resource, "OrderDoNotHaveEnoughProducts", messageMap , cart.getLocale()); Debug.logWarning(excMsg, module); throw new CartItemModifyException(excMsg); } } } // set quantity before promos so order total, etc will be updated this.quantity = quantity; if (updateProductPrice) { this.updatePrice(dispatcher, cart); } // apply/unapply promotions if (triggerExternalOps) { ProductPromoWorker.doPromotions(cart, dispatcher); } if (!"PURCHASE_ORDER".equals(cart.getOrderType())) { // store the auto-save cart if (triggerExternalOps && ProductStoreWorker.autoSaveCart(delegator, productStoreId)) { try { ShoppingListEvents.fillAutoSaveList(cart, dispatcher); } catch (GeneralException e) { Debug.logWarning(e, UtilProperties.getMessage(resource_error,"OrderUnableToStoreAutoSaveCart", locale)); } } } // set the item ship group if (resetShipGroup) { int itemId = cart.getItemIndex(this); int shipGroupIndex = 0; if (itemId != -1) { shipGroupIndex = cart.getItemShipGroupIndex(itemId); } cart.clearItemShipInfo(this); cart.setItemShipGroupQty(this, quantity, shipGroupIndex); } } public void updatePrice(LocalDispatcher dispatcher, ShoppingCart cart) throws CartItemModifyException { // set basePrice using the calculateProductPrice service if (_product != null && isModifiedPrice == false) { try { Map<String, Object> priceContext = new HashMap<String, Object>(); String partyId = cart.getPartyId(); if (partyId != null) { priceContext.put("partyId", partyId); } // check alternative packaging boolean isAlternativePacking = ProductWorker.isAlternativePacking(delegator, this.productId , this.getParentProductId()); BigDecimal pieces = BigDecimal.ONE; if(isAlternativePacking && UtilValidate.isNotEmpty(this.getParentProduct())){ GenericValue originalProduct = this.getParentProduct(); if (originalProduct != null) pieces = new BigDecimal(originalProduct.getLong("piecesIncluded")); priceContext.put("product", originalProduct); this._parentProduct = null; }else{ priceContext.put("product", this.getProduct()); } priceContext.put("quantity", this.getQuantity()); priceContext.put("amount", this.getSelectedAmount()); if (cart.getOrderType().equals("PURCHASE_ORDER")) { priceContext.put("currencyUomId", cart.getCurrency()); Map<String, Object> priceResult = dispatcher.runSync("calculatePurchasePrice", priceContext); if (ServiceUtil.isError(priceResult)) { throw new CartItemModifyException("There was an error while calculating the price: " + ServiceUtil.getErrorMessage(priceResult)); } Boolean validPriceFound = (Boolean) priceResult.get("validPriceFound"); if (!validPriceFound.booleanValue()) { throw new CartItemModifyException("Could not find a valid price for the product with ID [" + this.getProductId() + "] and supplier with ID [" + partyId + "], not adding to cart."); } if(isAlternativePacking){ this.setBasePrice(((BigDecimal) priceResult.get("price")).divide(pieces, RoundingMode.HALF_UP)); }else{ this.setBasePrice(((BigDecimal) priceResult.get("price"))); } this.setDisplayPrice(this.basePrice); this.orderItemPriceInfos = UtilGenerics.checkList(priceResult.get("orderItemPriceInfos")); } else { if (productId != null) { String productStoreId = cart.getProductStoreId(); List<GenericValue> productSurvey = ProductStoreWorker.getProductSurveys(delegator, productStoreId, productId, "CART_ADD", parentProductId); if (UtilValidate.isNotEmpty(productSurvey) && UtilValidate.isNotEmpty(attributes)) { List<String> surveyResponses = UtilGenerics.checkList(attributes.get("surveyResponses")); if (UtilValidate.isNotEmpty(surveyResponses)) { for (String surveyResponseId : surveyResponses) { // TODO: implement multiple survey per product if (UtilValidate.isNotEmpty(surveyResponseId)) { priceContext.put("surveyResponseId", surveyResponseId); break; } } } } } if ("true".equals(EntityUtilProperties.getPropertyValue("catalog", "convertProductPriceCurrency", delegator))){ priceContext.put("currencyUomIdTo", cart.getCurrency()); } else { priceContext.put("currencyUomId", cart.getCurrency()); } priceContext.put("prodCatalogId", this.getProdCatalogId()); priceContext.put("webSiteId", cart.getWebSiteId()); priceContext.put("productStoreId", cart.getProductStoreId()); priceContext.put("agreementId", cart.getAgreementId()); priceContext.put("productPricePurposeId", "PURCHASE"); priceContext.put("checkIncludeVat", "Y"); // check if a survey is associated with the item and add to the price calculation List<String> surveyResponses = UtilGenerics.checkList(getAttribute("surveyResponses")); if (UtilValidate.isNotEmpty(surveyResponses)) { priceContext.put("surveyResponseId", surveyResponses.get(0)); } Map<String, Object> priceResult = dispatcher.runSync("calculateProductPrice", priceContext); if (ServiceUtil.isError(priceResult)) { throw new CartItemModifyException("There was an error while calculating the price: " + ServiceUtil.getErrorMessage(priceResult)); } Boolean validPriceFound = (Boolean) priceResult.get("validPriceFound"); if (Boolean.FALSE.equals(validPriceFound)) { throw new CartItemModifyException("Could not find a valid price for the product with ID [" + this.getProductId() + "], not adding to cart."); } //set alternative product price if(isAlternativePacking){ int decimals = 2; if (priceResult.get("listPrice") != null) { this.listPrice = ((BigDecimal) priceResult.get("listPrice")).divide(pieces, decimals, RoundingMode.HALF_UP); } if (priceResult.get("basePrice") != null) { this.setBasePrice(((BigDecimal) priceResult.get("basePrice")).divide(pieces, decimals, RoundingMode.HALF_UP)); } if (priceResult.get("price") != null) { this.setDisplayPrice(((BigDecimal) priceResult.get("price")).divide(pieces, decimals, RoundingMode.HALF_UP)); } if (priceResult.get("specialPromoPrice") != null) { this.setSpecialPromoPrice(((BigDecimal) priceResult.get("specialPromoPrice")).divide(pieces, decimals, RoundingMode.HALF_UP)); } }else{ if (priceResult.get("listPrice") != null) { this.listPrice = ((BigDecimal) priceResult.get("listPrice")); } if (priceResult.get("basePrice") != null) { this.setBasePrice(((BigDecimal) priceResult.get("basePrice"))); } if (priceResult.get("price") != null) { this.setDisplayPrice(((BigDecimal) priceResult.get("price"))); } this.setSpecialPromoPrice((BigDecimal) priceResult.get("specialPromoPrice")); } this.orderItemPriceInfos = UtilGenerics.checkList(priceResult.get("orderItemPriceInfos")); // If product is configurable, the price is taken from the configWrapper. if (configWrapper != null) { // TODO: for configurable products need to do something to make them VAT aware... for now base and display prices are the same this.setBasePrice(configWrapper.getTotalPrice()); // Check if price display with taxes GenericValue productStore = ProductStoreWorker.getProductStore(cart.getProductStoreId(), delegator); if (productStore != null && "Y".equals(productStore.get("showPricesWithVatTax"))) { BigDecimal totalPrice = configWrapper.getTotalPrice(); // Get Taxes Map<String, Object> totalPriceWithTaxMap = dispatcher.runSync("calcTaxForDisplay", UtilMisc.toMap("basePrice", totalPrice, "productId", this.productId, "productStoreId", cart.getProductStoreId())); this.setDisplayPrice((BigDecimal) totalPriceWithTaxMap.get("priceWithTax")); } else { this.setDisplayPrice(configWrapper.getTotalPrice()); } } // no try to do a recurring price calculation; not all products have recurring prices so may be null Map<String, Object> recurringPriceContext = new HashMap<String, Object>(); recurringPriceContext.putAll(priceContext); recurringPriceContext.put("productPricePurposeId", "RECURRING_CHARGE"); Map<String, Object> recurringPriceResult = dispatcher.runSync("calculateProductPrice", recurringPriceContext); if (ServiceUtil.isError(recurringPriceResult)) { throw new CartItemModifyException("There was an error while calculating the price: " + ServiceUtil.getErrorMessage(recurringPriceResult)); } // for the recurring price only set the values iff validPriceFound is true Boolean validRecurringPriceFound = (Boolean) recurringPriceResult.get("validPriceFound"); if (Boolean.TRUE.equals(validRecurringPriceFound)) { if (recurringPriceResult.get("basePrice") != null) { this.setRecurringBasePrice((BigDecimal) recurringPriceResult.get("basePrice")); } if (recurringPriceResult.get("price") != null) { this.setRecurringDisplayPrice((BigDecimal) recurringPriceResult.get("price")); } } } } catch (GenericServiceException e) { throw new CartItemModifyException("There was an error while calculating the price", e); } } } /** Returns the quantity. */ public BigDecimal getQuantity() { return this.quantity; } /** Returns the reservation start date. */ public Timestamp getReservStart() { return this.getReservStart(BigDecimal.ZERO); } /** Returns the reservation start date with a number of days added. */ public Timestamp getReservStart(BigDecimal addDays) { if (addDays.compareTo(BigDecimal.ZERO) == 0) return this.reservStart; else { if (this.reservStart != null) return new Timestamp((long)(this.reservStart.getTime() + (addDays.doubleValue() * 86400000.0))); else return null; } } /** Returns the reservation length. */ public BigDecimal getReservLength() { return this.reservLength; } /** Returns the reservation number of persons. */ public BigDecimal getReservPersons() { return this.reservPersons; } /** Returns accommodationMapId */ public String getAccommodationMapId() { return this.accommodationMapId; } /** Returns accommodationSpotId */ public String getAccommodationSpotId() { return this.accommodationSpotId; } public BigDecimal getPromoQuantityUsed() { if (this.getIsPromo()) { return this.quantity; } else { return this.promoQuantityUsed; } } public BigDecimal getPromoQuantityAvailable() { if (this.getIsPromo()) { return BigDecimal.ZERO; } else { return this.quantity.subtract(this.promoQuantityUsed); } } public Iterator<Map.Entry<GenericPK, BigDecimal>> getQuantityUsedPerPromoActualIter() { return this.quantityUsedPerPromoActual.entrySet().iterator(); } public Iterator<Map.Entry<GenericPK, BigDecimal>> getQuantityUsedPerPromoCandidateIter() { return this.quantityUsedPerPromoCandidate.entrySet().iterator(); } public Iterator<Map.Entry<GenericPK, BigDecimal>> getQuantityUsedPerPromoFailedIter() { return this.quantityUsedPerPromoFailed.entrySet().iterator(); } public synchronized BigDecimal addPromoQuantityCandidateUse(BigDecimal quantityDesired, GenericValue productPromoCondAction, boolean checkAvailableOnly) { if (quantityDesired.compareTo(BigDecimal.ZERO) == 0) return BigDecimal.ZERO; BigDecimal promoQuantityAvailable = this.getPromoQuantityAvailable(); BigDecimal promoQuantityToUse = quantityDesired; if (promoQuantityAvailable.compareTo(BigDecimal.ZERO) > 0) { if (promoQuantityToUse.compareTo(promoQuantityAvailable) > 0) { promoQuantityToUse = promoQuantityAvailable; } if (!checkAvailableOnly) { // keep track of candidate promo uses on cartItem GenericPK productPromoCondActionPK = productPromoCondAction.getPrimaryKey(); BigDecimal existingValue = this.quantityUsedPerPromoCandidate.get(productPromoCondActionPK); if (existingValue == null) { this.quantityUsedPerPromoCandidate.put(productPromoCondActionPK, promoQuantityToUse); } else { this.quantityUsedPerPromoCandidate.put(productPromoCondActionPK, promoQuantityToUse.add(existingValue)); } this.promoQuantityUsed = this.promoQuantityUsed.add(promoQuantityToUse); } return promoQuantityToUse; } else { return BigDecimal.ZERO; } } public BigDecimal getPromoQuantityCandidateUse(GenericValue productPromoCondAction) { GenericPK productPromoCondActionPK = productPromoCondAction.getPrimaryKey(); BigDecimal existingValue = this.quantityUsedPerPromoCandidate.get(productPromoCondActionPK); if (existingValue == null) { return BigDecimal.ZERO; } else { return existingValue; } } public BigDecimal getPromoQuantityCandidateUseActionAndAllConds(GenericValue productPromoAction) { BigDecimal totalUse = BigDecimal.ZERO; String productPromoId = productPromoAction.getString("productPromoId"); String productPromoRuleId = productPromoAction.getString("productPromoRuleId"); GenericPK productPromoActionPK = productPromoAction.getPrimaryKey(); BigDecimal existingValue = this.quantityUsedPerPromoCandidate.get(productPromoActionPK); if (existingValue != null) { totalUse = existingValue; } for (Map.Entry<GenericPK, BigDecimal> entry : this.quantityUsedPerPromoCandidate.entrySet()) { GenericPK productPromoCondActionPK = entry.getKey(); BigDecimal quantityUsed = entry.getValue(); if (quantityUsed != null) { // must be in the same rule and be a condition if (productPromoId.equals(productPromoCondActionPK.getString("productPromoId")) && productPromoRuleId.equals(productPromoCondActionPK.getString("productPromoRuleId")) && productPromoCondActionPK.containsKey("productPromoCondSeqId")) { totalUse = totalUse.add(quantityUsed); } } } return totalUse; } public synchronized void resetPromoRuleUse(String productPromoId, String productPromoRuleId) { Iterator<Map.Entry<GenericPK, BigDecimal>> entryIter = this.quantityUsedPerPromoCandidate.entrySet().iterator(); while (entryIter.hasNext()) { Map.Entry<GenericPK, BigDecimal> entry = entryIter.next(); GenericPK productPromoCondActionPK = entry.getKey(); BigDecimal quantityUsed = entry.getValue(); if (productPromoId.equals(productPromoCondActionPK.getString("productPromoId")) && productPromoRuleId.equals(productPromoCondActionPK.getString("productPromoRuleId"))) { entryIter.remove(); BigDecimal existingValue = this.quantityUsedPerPromoFailed.get(productPromoCondActionPK); if (existingValue == null) { this.quantityUsedPerPromoFailed.put(productPromoCondActionPK, quantityUsed); } else { this.quantityUsedPerPromoFailed.put(productPromoCondActionPK, quantityUsed.add(existingValue)); } this.promoQuantityUsed = this.promoQuantityUsed.subtract(quantityUsed); } } } public synchronized void confirmPromoRuleUse(String productPromoId, String productPromoRuleId) { Iterator<Map.Entry<GenericPK, BigDecimal>> entryIter = this.quantityUsedPerPromoCandidate.entrySet().iterator(); while (entryIter.hasNext()) { Map.Entry<GenericPK, BigDecimal> entry = entryIter.next(); GenericPK productPromoCondActionPK = entry.getKey(); BigDecimal quantityUsed = entry.getValue(); if (productPromoId.equals(productPromoCondActionPK.getString("productPromoId")) && productPromoRuleId.equals(productPromoCondActionPK.getString("productPromoRuleId"))) { entryIter.remove(); BigDecimal existingValue = this.quantityUsedPerPromoActual.get(productPromoCondActionPK); if (existingValue == null) { this.quantityUsedPerPromoActual.put(productPromoCondActionPK, quantityUsed); } else { this.quantityUsedPerPromoActual.put(productPromoCondActionPK, quantityUsed.add(existingValue)); } } } } public synchronized void clearPromoRuleUseInfo() { this.quantityUsedPerPromoActual.clear(); this.quantityUsedPerPromoCandidate.clear(); this.quantityUsedPerPromoFailed.clear(); this.promoQuantityUsed = this.getIsPromo() ? this.quantity : BigDecimal.ZERO; } /** Sets the item comment. */ public void setItemComment(String itemComment) { this.setAttribute("itemComment", itemComment); } /** Returns the item's comment. */ public String getItemComment() { return (String) this.getAttribute("itemComment"); } /** Sets the item's customer desired delivery date. */ public void setDesiredDeliveryDate(Timestamp ddDate) { if (ddDate != null) { this.setAttribute("itemDesiredDeliveryDate", ddDate.toString()); } } /** Returns the item's customer desired delivery date. */ public Timestamp getDesiredDeliveryDate() { String ddDate = (String) this.getAttribute("itemDesiredDeliveryDate"); if (ddDate != null) { try { return Timestamp.valueOf(ddDate); } catch (IllegalArgumentException e) { Debug.logWarning(e, UtilProperties.getMessage(resource_error,"OrderProblemGettingItemDesiredDeliveryDateFor", UtilMisc.toMap("productId",this.getProductId()), locale)); return null; } } return null; } /** Sets the date to ship before */ public void setShipBeforeDate(Timestamp date) { this.shipBeforeDate = date; } /** Returns the date to ship before */ public Timestamp getShipBeforeDate() { return this.shipBeforeDate; } /** Sets the date to ship after */ public void setShipAfterDate(Timestamp date) { this.shipAfterDate = date; } /** Returns the date to ship after */ public Timestamp getShipAfterDate() { return this.shipAfterDate; } /** Sets the cancel back order date */ public void setCancelBackOrderDate(Timestamp date) { this.cancelBackOrderDate = date; } /** Returns the cancel back order date */ public Timestamp getCancelBackOrderDate() { return this.cancelBackOrderDate; } /** Sets the date to EstimatedShipDate */ public void setEstimatedShipDate(Timestamp date) { this.estimatedShipDate = date; } /** Returns the date to EstimatedShipDate */ public Timestamp getEstimatedShipDate() { return this.estimatedShipDate; } /** Sets the item type. */ public void setItemType(String itemType) { this.itemType = itemType; } /** Returns the item type. */ public String getItemType() { return this.itemType; } /** Returns the item type. */ public GenericValue getItemTypeGenericValue() { try { return this.getDelegator().findOne("OrderItemType", UtilMisc.toMap("orderItemTypeId", this.itemType), true); } catch (GenericEntityException e) { Debug.logError(e, "Error getting ShippingCartItem's OrderItemType", module); return null; } } /** Sets the item group. */ public void setItemGroup(ShoppingCart.ShoppingCartItemGroup itemGroup) { this.itemGroup = itemGroup; } /** Sets the item group. */ public void setItemGroup(String groupNumber, ShoppingCart cart) { this.itemGroup = cart.getItemGroupByNumber(groupNumber); } /** Returns the item group. */ public ShoppingCart.ShoppingCartItemGroup getItemGroup() { return this.itemGroup; } public boolean isInItemGroup(String groupNumber) { if (this.itemGroup == null) return false; if (this.itemGroup.getGroupNumber().equals(groupNumber)) return true; return false; } /** Returns the item type description. */ public String getItemTypeDescription() { GenericValue orderItemType = null; if (this.getItemType() != null) { try { orderItemType = this.getDelegator().findOne("OrderItemType", UtilMisc.toMap("orderItemTypeId", this.getItemType()), true); } catch (GenericEntityException e) { Debug.logWarning(e, UtilProperties.getMessage(resource_error,"OrderProblemsGettingOrderItemTypeFor", UtilMisc.toMap("orderItemTypeId",this.getItemType()), locale)); } } if (orderItemType != null) { return orderItemType.getString("description"); } return null; } /** Returns the productCategoryId for the item or null if none. */ public String getProductCategoryId() { return this.productCategoryId; } public void setProductCategoryId(String productCategoryId) { this.productCategoryId = productCategoryId; } public void setOrderItemSeqId(String orderItemSeqId) { Debug.logInfo("Setting orderItemSeqId - " + orderItemSeqId, module); this.orderItemSeqId = orderItemSeqId; } public String getOrderItemSeqId() { return orderItemSeqId; } public void setShoppingList(String shoppingListId, String itemSeqId) { attributes.put("shoppingListId", shoppingListId); attributes.put("shoppingListItemSeqId", itemSeqId); } public String getShoppingListId() { return (String) attributes.get("shoppingListId"); } public String getShoppingListItemSeqId() { return (String) attributes.get("shoppingListItemSeqId"); } /** Sets the requirementId. */ public void setRequirementId(String requirementId) { this.requirementId = requirementId; } /** Returns the requirementId. */ public String getRequirementId() { return this.requirementId; } /** Sets the quoteId. */ public void setQuoteId(String quoteId) { this.quoteId = quoteId; } /** Returns the quoteId. */ public String getQuoteId() { return this.quoteId; } /** Sets the quoteItemSeqId. */ public void setQuoteItemSeqId(String quoteItemSeqId) { this.quoteItemSeqId = quoteItemSeqId; } /** Returns the quoteItemSeqId. */ public String getQuoteItemSeqId() { return this.quoteItemSeqId; } /** Sets the orderItemAssocTypeId. */ public void setOrderItemAssocTypeId(String orderItemAssocTypeId) { if (orderItemAssocTypeId != null) { this.orderItemAssocTypeId = orderItemAssocTypeId; } } /** Returns the OrderItemAssocTypeId. */ public String getOrderItemAssocTypeId() { return this.orderItemAssocTypeId; } /** Sets the associatedOrderId. */ public void setAssociatedOrderId(String associatedOrderId) { this.associatedOrderId = associatedOrderId; } /** Returns the associatedId. */ public String getAssociatedOrderId() { return this.associatedOrderId; } /** Sets the associatedOrderItemSeqId. */ public void setAssociatedOrderItemSeqId(String associatedOrderItemSeqId) { this.associatedOrderItemSeqId = associatedOrderItemSeqId; } /** Returns the associatedOrderItemSeqId. */ public String getAssociatedOrderItemSeqId() { return this.associatedOrderItemSeqId; } public String getStatusId() { return this.statusId; } public void setStatusId(String statusId) { this.statusId = statusId; } /** Returns true if shipping charges apply to this item. */ public boolean shippingApplies() { GenericValue product = getProduct(); if (product != null) { return ProductWorker.shippingApplies(product); } else { // we don't ship non-product items return false; } } /** Returns true if tax charges apply to this item. */ public boolean taxApplies() { GenericValue product = getProduct(); if (product != null) { return ProductWorker.taxApplies(product); } else { // we do tax non-product items return true; } } /** Returns the item's productId. */ public String getProductId() { return productId; } /** Returns the item's supplierProductId. */ public String getSupplierProductId() { return supplierProductId; } /** Set the item's supplierProductId. */ public void setSupplierProductId(String supplierProductId) { this.supplierProductId = supplierProductId; } /** Set the item's locale (from ShoppingCart.setLocale) */ protected void setLocale(Locale locale) { this.locale = locale; } /** Set the item's description. */ public void setName(String itemName) { this.itemDescription = itemName; } /** Returns the item's description. * @deprecated use getName(LocalDispatcher dispatcher) **/ public String getName() { return itemDescription; } /** Returns the item's description or PRODUCT_NAME from content. */ public String getName(LocalDispatcher dispatcher) { if (itemDescription != null) { return itemDescription; } else { GenericValue product = getProduct(); if (product != null) { String productName = ProductContentWrapper.getProductContentAsText(product, "PRODUCT_NAME", this.locale, dispatcher, "html"); // if the productName is null or empty, see if there is an associated virtual product and get the productName of that product if (UtilValidate.isEmpty(productName)) { GenericValue parentProduct = this.getParentProduct(); if (parentProduct != null) { productName = ProductContentWrapper.getProductContentAsText(parentProduct, "PRODUCT_NAME", this.locale, dispatcher, "html"); } } if (productName == null) { return ""; } else { return productName; } } else { return ""; } } } /** Returns the item's description. */ public String getDescription(LocalDispatcher dispatcher) { GenericValue product = getProduct(); if (product != null) { String description = ProductContentWrapper.getProductContentAsText(product, "DESCRIPTION", this.locale, dispatcher, "html"); // if the description is null or empty, see if there is an associated virtual product and get the description of that product if (UtilValidate.isEmpty(description)) { GenericValue parentProduct = this.getParentProduct(); if (parentProduct != null) { description = ProductContentWrapper.getProductContentAsText(parentProduct, "DESCRIPTION", this.locale, dispatcher, "html"); } } if (description == null) { return ""; } else { return description; } } else { return null; } } public ProductConfigWrapper getConfigWrapper() { return configWrapper; } /** Returns the item's unit weight */ public BigDecimal getWeight() { GenericValue product = getProduct(); if (product != null) { BigDecimal weight = product.getBigDecimal("productWeight"); // if the weight is null, see if there is an associated virtual product and get the weight of that product if (weight == null) { GenericValue parentProduct = this.getParentProduct(); if (parentProduct != null) weight = parentProduct.getBigDecimal("productWeight"); } if (weight == null) { return BigDecimal.ZERO; } else { return weight; } } else { // non-product items have 0 weight return BigDecimal.ZERO; } } /** Returns the item's pieces included */ public long getPiecesIncluded() { GenericValue product = getProduct(); if (product != null) { Long pieces = product.getLong("piecesIncluded"); // if the piecesIncluded is null, see if there is an associated virtual product and get the piecesIncluded of that product if (pieces == null) { GenericValue parentProduct = this.getParentProduct(); if (parentProduct != null) pieces = parentProduct.getLong("piecesIncluded"); } if (pieces == null) { return 1; } else { return pieces.longValue(); } } else { // non-product item assumed 1 piece return 1; } } /** Returns a Set of the item's features */ public Set<String> getFeatureSet() { Set<String> featureSet = new LinkedHashSet<String>(); GenericValue product = this.getProduct(); if (product != null) { List<GenericValue> featureAppls = null; try { featureAppls = product.getRelated("ProductFeatureAppl", null, null, false); List<EntityExpr> filterExprs = UtilMisc.toList(EntityCondition.makeCondition("productFeatureApplTypeId", EntityOperator.EQUALS, "STANDARD_FEATURE")); filterExprs.add(EntityCondition.makeCondition("productFeatureApplTypeId", EntityOperator.EQUALS, "REQUIRED_FEATURE")); featureAppls = EntityUtil.filterByOr(featureAppls, filterExprs); } catch (GenericEntityException e) { Debug.logError(e, "Unable to get features from product : " + product.get("productId"), module); } if (featureAppls != null) { for (GenericValue appl : featureAppls) { featureSet.add(appl.getString("productFeatureId")); } } } if (this.additionalProductFeatureAndAppls != null) { for (GenericValue appl : this.additionalProductFeatureAndAppls.values()) { featureSet.add(appl.getString("productFeatureId")); } } return featureSet; } /** Returns a list of the item's standard features */ public List<GenericValue> getStandardFeatureList() { List<GenericValue> features = null; GenericValue product = this.getProduct(); if (product != null) { try { List<GenericValue> featureAppls = product.getRelated("ProductFeatureAndAppl", null, null, false); features=EntityUtil.filterByAnd(featureAppls,UtilMisc.toMap("productFeatureApplTypeId","STANDARD_FEATURE")); } catch (GenericEntityException e) { Debug.logError(e, "Unable to get features from product : " + product.get("productId"), module); } } return features; } /** Returns a List of the item's features for supplier*/ public List<GenericValue> getFeaturesForSupplier(LocalDispatcher dispatcher,String partyId) { List<GenericValue> featureAppls = getStandardFeatureList(); if (UtilValidate.isNotEmpty(featureAppls)) { try { Map<String, Object> result = dispatcher.runSync("convertFeaturesForSupplier", UtilMisc.toMap("partyId", partyId, "productFeatures", featureAppls)); featuresForSupplier = UtilGenerics.checkList(result.get("convertedProductFeatures")); } catch (GenericServiceException e) { Debug.logError(e, "Unable to get features for supplier from product : " + this.productId, module); } } return featuresForSupplier; } /** Returns the item's size (length + girth) */ public BigDecimal getSize() { GenericValue product = getProduct(); if (product != null) { BigDecimal height = product.getBigDecimal("shippingHeight"); BigDecimal width = product.getBigDecimal("shippingWidth"); BigDecimal depth = product.getBigDecimal("shippingDepth"); // if all are null, see if there is an associated virtual product and get the info of that product if (height == null && width == null && depth == null) { GenericValue parentProduct = this.getParentProduct(); if (parentProduct != null) { height = parentProduct.getBigDecimal("shippingHeight"); width = parentProduct.getBigDecimal("shippingWidth"); depth = parentProduct.getBigDecimal("shippingDepth"); } } if (height == null) height = BigDecimal.ZERO; if (width == null) width = BigDecimal.ZERO; if (depth == null) depth = BigDecimal.ZERO; // determine girth (longest field is length) BigDecimal[] sizeInfo = { height, width, depth }; Arrays.sort(sizeInfo); return (sizeInfo[0].add(sizeInfo[0])).add(sizeInfo[1].add(sizeInfo[1])).add(sizeInfo[2]); } else { // non-product items have 0 size return BigDecimal.ZERO; } } public Map<String, Object> getItemProductInfo() { Map<String, Object> itemInfo = new HashMap<String, Object>(); itemInfo.put("productId", this.getProductId()); itemInfo.put("weight", this.getWeight()); itemInfo.put("weightUomId", this.getProduct().getString("weightUomId")); itemInfo.put("size", this.getSize()); itemInfo.put("piecesIncluded", Long.valueOf(this.getPiecesIncluded())); itemInfo.put("featureSet", this.getFeatureSet()); GenericValue product = getProduct(); if (product != null) { itemInfo.put("inShippingBox", product.getString("inShippingBox")); if (product.getString("inShippingBox") != null && product.getString("inShippingBox").equals("Y")) { itemInfo.put("shippingHeight", product.getBigDecimal("shippingHeight")); itemInfo.put("shippingWidth", product.getBigDecimal("shippingWidth")); itemInfo.put("shippingDepth", product.getBigDecimal("shippingDepth")); } } return itemInfo; } /** Returns the base price. */ public BigDecimal getBasePrice() { BigDecimal curBasePrice; if (selectedAmount.compareTo(BigDecimal.ZERO) > 0) { curBasePrice = basePrice.multiply(selectedAmount); } else { curBasePrice = basePrice; } return curBasePrice; } public BigDecimal getDisplayPrice() { BigDecimal curDisplayPrice; if (this.displayPrice == null) { curDisplayPrice = this.getBasePrice(); } else { if (selectedAmount.compareTo(BigDecimal.ZERO) > 0) { curDisplayPrice = this.displayPrice.multiply(this.selectedAmount); } else { curDisplayPrice = this.displayPrice; } } return curDisplayPrice; } public BigDecimal getSpecialPromoPrice() { return this.specialPromoPrice; } public BigDecimal getRecurringBasePrice() { if (this.recurringBasePrice == null) return null; if (selectedAmount.compareTo(BigDecimal.ZERO) > 0) { return this.recurringBasePrice.multiply(selectedAmount); } else { return this.recurringBasePrice; } } public BigDecimal getRecurringDisplayPrice() { if (this.recurringDisplayPrice == null) { return this.getRecurringBasePrice(); } if (selectedAmount.compareTo(BigDecimal.ZERO) > 0) { return this.recurringDisplayPrice.multiply(this.selectedAmount); } else { return this.recurringDisplayPrice; } } /** Returns the list price. */ public BigDecimal getListPrice() { return listPrice; } public void setListPrice(BigDecimal listPrice) { this.listPrice = listPrice; } /** Returns isModifiedPrice */ public boolean getIsModifiedPrice() { return isModifiedPrice; } /** Set isModifiedPrice */ public void setIsModifiedPrice(boolean isModifiedPrice) { this.isModifiedPrice = isModifiedPrice; } /** get the percentage for the second person */ public BigDecimal getReserv2ndPPPerc() { return reserv2ndPPPerc; } /** get the percentage for the third and following person */ public BigDecimal getReservNthPPPerc() { return reservNthPPPerc; } /** Returns the "other" adjustments. */ public BigDecimal getOtherAdjustments() { return OrderReadHelper.calcItemAdjustments(quantity, getBasePrice(), this.getAdjustments(), true, false, false, false, false); } /** Returns the "other" adjustments. */ public BigDecimal getOtherAdjustmentsRecurring() { return OrderReadHelper.calcItemAdjustmentsRecurringBd(quantity, getRecurringBasePrice() == null ? BigDecimal.ZERO : getRecurringBasePrice(), this.getAdjustments(), true, false, false, false, false); } /** calculates for a reservation the percentage/100 extra for more than 1 person. */ // similar code at EditShoppingList.groovy public BigDecimal getRentalAdjustment() { if (!"RENTAL_ORDER_ITEM".equals(this.itemType)) { // not a rental item? return BigDecimal.ONE; } BigDecimal persons = this.getReservPersons(); BigDecimal rentalValue = BigDecimal.ZERO; if (persons.compareTo(BigDecimal.ONE) > 0) { if (persons.compareTo(new BigDecimal("2")) > 0) { persons = persons.subtract(new BigDecimal("2")); if (getReservNthPPPerc().compareTo(BigDecimal.ZERO) > 0) { rentalValue = persons.multiply(getReservNthPPPerc()); } else { rentalValue = persons.multiply(getReserv2ndPPPerc()); } persons = new BigDecimal("2"); } if (persons.compareTo(new BigDecimal("2")) == 0) { rentalValue = rentalValue.add(getReserv2ndPPPerc()); } } rentalValue = rentalValue.add(new BigDecimal("100")); // add final 100 percent for first person return rentalValue.movePointLeft(2).multiply(getReservLength()); // return total rental adjustment } /** Returns the total line price. */ public BigDecimal getItemSubTotal(BigDecimal quantity) { return getBasePrice().multiply(quantity).multiply(getRentalAdjustment()).add(getOtherAdjustments()); } public BigDecimal getItemSubTotal() { return this.getItemSubTotal(this.getQuantity()); } public BigDecimal getDisplayItemSubTotal() { return this.getDisplayPrice().multiply(this.getQuantity()).multiply(this.getRentalAdjustment()).add(this.getOtherAdjustments()); } public BigDecimal getDisplayItemSubTotalNoAdj() { return this.getDisplayPrice().multiply(this.getQuantity()); } public BigDecimal getDisplayItemRecurringSubTotal() { BigDecimal curRecurringDisplayPrice = this.getRecurringDisplayPrice(); if (curRecurringDisplayPrice == null) { return this.getOtherAdjustmentsRecurring(); } return curRecurringDisplayPrice.multiply(this.getQuantity()).add(this.getOtherAdjustmentsRecurring()); } public BigDecimal getDisplayItemRecurringSubTotalNoAdj() { BigDecimal curRecurringDisplayPrice = this.getRecurringDisplayPrice(); if (curRecurringDisplayPrice == null) return BigDecimal.ZERO; return curRecurringDisplayPrice.multiply(this.getQuantity()); } public void addAllProductFeatureAndAppls(Map<String, GenericValue> productFeatureAndApplsToAdd) { if (productFeatureAndApplsToAdd == null) return; for (GenericValue additionalProductFeatureAndAppl : productFeatureAndApplsToAdd.values()) { this.putAdditionalProductFeatureAndAppl(additionalProductFeatureAndAppl); } } public void putAdditionalProductFeatureAndAppl(GenericValue additionalProductFeatureAndAppl) { if (additionalProductFeatureAndAppl == null) return; // if one already exists with the given type, remove it with the corresponding adjustment removeAdditionalProductFeatureAndAppl(additionalProductFeatureAndAppl.getString("productFeatureTypeId")); // adds to additional map and creates an adjustment with given price String featureType = additionalProductFeatureAndAppl.getString("productFeatureTypeId"); this.additionalProductFeatureAndAppls.put(featureType, additionalProductFeatureAndAppl); GenericValue orderAdjustment = this.getDelegator().makeValue("OrderAdjustment"); orderAdjustment.set("orderAdjustmentTypeId", "ADDITIONAL_FEATURE"); orderAdjustment.set("description", additionalProductFeatureAndAppl.get("description")); orderAdjustment.set("productFeatureId", additionalProductFeatureAndAppl.get("productFeatureId")); // NOTE: this is a VERY simple pricing scheme for additional features and will likely need to be extended for most real applications BigDecimal amount = (BigDecimal) additionalProductFeatureAndAppl.get("amount"); if (amount != null) { amount = amount.multiply(this.getQuantity()); orderAdjustment.set("amount", amount); } BigDecimal recurringAmount = (BigDecimal) additionalProductFeatureAndAppl.get("recurringAmount"); if (recurringAmount != null) { recurringAmount = recurringAmount.multiply(this.getQuantity()); orderAdjustment.set("recurringAmount", recurringAmount); } if (amount == null && recurringAmount == null) { Debug.logWarning("In putAdditionalProductFeatureAndAppl the amount and recurringAmount are null for this adjustment: " + orderAdjustment, module); } this.addAdjustment(orderAdjustment); } public GenericValue getAdditionalProductFeatureAndAppl(String productFeatureTypeId) { if (this.additionalProductFeatureAndAppls == null) return null; return this.additionalProductFeatureAndAppls.get(productFeatureTypeId); } public GenericValue removeAdditionalProductFeatureAndAppl(String productFeatureTypeId) { if (this.additionalProductFeatureAndAppls == null) return null; GenericValue oldAdditionalProductFeatureAndAppl = this.additionalProductFeatureAndAppls.remove(productFeatureTypeId); if (oldAdditionalProductFeatureAndAppl != null) { removeFeatureAdjustment(oldAdditionalProductFeatureAndAppl.getString("productFeatureId")); } return oldAdditionalProductFeatureAndAppl; } public Map<String, GenericValue> getAdditionalProductFeatureAndAppls() { return this.additionalProductFeatureAndAppls; } public Map<String, BigDecimal> getFeatureIdQtyMap(BigDecimal quantity) { Map<String, BigDecimal> featureMap = new HashMap<String, BigDecimal>(); GenericValue product = this.getProduct(); if (product != null) { List<GenericValue> featureAppls = null; try { featureAppls = product.getRelated("ProductFeatureAppl", null, null, false); List<EntityExpr> filterExprs = UtilMisc.toList(EntityCondition.makeCondition("productFeatureApplTypeId", EntityOperator.EQUALS, "STANDARD_FEATURE")); filterExprs.add(EntityCondition.makeCondition("productFeatureApplTypeId", EntityOperator.EQUALS, "REQUIRED_FEATURE")); filterExprs.add(EntityCondition.makeCondition("productFeatureApplTypeId", EntityOperator.EQUALS, "DISTINGUISHING_FEAT")); featureAppls = EntityUtil.filterByOr(featureAppls, filterExprs); } catch (GenericEntityException e) { Debug.logError(e, "Unable to get features from product : " + product.get("productId"), module); } if (featureAppls != null) { for (GenericValue appl : featureAppls) { BigDecimal lastQuantity = featureMap.get(appl.getString("productFeatureId")); if (lastQuantity == null) { lastQuantity = BigDecimal.ZERO; } BigDecimal newQuantity = lastQuantity.add(quantity); featureMap.put(appl.getString("productFeatureId"), newQuantity); } } } if (this.additionalProductFeatureAndAppls != null) { for (GenericValue appl : this.additionalProductFeatureAndAppls.values()) { BigDecimal lastQuantity = featureMap.get(appl.getString("productFeatureId")); if (lastQuantity == null) { lastQuantity = BigDecimal.ZERO; } BigDecimal newQuantity = lastQuantity.add(quantity); featureMap.put(appl.getString("productFeatureId"), newQuantity); } } return featureMap; } /** Removes an item attribute. */ public void removeAttribute(String name) { attributes.remove(name); } /** Sets an item attribute. */ public void setAttribute(String name, Object value) { attributes.put(name, value); } /** Return a specific attribute. */ public Object getAttribute(String name) { return attributes.get(name); } /** Returns the attributes for the item. */ public Map<String, Object> getAttributes() { return attributes; } /** Remove an OrderItemAttribute. */ public void removeOrderItemAttribute(String name) { if (orderItemAttributes != null) { orderItemAttributes.remove(name); } } /** Creates an OrderItemAttribute entry. */ public void setOrderItemAttribute(String name, String value) { if (orderItemAttributes == null) orderItemAttributes = new HashMap<String, String>(); this.orderItemAttributes.put(name, value); } /** Return an OrderItemAttribute. */ public String getOrderItemAttribute(String name) { if (orderItemAttributes == null) return null; return this.orderItemAttributes.get(name); } public Map<String, String> getOrderItemAttributes() { Map<String, String> attrs = new HashMap<String, String>(); if (orderItemAttributes != null) { attrs.putAll(orderItemAttributes); } return attrs; } /** Add an adjustment to the order item; don't worry about setting the orderId, orderItemSeqId or orderAdjustmentId; they will be set when the order is created */ public int addAdjustment(GenericValue adjustment) { itemAdjustments.add(adjustment); return itemAdjustments.indexOf(adjustment); } public void removeAdjustment(GenericValue adjustment) { itemAdjustments.remove(adjustment); } public void removeAdjustment(int index) { itemAdjustments.remove(index); } public List<GenericValue> getAdjustments() { return itemAdjustments; } public void removeFeatureAdjustment(String productFeatureId) { if (productFeatureId == null) return; Iterator<GenericValue> itemAdjustmentsIter = itemAdjustments.iterator(); while (itemAdjustmentsIter.hasNext()) { GenericValue itemAdjustment = itemAdjustmentsIter.next(); if (productFeatureId.equals(itemAdjustment.getString("productFeatureId"))) { itemAdjustmentsIter.remove(); } } } public List<GenericValue> getOrderItemPriceInfos() { return orderItemPriceInfos; } /** Add a contact mech to this purpose; the contactMechPurposeTypeId is required */ public void addContactMech(String contactMechPurposeTypeId, String contactMechId) { if (contactMechPurposeTypeId == null) throw new IllegalArgumentException("You must specify a contactMechPurposeTypeId to add a ContactMech"); contactMechIdsMap.put(contactMechPurposeTypeId, contactMechId); } /** Get the contactMechId for this item given the contactMechPurposeTypeId */ public String getContactMech(String contactMechPurposeTypeId) { return contactMechIdsMap.get(contactMechPurposeTypeId); } /** Remove the contactMechId from this item given the contactMechPurposeTypeId */ public String removeContactMech(String contactMechPurposeTypeId) { return contactMechIdsMap.remove(contactMechPurposeTypeId); } public Map<String, String> getOrderItemContactMechIds() { return contactMechIdsMap; } public void setIsPromo(boolean isPromo) { this.isPromo = isPromo; } public boolean getIsPromo() { return this.isPromo; } public List<String> getAlternativeOptionProductIds() { return this.alternativeOptionProductIds; } public void setAlternativeOptionProductIds(List<String> alternativeOptionProductIds) { this.alternativeOptionProductIds = alternativeOptionProductIds; } /** Compares the specified object with this cart item. */ public boolean equals(ShoppingCartItem item) { if (item == null) return false; return this.equals(item.getProductId(), item.additionalProductFeatureAndAppls, item.attributes, item.prodCatalogId, item.selectedAmount, item.getItemType(), item.getItemGroup(), item.getIsPromo()); } /** Compares the specified object with this cart item. Defaults isPromo to false. Default to no itemGroup. */ public boolean equals(String productId, Map<String, GenericValue> additionalProductFeatureAndAppls, Map<String, Object> attributes, String prodCatalogId, BigDecimal selectedAmount) { return equals(productId, additionalProductFeatureAndAppls, attributes, prodCatalogId, selectedAmount, null, null, false); } /** Compares the specified object with this cart item. Defaults isPromo to false. */ public boolean equals(String productId, Map<String, GenericValue> additionalProductFeatureAndAppls, Map<String, Object> attributes, String prodCatalogId, ProductConfigWrapper configWrapper, String itemType, ShoppingCart.ShoppingCartItemGroup itemGroup, BigDecimal selectedAmount) { return equals(productId, null, BigDecimal.ZERO, BigDecimal.ZERO, null, null, additionalProductFeatureAndAppls, attributes, prodCatalogId, selectedAmount, configWrapper, itemType, itemGroup, false); } /** Compares the specified object with this cart item including rental data. Defaults isPromo to false. */ public boolean equals(String productId, Timestamp reservStart, BigDecimal reservLength, BigDecimal reservPersons, Map<String, GenericValue> additionalProductFeatureAndAppls, Map<String, Object> attributes, String prodCatalogId, ProductConfigWrapper configWrapper, String itemType, ShoppingCart.ShoppingCartItemGroup itemGroup, BigDecimal selectedAmount) { return equals(productId, reservStart, reservLength, reservPersons, null, null, additionalProductFeatureAndAppls, attributes, prodCatalogId, selectedAmount, configWrapper, itemType, itemGroup, false); } /** Compares the specified object with this cart item. Defaults isPromo to false. */ public boolean equals(String productId, Map<String, GenericValue> additionalProductFeatureAndAppls, Map<String, Object> attributes, String prodCatalogId, BigDecimal selectedAmount, String itemType, ShoppingCart.ShoppingCartItemGroup itemGroup, boolean isPromo) { return equals(productId, null, BigDecimal.ZERO, BigDecimal.ZERO, null, null, additionalProductFeatureAndAppls, attributes, prodCatalogId, selectedAmount, null, itemType, itemGroup, isPromo); } /** Compares the specified object with this cart item. */ public boolean equals(String productId, Timestamp reservStart, BigDecimal reservLength, BigDecimal reservPersons, String accommodationMapId, String accommodationSpotId, Map<String, GenericValue> additionalProductFeatureAndAppls, Map<String, Object> attributes, String prodCatalogId, BigDecimal selectedAmount, ProductConfigWrapper configWrapper, String itemType, ShoppingCart.ShoppingCartItemGroup itemGroup, boolean isPromo) { return equals(productId, reservStart, reservLength, reservPersons, accommodationMapId, accommodationSpotId, additionalProductFeatureAndAppls, attributes, null, prodCatalogId, selectedAmount, configWrapper, itemType, itemGroup, isPromo); } /** Compares the specified object order item attributes. */ public boolean equals(String productId, Timestamp reservStart, BigDecimal reservLength, BigDecimal reservPersons, String accommodationMapId, String accommodationSpotId, Map<String, GenericValue> additionalProductFeatureAndAppls, Map<String, Object> attributes, Map<String, String> orderItemAttributes, String prodCatalogId, BigDecimal selectedAmount, ProductConfigWrapper configWrapper, String itemType, ShoppingCart.ShoppingCartItemGroup itemGroup, boolean isPromo) { if (this.productId == null || productId == null) { // all non-product items are unique return false; } if (!this.productId.equals(productId)) { return false; } if ((this.prodCatalogId == null && prodCatalogId != null) || (this.prodCatalogId != null && prodCatalogId == null)) { return false; } if (this.prodCatalogId != null && prodCatalogId != null && !this.prodCatalogId.equals(prodCatalogId)) { return false; } if (selectedAmount != null && this.selectedAmount.compareTo(selectedAmount) != 0) { return false; } if ((this.reservStart == null && reservStart != null) || (this.reservStart != null && reservStart == null)) { return false; } if (this.reservStart != null && reservStart != null && !this.reservStart.equals(reservStart)) { return false; } if (reservLength != null && this.reservLength.compareTo(reservLength) != 0) { return false; } if (reservPersons != null && this.reservPersons.compareTo(reservPersons) != 0) { return false; } if (this.accommodationMapId != null && !this.accommodationMapId.equals(accommodationMapId)) { return false; } if (this.accommodationSpotId != null && !this.accommodationSpotId.equals(accommodationSpotId)) { return false; } if (this.isPromo != isPromo) { return false; } if ((this.additionalProductFeatureAndAppls == null && UtilValidate.isNotEmpty(additionalProductFeatureAndAppls)) || (UtilValidate.isNotEmpty(this.additionalProductFeatureAndAppls) && additionalProductFeatureAndAppls == null) || (this.additionalProductFeatureAndAppls != null && additionalProductFeatureAndAppls != null && (this.additionalProductFeatureAndAppls.size() != additionalProductFeatureAndAppls.size() || !(this.additionalProductFeatureAndAppls.equals(additionalProductFeatureAndAppls))))) { return false; } if ((this.attributes == null && UtilValidate.isNotEmpty(attributes)) || (UtilValidate.isNotEmpty(this.attributes) && attributes == null) || (this.attributes != null && attributes != null && (this.attributes.size() != attributes.size() || !(this.attributes.equals(attributes))))) { return false; } if (configWrapper != null && !configWrapper.equals(this.configWrapper)) { return false; } if (itemType != null && !itemType.equals(this.itemType)) { return false; } if (itemGroup != null && !itemGroup.equals(this.itemGroup)) { return false; } if (quoteId != null) { // all items linked to a quote are unique return false; } if (requirementId != null) { // all items linked to a requirement are unique return false; } if ((this.orderItemAttributes == null && UtilValidate.isNotEmpty(orderItemAttributes)) || (UtilValidate.isNotEmpty(this.orderItemAttributes) && orderItemAttributes == null) || (this.orderItemAttributes != null && orderItemAttributes != null && (this.orderItemAttributes.size() != orderItemAttributes.size() || !(this.orderItemAttributes.equals(orderItemAttributes))))) { // order item attribute unique return false; } return true; } /** Gets the Product entity. If it is not already retreived gets it from the delegator */ public GenericValue getProduct() { if (this._product != null) { return this._product; } if (this.productId != null) { try { this._product = this.getDelegator().findOne("Product", UtilMisc.toMap("productId", productId), true); } catch (GenericEntityException e) { throw new RuntimeException("Entity Engine error getting Product (" + e.getMessage() + ")"); } } return this._product; } public GenericValue getParentProduct() { if (this._parentProduct != null) { return this._parentProduct; } if (this.productId == null) { throw new IllegalStateException("Bad product id"); } this._parentProduct = ProductWorker.getParentProduct(productId, this.getDelegator()); return this._parentProduct; } public String getParentProductId() { GenericValue parentProduct = this.getParentProduct(); if (parentProduct != null) { return parentProduct.getString("productId"); } else { return null; } } public Map<String, List<GenericValue>> getOptionalProductFeatures() { if (_product != null) { return ProductWorker.getOptionalProductFeatures(getDelegator(), this.productId); } else { // non-product items do not have features return new HashMap<String, List<GenericValue>>(); } } public Delegator getDelegator() { if (delegator == null) { if (UtilValidate.isEmpty(delegatorName)) { throw new IllegalStateException("No delegator or delegatorName on ShoppingCartItem, somehow was not setup right."); } delegator = DelegatorFactory.getDelegator(delegatorName); } return delegator; } public List<ShoppingCartItem> explodeItem(ShoppingCart cart, LocalDispatcher dispatcher) throws CartItemModifyException { BigDecimal baseQuantity = this.getQuantity(); int thisIndex = cart.items().indexOf(this); List<ShoppingCartItem> newItems = new ArrayList<ShoppingCartItem>(); if (baseQuantity.compareTo(BigDecimal.ONE) > 0) { for (int i = 1; i < baseQuantity.intValue(); i++) { // clone the item ShoppingCartItem item = new ShoppingCartItem(this); // set the new item's quantity item.setQuantity(BigDecimal.ONE, dispatcher, cart, false); // now copy/calc the adjustments Debug.logInfo("Clone's adj: " + item.getAdjustments(), module); if (UtilValidate.isNotEmpty(item.getAdjustments())) { List<GenericValue> adjustments = UtilMisc.makeListWritable(item.getAdjustments()); for (GenericValue adjustment: adjustments) { if (adjustment != null) { item.removeAdjustment(adjustment); GenericValue newAdjustment = GenericValue.create(adjustment); BigDecimal adjAmount = newAdjustment.getBigDecimal("amount"); // we use != because adjustments can be +/- if (adjAmount != null && adjAmount.compareTo(BigDecimal.ZERO) != 0) newAdjustment.set("amount", adjAmount.divide(baseQuantity, generalRounding)); Debug.logInfo("Cloned adj: " + newAdjustment, module); item.addAdjustment(newAdjustment); } else { Debug.logInfo("Clone Adjustment is null", module); } } } newItems.add(item); } // set this item's quantity this.setQuantity(BigDecimal.ONE, dispatcher, cart, false); Debug.logInfo("BaseQuantity: " + baseQuantity, module); Debug.logInfo("Item's Adj: " + this.getAdjustments(), module); // re-calc this item's adjustments if (UtilValidate.isNotEmpty(this.getAdjustments())) { List<GenericValue> adjustments = UtilMisc.makeListWritable(this.getAdjustments()); for (GenericValue adjustment: adjustments) { if (adjustment != null) { this.removeAdjustment(adjustment); GenericValue newAdjustment = GenericValue.create(adjustment); BigDecimal adjAmount = newAdjustment.getBigDecimal("amount"); // we use != becuase adjustments can be +/- if (adjAmount != null && adjAmount.compareTo(BigDecimal.ZERO) != 0) newAdjustment.set("amount", adjAmount.divide(baseQuantity, generalRounding)); Debug.logInfo("Updated adj: " + newAdjustment, module); this.addAdjustment(newAdjustment); } } } } return newItems; } public static String getPurchaseOrderItemDescription(GenericValue product, GenericValue supplierProduct, Locale locale, LocalDispatcher dispatcher) { String itemDescription = null; if (supplierProduct != null) { itemDescription = supplierProduct.getString("supplierProductName"); } if (UtilValidate.isEmpty(itemDescription)) { itemDescription = ProductContentWrapper.getProductContentAsText(product, "PRODUCT_NAME", locale, dispatcher, "html"); } return itemDescription; } }