Start line:  
End line:  

Snippet Preview

Snippet HTML Code

Stack Overflow Questions
  /*
   * #%L
   * BroadleafCommerce Framework
   * %%
   * Copyright (C) 2009 - 2013 Broadleaf Commerce
   * %%
   * Licensed 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.
  * #L%
  */
 package org.broadleafcommerce.core.offer.service.processor;
 
 
 import java.util.List;
 import java.util.Map;
Filter and apply order item offers.

Author(s):
jfischer
 
 @Service("blItemOfferProcessor")
     
     protected static final Log LOG = LogFactory.getLog(ItemOfferProcessorImpl.class);
 
     /* (non-Javadoc)
      * @see org.broadleafcommerce.core.offer.service.processor.ItemOfferProcessor#filterItemLevelOffer(org.broadleafcommerce.core.order.domain.Order, java.util.List, java.util.List, org.broadleafcommerce.core.offer.domain.Offer)
      */
     @Override
     public void filterItemLevelOffer(PromotableOrder orderList<PromotableCandidateItemOfferqualifiedItemOffersOffer offer) {
         boolean isNewFormat = !CollectionUtils.isEmpty(offer.getQualifyingItemCriteria()) || !CollectionUtils.isEmpty(offer.getTargetItemCriteria());
         boolean itemLevelQualification = false;
         boolean offerCreated = false;
 
         for (PromotableOrderItem promotableOrderItem : order.getDiscountableOrderItems()) {
             if(couldOfferApplyToOrder(offerorderpromotableOrderItem)) {
                 if (!isNewFormat) {
                     //support legacy offers                   
                     PromotableCandidateItemOffer candidate = createCandidateItemOffer(qualifiedItemOffersofferorder);
                    
                     if (!candidate.getLegacyCandidateTargets().contains(promotableOrderItem)) {
                         candidate.getLegacyCandidateTargets().add(promotableOrderItem);
                     }
                     offerCreated = true;
                     continue;
                 }
                 itemLevelQualification = true;
                 break;
             }
             for (PromotableFulfillmentGroup fulfillmentGroup : order.getFulfillmentGroups()) {
                 if(couldOfferApplyToOrder(offerorderpromotableOrderItemfulfillmentGroup)) {
                     if (!isNewFormat) {
                         //support legacy offers
                         PromotableCandidateItemOffer candidate = createCandidateItemOffer(qualifiedItemOffersofferorder);
                         if (!candidate.getLegacyCandidateTargets().contains(promotableOrderItem)) {
                             candidate.getLegacyCandidateTargets().add(promotableOrderItem);
                         }
                         offerCreated = true;
                         continue;
                     }
                     itemLevelQualification = true;
                     break;
                }
            }
        }
        //Item Qualification - new for 1.5!
        if (itemLevelQualification && !offerCreated) {
            CandidatePromotionItems candidates = couldOfferApplyToOrderItems(offer,
                    order.getDiscountableOrderItems(offer.getApplyDiscountToSalePrice()));
            PromotableCandidateItemOffer candidateOffer = null;
            if (candidates.isMatchedQualifier()) {
                //we don't know the final target yet, so put null for the order item for now
                candidateOffer = createCandidateItemOffer(qualifiedItemOffersofferorder);
                candidateOffer.getCandidateQualifiersMap().putAll(candidates.getCandidateQualifiersMap());
            }
            if (candidates.isMatchedTarget() && candidates.isMatchedQualifier()) {
                if (candidateOffer == null) {
                    //we don't know the final target yet, so put null for the order item for now
                    candidateOffer = createCandidateItemOffer(qualifiedItemOffersofferorder);
                }
                candidateOffer.getCandidateTargetsMap().putAll(candidates.getCandidateTargetsMap());
            }
        }
    }
    
    
Create a candidate item offer based on the offer in question and a specific order item

