Package org.broadleafcommerce.core.offer.service

Source Code of org.broadleafcommerce.core.offer.service.OfferServiceUtilitiesImpl

/*
* #%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;

import org.broadleafcommerce.common.extension.ExtensionResultHolder;
import org.broadleafcommerce.common.money.Money;
import org.broadleafcommerce.core.offer.dao.OfferDao;
import org.broadleafcommerce.core.offer.domain.Offer;
import org.broadleafcommerce.core.offer.domain.OfferItemCriteria;
import org.broadleafcommerce.core.offer.domain.OrderItemPriceDetailAdjustment;
import org.broadleafcommerce.core.offer.service.discount.PromotionDiscount;
import org.broadleafcommerce.core.offer.service.discount.PromotionQualifier;
import org.broadleafcommerce.core.offer.service.discount.domain.PromotableCandidateItemOffer;
import org.broadleafcommerce.core.offer.service.discount.domain.PromotableItemFactory;
import org.broadleafcommerce.core.offer.service.discount.domain.PromotableOrder;
import org.broadleafcommerce.core.offer.service.discount.domain.PromotableOrderItem;
import org.broadleafcommerce.core.offer.service.discount.domain.PromotableOrderItemPriceDetail;
import org.broadleafcommerce.core.offer.service.discount.domain.PromotableOrderItemPriceDetailAdjustment;
import org.broadleafcommerce.core.offer.service.processor.ItemOfferMarkTargets;
import org.broadleafcommerce.core.order.domain.Order;
import org.broadleafcommerce.core.order.domain.OrderItem;
import org.broadleafcommerce.core.order.domain.OrderItemContainer;
import org.broadleafcommerce.core.order.domain.OrderItemPriceDetail;
import org.broadleafcommerce.core.order.domain.OrderItemQualifier;
import org.broadleafcommerce.core.order.domain.dto.OrderItemHolder;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import javax.annotation.Resource;

/**
*
* @author bpolster
*
*/
@Service("blOfferServiceUtilities")
public class OfferServiceUtilitiesImpl implements OfferServiceUtilities {

    @Resource(name = "blPromotableItemFactory")
    protected PromotableItemFactory promotableItemFactory;

    @Resource(name = "blOfferDao")
    protected OfferDao offerDao;

    @Resource(name = "blOfferServiceExtensionManager")
    protected OfferServiceExtensionManager extensionManager;

    @Override
    public void sortTargetItemDetails(List<PromotableOrderItemPriceDetail> itemPriceDetails, boolean applyToSalePrice) {
        Collections.sort(itemPriceDetails, getPromotableItemComparator(applyToSalePrice));
    }

    @Override
    public void sortQualifierItemDetails(List<PromotableOrderItemPriceDetail> itemPriceDetails, boolean applyToSalePrice) {
        Collections.sort(itemPriceDetails, getPromotableItemComparator(applyToSalePrice));
    }

    protected Comparator<PromotableOrderItemPriceDetail> getPromotableItemComparator(final boolean applyToSalePrice) {
        return new Comparator<PromotableOrderItemPriceDetail>() {

            @Override
            public int compare(PromotableOrderItemPriceDetail o1, PromotableOrderItemPriceDetail o2) {
                Money price = o1.getPromotableOrderItem().getPriceBeforeAdjustments(applyToSalePrice);
                Money price2 = o2.getPromotableOrderItem().getPriceBeforeAdjustments(applyToSalePrice);

                // highest amount first
                return price2.compareTo(price);
            }
        };
    }

    @Override
    public OrderItem findRelatedQualifierRoot(OrderItem relatedQualifier) {
        OrderItem relatedQualifierRoot = null;
        if (relatedQualifier != null) {
            relatedQualifierRoot = relatedQualifier;
            while (relatedQualifierRoot.getParentOrderItem() != null) {
                relatedQualifierRoot = relatedQualifierRoot.getParentOrderItem();
            }
        }
        return relatedQualifierRoot;
    }

