@@ -24,7 +24,6 @@
import org .broadleafcommerce .core .offer .domain .OrderItemAdjustment ;
import org .broadleafcommerce .core .offer .service .discount .CandidatePromotionItems ;
import org .broadleafcommerce .core .offer .service .discount .ItemOfferComparator ;
import org .broadleafcommerce .core .offer .service .discount .OrderItemPriceComparator ;
import org .broadleafcommerce .core .offer .service .discount .PromotionDiscount ;
import org .broadleafcommerce .core .offer .service .discount .domain .PromotableCandidateItemOffer ;
import org .broadleafcommerce .core .offer .service .discount .domain .PromotableCandidateOrderOffer ;
@@ -38,6 +37,7 @@
import java .util .ArrayList ;
import java .util .Collections ;
import java .util .Comparator ;
import java .util .List ;
/**
@@ -52,7 +52,8 @@ public class ItemOfferProcessorImpl extends OrderOfferProcessorImpl implements I
/* (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)
*/
public void filterItemLevelOffer (PromotableOrder order , List <PromotableCandidateItemOffer > qualifiedItemOffers , Offer offer ) {
@ Override
public void filterItemLevelOffer (PromotableOrder order , List <PromotableCandidateItemOffer > qualifiedItemOffers , Offer offer ) {
boolean isNewFormat = (offer .getQualifyingItemCriteria () != null && offer .getQualifyingItemCriteria ().size () > 0 ) || offer .getTargetItemCriteria () != null ;
boolean itemLevelQualification = false ;
boolean offerCreated = false ;
@@ -101,7 +102,7 @@ public void filterItemLevelOffer(PromotableOrder order, List<PromotableCandidate
candidateOffer = createCandidateItemOffer (qualifiedItemOffers , offer , null );
}
for (PromotableOrderItem candidateItem : candidates .getCandidateTargets ()) {
PromotableCandidateItemOffer itemOffer = ( PromotableCandidateItemOffer ) candidateOffer .clone ();
PromotableCandidateItemOffer itemOffer = candidateOffer .clone ();
itemOffer .setOrderItem (candidateItem );
candidateItem .addCandidateItemOffer (itemOffer );
}
@@ -135,6 +136,7 @@ protected PromotableCandidateItemOffer createCandidateItemOffer(List<PromotableC
/* (non-Javadoc)
* @see org.broadleafcommerce.core.offer.service.processor.ItemOfferProcessor#applyAllItemOffers(java.util.List, java.util.List)
*/
@ Override
public boolean applyAllItemOffers (List <PromotableCandidateItemOffer > itemOffers , PromotableOrder 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.
@@ -153,6 +155,8 @@ public boolean applyAllItemOffers(List<PromotableCandidateItemOffer> itemOffers,
isLegacyFormat = true ;
appliedItemOffersCount = applyLegacyAdjustments (appliedItemOffersCount , itemOffer , beforeCount , orderItem );
} else {
// TODO: Add filter for item-subtotal
skipOfferIfSubtotalRequirementNotMet (order , itemOffer );
appliedItemOffersCount = applyAdjustments (order , appliedItemOffersCount , itemOffer , beforeCount );
}
}
@@ -166,6 +170,74 @@ public boolean applyAllItemOffers(List<PromotableCandidateItemOffer> itemOffers,
}
return itemOffersApplied ;
}
protected boolean skipOfferIfSubtotalRequirementNotMet (PromotableOrder order , PromotableCandidateItemOffer itemOffer ) {
if (itemOffer .getOffer ().getQualifyingItemSubTotal () == null || itemOffer .getOffer ().getQualifyingItemSubTotal ().lessThanOrEqual (Money .ZERO )) {
return false ;
}
/*
boolean notCombinableOfferApplied = false;
boolean offerApplied = false;
List<PromotableOrderItem> allSplitItems = order.getAllSplitItems();
for (PromotableOrderItem targetItem : allSplitItems) {
notCombinableOfferApplied = targetItem.isNotCombinableOfferApplied();
if (!offerApplied) {
offerApplied = targetItem.isHasOrderItemAdjustments();
}
if (notCombinableOfferApplied) {
break;
}
}
if (
!notCombinableOfferApplied && (
(
(itemOffer.getOffer().isCombinableWithOtherOffers() || itemOffer.getOffer().isTotalitarianOffer() == null || !itemOffer.getOffer().isTotalitarianOffer())
//&& itemOffer.getOffer().isStackable()
)
|| !offerApplied
)
)
{
// At this point, we should not have any official adjustment on the order
// for this item.
applyItemQualifiersAndTargets(itemOffer, order);
allSplitItems = order.getAllSplitItems();
for (PromotableOrderItem splitItem : allSplitItems) {
for (PromotionDiscount discount : splitItem.getPromotionDiscounts()) {
if (discount.getPromotion().equals(itemOffer.getOffer())) {
applyOrderItemAdjustment(itemOffer, splitItem);
break;
}
}
}
}
// check if not combinable offer is better than sale price; if no, remove the not combinable offer so
// that another offer may be applied to the item
if ((!itemOffer.getOffer().isCombinableWithOtherOffers() || (itemOffer.getOffer().isTotalitarianOffer() != null && itemOffer.getOffer().isTotalitarianOffer())) && appliedItemOffersCount > beforeCount) {
Money adjustmentTotal = new Money(0D);
Money saleTotal = new Money(0D);
for (PromotableOrderItem splitItem : allSplitItems) {
adjustmentTotal = adjustmentTotal.add(splitItem.getCurrentPrice().multiply(splitItem.getQuantity()));
saleTotal = saleTotal.add(splitItem.getPriceBeforeAdjustments(true).multiply(splitItem.getQuantity()));
}
if (adjustmentTotal.greaterThanOrEqual(saleTotal)) {
// adjustment price is not best price, remove adjustments for this item
for (PromotableOrderItem splitItem : allSplitItems) {
if (splitItem.isHasOrderItemAdjustments()) {
appliedItemOffersCount--;
}
}
order.getSplitItems().clear();
}
}
return appliedItemOffersCount;
*/
return false ;
}
protected int applyAdjustments (PromotableOrder order , int appliedItemOffersCount , PromotableCandidateItemOffer itemOffer , int beforeCount ) {
boolean notCombinableOfferApplied = false ;
@@ -323,7 +395,6 @@ protected int applyLegacyAdjustments(int appliedItemOffersCount, PromotableCandi
protected void applyItemQualifiersAndTargets (PromotableCandidateItemOffer itemOffer , PromotableOrder order ) {
Offer promotion = itemOffer .getOffer ();
OrderItemPriceComparator priceComparator = new OrderItemPriceComparator (promotion .getApplyDiscountToSalePrice ());
boolean matchFound = false ;
do {
matchFound = false ;
@@ -337,8 +408,8 @@ protected void applyItemQualifiersAndTargets(PromotableCandidateItemOffer itemOf
for (OfferItemCriteria itemCriteria : itemOffer .getCandidateQualifiersMap ().keySet ()) {
List <PromotableOrderItem > chargeableItems = itemOffer .getCandidateQualifiersMap ().get (itemCriteria );
// Sort the items so that the highest priced ones are at the top
Collections . sort ( chargeableItems , priceComparator );
Collections . sort ( chargeableItems , getQualifierItemComparator ( promotion . 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 ();
@@ -366,7 +437,7 @@ protected void applyItemQualifiersAndTargets(PromotableCandidateItemOffer itemOf
}
checkTargets :{
List <PromotableOrderItem > chargeableItems = itemOffer .getCandidateTargets ();
Collections .sort (chargeableItems , priceComparator );
Collections .sort (chargeableItems , getTargetItemComparator ( promotion . getApplyDiscountToSalePrice ()) );
for (PromotableOrderItem chargeableItem : chargeableItems ) {
// Mark Targets
if (receiveQtyNeeded > 0 ) {
@@ -425,6 +496,56 @@ protected void applyItemQualifiersAndTargets(PromotableCandidateItemOffer itemOf
}
}
}
/**
* Used in {@link #applyItemQualifiersAndTargets(PromotableCandidateItemOffer, PromotableOrder)} allow for customized
* sorting for which qualifier items should be attempted to be used first for a promotion. Default behavior
* is to sort descending, so higher-value items are attempted to be qualified first.
*
* @param applyToSalePrice - whether or not the Comparator should use the sale price for comparison
* @return
*/
protected Comparator <PromotableOrderItem > getQualifierItemComparator (final boolean applyToSalePrice ) {
return new Comparator <PromotableOrderItem >() {
@ Override
public int compare (PromotableOrderItem o1 , PromotableOrderItem o2 ) {
Money price = o1 .getPriceBeforeAdjustments (applyToSalePrice );
Money price2 = o2 .getPriceBeforeAdjustments (applyToSalePrice );
// highest amount first
return price2 .compareTo (price );
}
};
}
/**
* <p>
* Used in {@link #applyItemQualifiersAndTargets(PromotableCandidateItemOffer, PromotableOrder)} allow for customized
* sorting for which target items the promotion should be attempted to be applied to first. Default behavior is to
* sort descending, so higher-value items get the promotion over lesser-valued items.
* </p>
* <p>
* Note: By default, both the {@link #getQualifierItemComparator(boolean)} and this target comparator are sorted
* in descending order. This means that higher-valued items can be paired with higher-valued items and lower-valued
* items can be paired with lower-valued items. This also ensures that you will <b>not</b> have the scenario where 2 lower-valued
* items can be used to qualify a higher-valued target.
* </p>
*
* @param applyToSalePrice - whether or not the Comparator should use the sale price for comparison
* @return
*/
protected Comparator <PromotableOrderItem > getTargetItemComparator (final boolean applyToSalePrice ) {
return new Comparator <PromotableOrderItem >() {
@ Override
public int compare (PromotableOrderItem o1 , PromotableOrderItem o2 ) {
Money price = o1 .getPriceBeforeAdjustments (applyToSalePrice );
Money price2 = o2 .getPriceBeforeAdjustments (applyToSalePrice );
// highest amount first
return price2 .compareTo (price );
}
};
}
/**
* Private method used by applyAllItemOffers to create an OrderItemAdjustment from a CandidateItemOffer
@@ -440,6 +561,7 @@ protected void applyOrderItemAdjustment(PromotableCandidateItemOffer itemOffer,
orderItem .addOrderItemAdjustment (promotableOrderItemAdjustment ); //This is how we can tell if an item has been discounted
}
@ Override
public void filterOffers (PromotableOrder order , List <Offer > filteredOffers , List <PromotableCandidateOrderOffer > qualifiedOrderOffers , List <PromotableCandidateItemOffer > qualifiedItemOffers ) {
// set order subtotal price to total item price without adjustments
order .setSubTotal (order .calculateOrderItemsFinalPrice (true ));
@@ -452,6 +574,7 @@ public void filterOffers(PromotableOrder order, List<Offer> filteredOffers, List
}
}
@ Override
@ SuppressWarnings ("unchecked" )
public void applyAndCompareOrderAndItemOffers (PromotableOrder order , List <PromotableCandidateOrderOffer > qualifiedOrderOffers , List <PromotableCandidateItemOffer > qualifiedItemOffers ) {
if (!qualifiedItemOffers .isEmpty ()) {