Parameters:
qualifiedItemOffers the container list for candidate item offers
offer the offer in question
Returns:
the candidate item offer
            Offer offerPromotableOrder promotableOrder) {
        PromotableCandidateItemOffer promotableCandidateItemOffer =
                .createPromotableCandidateItemOffer(promotableOrderoffer);
        qualifiedItemOffers.add(promotableCandidateItemOffer);
        
        return promotableCandidateItemOffer;
    }
    
    
    /* (non-Javadoc)
     * @see org.broadleafcommerce.core.offer.service.processor.ItemOfferProcessor#applyAllItemOffers(java.util.List, java.util.List)
     */
    @Override
    public void applyAllItemOffers(List<PromotableCandidateItemOfferitemOffersPromotableOrder order) {
        // Iterate through the collection of CandidateItemOffers. Remember that each one is an offer that may apply to a
        // particular OrderItem.  Multiple CandidateItemOffers may contain a reference to the same OrderItem object.
        // The same offer may be applied to different Order Items
        
        for (PromotableCandidateItemOffer itemOffer : itemOffers) {
            if (offerMeetsSubtotalRequirements(orderitemOffer)) {
                applyItemOffer(orderitemOffer);
            }
        }
    }
    
    
    protected boolean offerMeetsSubtotalRequirements(PromotableOrder orderPromotableCandidateItemOffer itemOffer) {
        if (itemOffer.getOffer().getQualifyingItemSubTotal() == null || itemOffer.getOffer().getQualifyingItemSubTotal().lessThanOrEqual(.)) {
            return true;
        }
        //TODO:  Check subtotal requirement before continuing
           
        return false;
    }
    protected boolean isTotalitarianOfferAppliedToAnyItem(PromotableOrder order) {
        List<PromotableOrderItemPriceDetailallPriceDetails = order.getAllPromotableOrderItemPriceDetails();       
        for (PromotableOrderItemPriceDetail targetItem : allPriceDetails) {
            if (targetItem.isTotalitarianOfferApplied()) {
                return true;
            }
        }
        return false;
    }
    
    
The itemOffer has been qualified and prior methods added PromotionDiscount objects onto the ItemPriceDetail. This code will convert the PromotionDiscounts into Adjustments

Parameters:
order
itemOffer
    protected void applyAdjustments(PromotableOrder orderPromotableCandidateItemOffer itemOffer) {
        List<PromotableOrderItemPriceDetailitemPriceDetails = order.getAllPromotableOrderItemPriceDetails();
        .applyAdjustmentsForItemPriceDetails(itemOfferitemPriceDetails);
    }


    
Legacy adjustments use the stackable flag instead of item qualifiers and targets

Parameters:
order
itemOffer
    protected void applyLegacyAdjustments(PromotableOrder orderPromotableCandidateItemOffer itemOffer) {
        for (PromotableOrderItem item : itemOffer.getLegacyCandidateTargets()) {
            for (PromotableOrderItemPriceDetail itemPriceDetail : item.getPromotableOrderItemPriceDetails()) {
                if (!itemOffer.getOffer().isStackable() || !itemOffer.getOffer().isCombinableWithOtherOffers()) {
                    if (itemPriceDetail.getCandidateItemAdjustments().size() != 0) {
                        continue;
                    }
                } else {
                    if (itemPriceDetail.hasNonCombinableAdjustments()) {
                        continue;
                    }
                }
                .applyOrderItemAdjustment(itemOfferitemPriceDetail);
            }
        }
    }
     
    
Call out to extension managers. Returns true if the core processing should still be performed for the passed in offer.

Parameters:
order
itemOffer
Returns:
            PromotableCandidateItemOffer itemOffer) {
        Map<StringObjectcontextMap = new HashMap<StringObject>();
        if ( != null) {
            .getProxy().applyItemOffer(orderitemOffercontextMap);
            if (contextMap.get(.) != null) {
                // Returning false               
                return !..equals(contextMap.get(.));
            }
        }
        return .;
    }
    protected void applyItemOffer(PromotableOrder orderPromotableCandidateItemOffer itemOffer) {
        if (applyItemOfferExtension(orderitemOffer)) {
            if (.itemOfferCanBeApplied(itemOfferorder.getAllPromotableOrderItemPriceDetails())) {
                applyItemQualifiersAndTargets(itemOfferorder);
                if (itemOffer.isLegacyOffer()) {
                    applyLegacyAdjustments(orderitemOffer);
                } else {
                    applyAdjustments(orderitemOffer);
                }
            }
        }
    }

    