    @Override
    public boolean itemOfferCanBeApplied(PromotableCandidateItemOffer itemOffer, List<PromotableOrderItemPriceDetail> details) {

        for (PromotableOrderItemPriceDetail detail : details) {
            for (PromotableOrderItemPriceDetailAdjustment adjustment : detail.getCandidateItemAdjustments()) {
                if (adjustment.isTotalitarian() || itemOffer.getOffer().isTotalitarianOffer()) {
                    // A totalitarian offer has already been applied or this offer is totalitarian
                    // and another offer was already applied.
                    return false;
                } else if (itemOffer.isLegacyOffer()) {
                    continue;
                } else if (!adjustment.isCombinable() || !itemOffer.getOffer().isCombinableWithOtherOffers()) {
                    // A nonCombinable offer has been applied or this is a non-combinable offer
                    // and adjustments have already been applied.
                    return false;
                }
            }
        }
        return true;
    }

    @Override
    public int markQualifiersForCriteria(PromotableCandidateItemOffer itemOffer, OfferItemCriteria itemCriteria,
            List<PromotableOrderItemPriceDetail> priceDetails) {

        sortQualifierItemDetails(priceDetails, itemOffer.getOffer().getApplyDiscountToSalePrice());

        // Calculate the number of qualifiers needed that will not receive the promotion. 
        // These will be reserved first before the target is assigned.
        int qualifierQtyNeeded = itemCriteria.getQuantity();

        for (PromotableOrderItemPriceDetail detail : priceDetails) {

            // Mark Qualifiers
            if (qualifierQtyNeeded > 0) {
                int itemQtyAvailableToBeUsedAsQualifier = detail.getQuantityAvailableToBeUsedAsQualifier(itemOffer);
                if (itemQtyAvailableToBeUsedAsQualifier > 0) {
                    int qtyToMarkAsQualifier = Math.min(qualifierQtyNeeded, itemQtyAvailableToBeUsedAsQualifier);
                    qualifierQtyNeeded -= qtyToMarkAsQualifier;
                    detail.addPromotionQualifier(itemOffer, itemCriteria, qtyToMarkAsQualifier);
                }
            }

            if (qualifierQtyNeeded == 0) {
                break;
            }
        }
        return qualifierQtyNeeded;
    }

    @Override
    public int markTargetsForCriteria(PromotableCandidateItemOffer itemOffer, OrderItem relatedQualifier,
            boolean checkOnly, Offer promotion, OrderItem relatedQualifierRoot, OfferItemCriteria itemCriteria,
            List<PromotableOrderItemPriceDetail> priceDetails, int targetQtyNeeded) {
        for (PromotableOrderItemPriceDetail priceDetail : priceDetails) {
            if (relatedQualifier != null) {
                // We need to make sure that this item is either a parent, child, or the same as the qualifier root
                OrderItem thisItem = priceDetail.getPromotableOrderItem().getOrderItem();
                if (!relatedQualifierRoot.isAParentOf(thisItem) && !thisItem.isAParentOf(relatedQualifierRoot) &&
                        !thisItem.equals(relatedQualifierRoot)) {
                    continue;
                }
            }

            int itemQtyAvailableToBeUsedAsTarget = priceDetail.getQuantityAvailableToBeUsedAsTarget(itemOffer);
            if (itemQtyAvailableToBeUsedAsTarget > 0) {
                if (promotion.isUnlimitedUsePerOrder() || (itemOffer.getUses() < promotion.getMaxUsesPerOrder())) {
                    int qtyToMarkAsTarget = Math.min(targetQtyNeeded, itemQtyAvailableToBeUsedAsTarget);
                    targetQtyNeeded -= qtyToMarkAsTarget;
                    if (!checkOnly) {
                        priceDetail.addPromotionDiscount(itemOffer, itemCriteria, qtyToMarkAsTarget);
                    }
                }
            }

            if (targetQtyNeeded == 0) {
                break;
            }
        }
        return targetQtyNeeded;
    }