Some promotions can only apply to the retail price. This method determines whether retailPrice only promotions should be used instead of those that can apply to the sale price as well.

Parameters:
order
Returns:
    protected void chooseSaleOrRetailAdjustments(PromotableOrder order) {
        List<PromotableOrderItemPriceDetailitemPriceDetails = order.getAllPromotableOrderItemPriceDetails();
        for (PromotableOrderItemPriceDetail itemDetail : itemPriceDetails) {
            itemDetail.chooseSaleOrRetailAdjustments();                
        }
        mergePriceDetails(order);
    }

    
Checks to see if any priceDetails need to be combined and if so, combines them.

Parameters:
order
Returns:
    protected void mergePriceDetails(PromotableOrder order) {
        List<PromotableOrderItemitems = order.getAllOrderItems();
        for (PromotableOrderItem item : items) {
            item.mergeLikeDetails();
        }
    }
   
    protected void applyItemQualifiersAndTargets(PromotableCandidateItemOffer itemOfferPromotableOrder order) {
        if (itemOffer.isLegacyOffer()) {
            .warn("The item offer with id " + itemOffer.getOffer().getId() + " is a legacy offer which means that it" +
            		" does not have any item qualifier criteria AND does not have any target item criteria. As a result," +
            		" we are skipping the marking of qualifiers and targets which will cause issues if you are relying on" +
            		" 'maxUsesPerOrder' behavior. To resolve this, qualifier criteria is not required but you must at least" +
            		" create some target item criteria for this offer.");
            return;
        } else {
            markQualifiersAndTargets(orderitemOffer);
        }
    }
        List<PromotableOrderItemPriceDetailitemPriceDetails = new ArrayList<PromotableOrderItemPriceDetail>();
        for (PromotableOrderItem item : items) {
            for (PromotableOrderItemPriceDetail detail : item.getPromotableOrderItemPriceDetails()) {
                itemPriceDetails.add(detail);
            }
        }
        return itemPriceDetails;
    }

    
Loop through ItemCriteria and mark qualifiers required to give the promotion to 1 or more targets.

Parameters:
itemOffer
order
Returns:
    protected boolean markQualifiers(PromotableCandidateItemOffer itemOfferPromotableOrder order) {
        for (OfferItemCriteria itemCriteria : itemOffer.getCandidateQualifiersMap().keySet()) {
            List<PromotableOrderItempromotableItems = itemOffer.getCandidateQualifiersMap().get(itemCriteria);
            List<PromotableOrderItemPriceDetailpriceDetails = buildPriceDetailListFromOrderItems(promotableItems);
            int qualifierQtyNeeded = .markQualifiersForCriteria(itemOfferitemCriteriapriceDetails);
            if (qualifierQtyNeeded != 0) {
                return false;
            }
        }
        return true;
    }
    protected boolean markTargets(PromotableCandidateItemOffer itemOfferPromotableOrder orderOrderItem relatedQualifier) {
        return markTargets(itemOfferorderrelatedQualifierfalse);
    }
    
    
Loop through ItemCriteria and mark targets that can get this promotion to give the promotion to 1 or more targets.

Parameters:
itemOffer
order
Returns:
    public boolean markTargets(PromotableCandidateItemOffer itemOfferPromotableOrder orderOrderItem relatedQualifier,
            boolean checkOnly) {
        Offer promotion = itemOffer.getOffer();
        if (itemOffer.getCandidateTargetsMap().keySet().isEmpty()) {
            return false;
        }
        
        OrderItem relatedQualifierRoot = .findRelatedQualifierRoot(relatedQualifier);
        for (OfferItemCriteria itemCriteria : itemOffer.getCandidateTargetsMap().keySet()) {
            List<PromotableOrderItempromotableItems = itemOffer.getCandidateTargetsMap().get(itemCriteria);
            List<PromotableOrderItemPriceDetailpriceDetails = buildPriceDetailListFromOrderItems(promotableItems);
            .sortTargetItemDetails(priceDetailsitemOffer.getOffer().getApplyDiscountToSalePrice());
            int targetQtyNeeded = itemCriteria.getQuantity();
            targetQtyNeeded = .markTargetsForCriteria(itemOfferrelatedQualifiercheckOnlypromotionrelatedQualifierRootitemCriteriapriceDetailstargetQtyNeeded);
            if (targetQtyNeeded != 0) {
                return false;
            }
        }
        if (!checkOnly) {
            itemOffer.addUse();
        }
        return true;
    }

    
When the org.broadleafcommerce.core.offer.domain.Offer.getRequiresRelatedTargetAndQualifiers() flag is set to true, we must make sure that we identify qualifiers and targets together, as they must be related to each other based on the org.broadleafcommerce.core.order.domain.OrderItem.getParentOrderItem() / org.broadleafcommerce.core.order.domain.OrderItem.getChildOrderItems() attributes.

Parameters:
itemOffer
order
Returns:
whether or not a suitable qualifier/target pair was found and marked
    protected boolean markRelatedQualifiersAndTargets(PromotableCandidateItemOffer itemOfferPromotableOrder order) {
        OrderItemHolder orderItemHolder = new OrderItemHolder(null);
        for (Entry<OfferItemCriteriaList<PromotableOrderItem>> entry : itemOffer.getCandidateQualifiersMap().entrySet()) {
            OfferItemCriteria itemCriteria = entry.getKey();
            List<PromotableOrderItempromotableItems = entry.getValue();
            List<PromotableOrderItemPriceDetailpriceDetails = buildPriceDetailListFromOrderItems(promotableItems);
            int qualifierQtyNeeded = .markRelatedQualifiersAndTargetsForItemCriteria(itemOfferorder,
                    orderItemHolderitemCriteriapriceDetailsthis);
            if (qualifierQtyNeeded != 0) {
                return false;
            }
        }
        
        return markTargets(itemOfferorderorderItemHolder.getOrderItem(), false);
    }
    
    @Override
    public void filterOffers(PromotableOrder orderList<OfferfilteredOffersList<PromotableCandidateOrderOfferqualifiedOrderOffersList<PromotableCandidateItemOfferqualifiedItemOffers) {
        // set order subTotal price to total item price without adjustments
        for (Offer offer : filteredOffers) {            
            if(offer.getType().equals(.)){
                filterOrderLevelOffer(orderqualifiedOrderOffersoffer);
            } else if(offer.getType().equals(.)){
                filterItemLevelOffer(orderqualifiedItemOffersoffer);
            }
        }
    }
    
    
Provide an opportunity to for modules to override the potentialSavingsCalculation