    @Override
    public int markRelatedQualifiersAndTargetsForItemCriteria(PromotableCandidateItemOffer itemOffer, PromotableOrder order,
            OrderItemHolder orderItemHolder, OfferItemCriteria itemCriteria,
            List<PromotableOrderItemPriceDetail> priceDetails, ItemOfferMarkTargets itemOfferMarkTargets) {
        sortQualifierItemDetails(priceDetails,
                itemOffer.getOffer().getApplyDiscountToSalePrice());

        // Calculate the number of qualifiers needed that will not receive the promotion. 
        // These will be reserved first before the target is assigned.
        int qualifierQtyNeeded = itemCriteria.getQuantity();

        for (PromotableOrderItemPriceDetail detail : priceDetails) {
            OrderItem oi = detail.getPromotableOrderItem().getOrderItem();

            if (qualifierQtyNeeded > 0) {
                int itemQtyAvailableToBeUsedAsQualifier = detail.getQuantityAvailableToBeUsedAsQualifier(itemOffer);
                if (itemQtyAvailableToBeUsedAsQualifier > 0) {
                    // We have found a qualifier that meets this offer criteria. First, we'll save some state that we
                    // might need in the future.
                    OfferItemCriteria previousQualifierCriteria = null;
                    for (PromotionQualifier possibleQualifier : detail.getPromotionQualifiers()) {
                        if (possibleQualifier.getPromotion().equals(itemOffer.getOffer())) {
                            previousQualifierCriteria = possibleQualifier.getItemCriteria();
                            break;
                        }
                    }

                    // Go ahead and mark this item as a qualifier
                    int qtyToMarkAsQualifier = Math.min(qualifierQtyNeeded, itemQtyAvailableToBeUsedAsQualifier);
                    qualifierQtyNeeded -= qtyToMarkAsQualifier;
                    PromotionQualifier pq = detail.addPromotionQualifier(itemOffer, itemCriteria, qtyToMarkAsQualifier);

                    // Now, we need to see if there exists a target(s) that is suitable for this qualifier due to
                    // the relationship flag. If we are on the last qualifier required for this offer, we want to
                    // actually go ahead and mark the target that we'll be using. Otherwise, we just want to check
                    // that there is an eligible target(s) and continue on.
                    if (itemOfferMarkTargets.markTargets(itemOffer, order, oi, true)) {
                        // We found a target. Let's save this related order item used as the qualifier in case
                        // we succeed
                        orderItemHolder.setOrderItem(oi);
                    } else {
                        // If we didn't find a target, we need to roll back how we marked this item as a qualifier.
                        qualifierQtyNeeded += qtyToMarkAsQualifier;
                        if (pq.getQuantity() == qtyToMarkAsQualifier) {
                            detail.getPromotionQualifiers().remove(pq);
                        } else {
                            pq.setItemCriteria(previousQualifierCriteria);
                            pq.setQuantity(pq.getQuantity() - qtyToMarkAsQualifier);
                        }
                    }
                }
            }

            if (qualifierQtyNeeded == 0) {
                break;
            }
        }
        return qualifierQtyNeeded;
    }

    @Override
    public void applyAdjustmentsForItemPriceDetails(PromotableCandidateItemOffer itemOffer, List<PromotableOrderItemPriceDetail> itemPriceDetails) {
        for (PromotableOrderItemPriceDetail itemPriceDetail : itemPriceDetails) {
            for (PromotionDiscount discount : itemPriceDetail.getPromotionDiscounts()) {
                if (discount.getPromotion().equals(itemOffer.getOffer())) {
                    if (itemOffer.getOffer().isTotalitarianOffer() || !itemOffer.getOffer().isCombinableWithOtherOffers()) {
                        // We've decided to apply this adjustment but if it doesn't actually reduce
                        // the value of the item
                        if (adjustmentIsNotGoodEnoughToBeApplied(itemOffer, itemPriceDetail)) {
                            break;
                        }

                    }
                    applyOrderItemAdjustment(itemOffer, itemPriceDetail);
                    break;
                }
            }
        }
    }