Parameters:
itemOffer
item
quantity
Returns:
            PromotableOrderItem itemint quantity) {
        if ( != null) {
            Map<String,ObjectcontextMap = new HashMap<String,Object>();
            .getProxy().calculatePotentialSavings(itemOfferitemquantitycontextMap);
            // If the extensionHandler added a savings element to the map, then return it 
            Object o = contextMap.get("savings");
            if (o != null && o instanceof Money) {
                return (Moneyo;
            }
            
            // If the extensionHandler added a quantity element to the map, then return it 
            o = contextMap.get("quantity");
            if (o != null && o instanceof Integer) {
                quantity = ((Integero).intValue();
            }
        }
        return itemOffer.calculateSavingsForOrderItem(itemquantity);
    }

    
This method determines the potential savings for each item offer as if it was the only item offer being applied.

Parameters:
itemOffers
order
    protected void calculatePotentialSavings(List<PromotableCandidateItemOfferitemOffersPromotableOrder order) {
        if (itemOffers.size() > 1) {
            for (PromotableCandidateItemOffer itemOffer : itemOffers) {
                Money potentialSavings = new Money(order.getOrderCurrency());
                if (itemOffer.isLegacyOffer()) {
                    for (PromotableOrderItem item : itemOffer.getLegacyCandidateTargets()) {
                        potentialSavings = potentialSavings.add(
                                itemOffer.calculateSavingsForOrderItem(itemitem.getQuantity()));
                    }
                } else {
                    markQualifiersAndTargets(orderitemOffer);
                    for (PromotableOrderItemPriceDetail detail : order.getAllPromotableOrderItemPriceDetails()) {
                        PromotableOrderItem item = detail.getPromotableOrderItem();
                        for (PromotionDiscount discount : detail.getPromotionDiscounts()) {
                            Money itemSavings = calculatePotentialSavingsForOrderItem(itemOfferitemdiscount.getQuantity());
                            potentialSavings = potentialSavings.add(itemSavings);
                        }
                        // Reset state back for next offer
                        detail.getPromotionDiscounts().clear();
                        detail.getPromotionQualifiers().clear();
                    }
                }
                itemOffer.setPotentialSavings(potentialSavings);
            }
        }
    }
    protected void markQualifiersAndTargets(PromotableOrder orderPromotableCandidateItemOffer itemOffer) {
        boolean matchFound = true;
        if (itemOffer.isLegacyOffer()) {
            return;
        }
        int count = 1;
        do {
            boolean qualifiersFound = false;
            boolean targetsFound = false;
            if (itemOffer.getOffer().getRequiresRelatedTargetAndQualifiers()) {
                boolean qualifiersAndTargetsFound = markRelatedQualifiersAndTargets(itemOfferorder);
                qualifiersFound = qualifiersAndTargetsFound;
                targetsFound = qualifiersAndTargetsFound;
            } else {
                qualifiersFound = markQualifiers(itemOfferorder);
                targetsFound = markTargets(itemOfferordernull);
            }
            if (qualifiersFound && targetsFound) {
                finalizeQuantities(order.getAllPromotableOrderItemPriceDetails());
            } else {
                matchFound = false;
                break;
            }
            // If we found a match, try again to see if the promotion can be applied again.
        } while (matchFound);
    }
    protected boolean offerListStartsWithNonCombinable(List<PromotableCandidateItemOfferofferList) {
        if (offerList.size() > 1) {
            PromotableCandidateItemOffer offer = offerList.get(0);
            if (offer.getOffer().isTotalitarianOffer() || !offer.getOffer().isCombinableWithOtherOffers()) {
                return true;
            }
        }
        return false;
    }

    
This method could be overridden to potentially run all permutations of offers. A reasonable alternative is to have a permutation with nonCombinable offers and another with combinable offers.

Parameters:
offers
Returns:
            List<PromotableCandidateItemOfferoffers) {
        List<List<PromotableCandidateItemOffer>> listOfOfferLists = new ArrayList<List<PromotableCandidateItemOffer>>();
        // add the default list
        listOfOfferLists.add(offers);
        if (offerListStartsWithNonCombinable(offers)) {
            List<PromotableCandidateItemOfferlistWithoutTotalitarianOrNonCombinables =
                    new ArrayList<PromotableCandidateItemOffer>(offers);
            Iterator<PromotableCandidateItemOfferofferIterator = listWithoutTotalitarianOrNonCombinables.iterator();
            while (offerIterator.hasNext()) {
                PromotableCandidateItemOffer offer = offerIterator.next();
                if (offer.getOffer().isTotalitarianOffer() || !offer.getOffer().isCombinableWithOtherOffers()) {
                    offerIterator.remove();
                }
            }
            if (listWithoutTotalitarianOrNonCombinables.size() > 0) {
                listOfOfferLists.add(listWithoutTotalitarianOrNonCombinables);
            }
        }
        return listOfOfferLists;
    }
    protected void restPriceDetails(PromotableOrderItem item) {
        item.resetPriceDetails();
        if ( != null) {
            .getProxy().resetPriceDetails(item);
        }
    }
    protected void determineBestPermutation(List<PromotableCandidateItemOfferitemOffersPromotableOrder order) {
        List<List<PromotableCandidateItemOffer>> permutations = buildItemOfferPermutations(itemOffers);
        List<PromotableCandidateItemOfferbestOfferList = null;
        Money lowestSubtotal = null;
        if (permutations.size() > 1) {
            for (List<PromotableCandidateItemOfferofferList : permutations) {
                applyAllItemOffers(offerListorder);
                chooseSaleOrRetailAdjustments(order);
                Money testSubtotal = order.calculateSubtotalWithAdjustments();
                if (lowestSubtotal == null || testSubtotal.lessThan(lowestSubtotal)) {
                    lowestSubtotal = testSubtotal;
                    bestOfferList = offerList;
                }
                // clear price details
                for (PromotableOrderItem item : order.getDiscountableOrderItems()) {
                    item.resetPriceDetails();
                }
            }
        } else {
            bestOfferList = permutations.get(0);
        }
        applyAllItemOffers(bestOfferListorder);
    }
    @Override
    @SuppressWarnings("unchecked")
            List<PromotableCandidateOrderOfferqualifiedOrderOffers,
            List<PromotableCandidateItemOfferqualifiedItemOffers) {
        if (!qualifiedItemOffers.isEmpty()) {
            calculatePotentialSavings(qualifiedItemOffersorder);
            
            //after savings have been calculated, uses will have been marked on offers which can effect
            //the actual application of those offers. Thus the uses for each item offer needs to be reset
            for (PromotableCandidateItemOffer itemOffer : qualifiedItemOffers) {
                itemOffer.resetUses();
            }
            
            // Sort order item offers by priority and potential total discount
            Collections.sort(qualifiedItemOffers.);
            
            if (qualifiedItemOffers.size() > 1) {
                determineBestPermutation(qualifiedItemOffersorder);
            } else {
                applyAllItemOffers(qualifiedItemOffersorder);
            }
        }
        chooseSaleOrRetailAdjustments(order);
        if ( != null) {
        }
        if (!qualifiedOrderOffers.isEmpty()) {
            // Sort order offers by priority and discount
            Collections.sort(qualifiedOrderOffers.);
            applyAllOrderOffers(qualifiedOrderOffersorder);
        }
        
        // TODO: only do this if absolutely required.    If you find one that no longer qualifies, then 
        // pull it out and reapply.
        if (!qualifiedOrderOffers.isEmpty() && !qualifiedItemOffers.isEmpty()) {
            List<PromotableCandidateOrderOfferfinalQualifiedOrderOffers = new ArrayList<PromotableCandidateOrderOffer>();
            order.removeAllCandidateOrderOfferAdjustments();
            for (PromotableCandidateOrderOffer candidateOrderOffer : qualifiedOrderOffers) {
                // recheck the list of order offers and verify if they still apply with the new subtotal
                /*
                 * Note - there is an edge case possibility where this logic would miss an order promotion
                 * that had a subtotal requirement that was missed because of item deductions, but without
                 * the item deductions, the order promotion would have been included and ended up giving the 
                 * customer a better deal than the item deductions.
                 */
                if (couldOfferApplyToOrder(candidateOrderOffer.getOffer(), order)) {
                    finalQualifiedOrderOffers.add(candidateOrderOffer);
                }
            }
            // Sort order offers by priority and discount
            Collections.sort(finalQualifiedOrderOffers.);
            if (!finalQualifiedOrderOffers.isEmpty()) {
                applyAllOrderOffers(finalQualifiedOrderOffersorder);
                order.setOrderSubTotalToPriceWithAdjustments();
            }  
        }
    }
New to GrepCode? Check out our FAQ X