    /**
     * The adjustment might not be better than the sale price.
     * @param itemOffer
     * @param detail
     * @return
     */
    protected boolean adjustmentIsNotGoodEnoughToBeApplied(PromotableCandidateItemOffer itemOffer,
            PromotableOrderItemPriceDetail detail) {
        if (!itemOffer.getOffer().getApplyDiscountToSalePrice()) {
            Money salePrice = detail.getPromotableOrderItem().getSalePriceBeforeAdjustments();
            Money retailPrice = detail.getPromotableOrderItem().getRetailPriceBeforeAdjustments();
            Money savings = itemOffer.calculateSavingsForOrderItem(detail.getPromotableOrderItem(), 1);
            if (salePrice != null) {
                if (salePrice.lessThan(retailPrice.subtract(savings))) {
                    // Not good enough
                    return true;
                }
            }
        }
        return false;
    }

    @Override
    public void applyOrderItemAdjustment(PromotableCandidateItemOffer itemOffer,
            PromotableOrderItemPriceDetail itemPriceDetail) {
        PromotableOrderItemPriceDetailAdjustment promotableOrderItemPriceDetailAdjustment =
                promotableItemFactory.createPromotableOrderItemPriceDetailAdjustment(itemOffer, itemPriceDetail);
        itemPriceDetail.addCandidateItemPriceDetailAdjustment(promotableOrderItemPriceDetailAdjustment);
    }

    @Override
    public List<OrderItem> buildOrderItemList(Order order) {
        List<OrderItem> orderItemList = new ArrayList<OrderItem>();
        for (OrderItem currentItem : order.getOrderItems()) {
            if (currentItem instanceof OrderItemContainer) {
                OrderItemContainer container = (OrderItemContainer) currentItem;
                if (container.isPricingAtContainerLevel()) {
                    orderItemList.add(currentItem);
                } else {
                    for (OrderItem containedItem : container.getOrderItems()) {
                        orderItemList.add(containedItem);
                    }
                }
            } else {
                orderItemList.add(currentItem);
            }
        }

        return orderItemList;
    }

    @Override
    public Map<OrderItem, PromotableOrderItem> buildPromotableItemMap(PromotableOrder promotableOrder) {
        Map<OrderItem, PromotableOrderItem> promotableItemMap = new HashMap<OrderItem, PromotableOrderItem>();
        for (PromotableOrderItem item : promotableOrder.getDiscountableOrderItems()) {
            promotableItemMap.put(item.getOrderItem(), item);
        }
        return promotableItemMap;
    }

    @Override
    public Map<Long, OrderItemPriceDetailAdjustment> buildItemDetailAdjustmentMap(OrderItemPriceDetail itemDetail) {
        Map<Long, OrderItemPriceDetailAdjustment> itemAdjustmentMap = new HashMap<Long, OrderItemPriceDetailAdjustment>();
        for (OrderItemPriceDetailAdjustment adjustment : itemDetail.getOrderItemPriceDetailAdjustments()) {
            itemAdjustmentMap.put(adjustment.getOffer().getId(), adjustment);
        }
        return itemAdjustmentMap;
    }

    @Override
    public void updatePriceDetail(OrderItemPriceDetail itemDetail,
            PromotableOrderItemPriceDetail promotableDetail) {
        Map<Long, OrderItemPriceDetailAdjustment> itemAdjustmentMap =
                buildItemDetailAdjustmentMap(itemDetail);

        if (itemDetail.getQuantity() != promotableDetail.getQuantity()) {
            itemDetail.setQuantity(promotableDetail.getQuantity());
        }

        if (promotableDetail.isAdjustmentsFinalized()) {
            itemDetail.setUseSalePrice(promotableDetail.useSaleAdjustments());
        }

        for (PromotableOrderItemPriceDetailAdjustment adjustment : promotableDetail.getCandidateItemAdjustments()) {
            OrderItemPriceDetailAdjustment itemAdjustment = itemAdjustmentMap.remove(adjustment.getOfferId());
            if (itemAdjustment != null) {
                // Update existing adjustment
                if (!itemAdjustment.getValue().equals(adjustment.getAdjustmentValue())) {
                    updateItemAdjustment(itemAdjustment, adjustment);
                }
            } else {
                // Add a new adjustment
                final OrderItemPriceDetailAdjustment newItemAdjustment;
                ExtensionResultHolder resultHolder = new ExtensionResultHolder();

                if (extensionManager != null) {
                    extensionManager.getProxy().createOrderItemPriceDetailAdjustment(resultHolder, itemDetail);
                }
                if (resultHolder != null && resultHolder.getContextMap().containsKey("OrderItemPriceDetailAdjustment")) {
                    newItemAdjustment = (OrderItemPriceDetailAdjustment) resultHolder.getContextMap().get("OrderItemPriceDetailAdjustment");
                } else {
                    newItemAdjustment = offerDao.createOrderItemPriceDetailAdjustment();
                }
                newItemAdjustment.init(itemDetail, adjustment.getOffer(), null);
                updateItemAdjustment(newItemAdjustment, adjustment);
                itemDetail.getOrderItemPriceDetailAdjustments().add(newItemAdjustment);
            }
        }

        if (itemAdjustmentMap.size() > 0) {
            // Remove adjustments that were on the order item but no longer needed.
            List<Long> adjustmentIdsToRemove = new ArrayList<Long>();
            for (OrderItemPriceDetailAdjustment adjustmentToRemove : itemAdjustmentMap.values()) {
                adjustmentIdsToRemove.add(adjustmentToRemove.getOffer().getId());
            }

            Iterator<OrderItemPriceDetailAdjustment> iterator = itemDetail.getOrderItemPriceDetailAdjustments().iterator();
            while (iterator.hasNext()) {
                OrderItemPriceDetailAdjustment adj = iterator.next();
                if (adjustmentIdsToRemove.contains(adj.getOffer().getId())) {
                    iterator.remove();
                }
            }
        }
    }

    protected void updateItemAdjustment(OrderItemPriceDetailAdjustment itemAdjustment,
            PromotableOrderItemPriceDetailAdjustment promotableAdjustment) {
        itemAdjustment.setValue(promotableAdjustment.getAdjustmentValue());
        itemAdjustment.setSalesPriceValue(promotableAdjustment.getSaleAdjustmentValue());
        itemAdjustment.setRetailPriceValue(promotableAdjustment.getRetailAdjustmentValue());
        itemAdjustment.setAppliedToSalePrice(promotableAdjustment.isAppliedToSalePrice());
    }

    @Override
    public void removeUnmatchedPriceDetails(Map<Long, ? extends OrderItemPriceDetail> unmatchedDetailsMap,
            Iterator<? extends OrderItemPriceDetail> pdIterator) {
        while (pdIterator.hasNext()) {
            OrderItemPriceDetail currentDetail = pdIterator.next();
            if (unmatchedDetailsMap.containsKey(currentDetail.getId())) {
                pdIterator.remove();
            }
        }
    }

    @Override
    public void removeUnmatchedQualifiers(Map<Long, ? extends OrderItemQualifier> unmatchedQualifiersMap,
            Iterator<? extends OrderItemQualifier> qIterator) {
        while (qIterator.hasNext()) {
            OrderItemQualifier currentQualifier = qIterator.next();
            if (unmatchedQualifiersMap.containsKey(currentQualifier.getId())) {
                qIterator.remove();
            }
        }
    }

    public PromotableItemFactory getPromotableItemFactory() {
        return promotableItemFactory;
    }

    public void setPromotableItemFactory(PromotableItemFactory promotableItemFactory) {
        this.promotableItemFactory = promotableItemFactory;
    }

    public OfferDao getOfferDao() {
        return offerDao;
    }

    public void setOfferDao(OfferDao offerDao) {
        this.offerDao = offerDao;
    }

}
TOP

Related Classes of org.broadleafcommerce.core.offer.service.OfferServiceUtilitiesImpl

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.