/*
* #%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.discount.domain;
import org.apache.commons.collections.CollectionUtils;
import org.broadleafcommerce.common.money.Money;
import org.broadleafcommerce.core.offer.domain.Offer;
import org.broadleafcommerce.core.offer.domain.OfferItemCriteria;
import org.broadleafcommerce.core.offer.service.discount.PromotionDiscount;
import org.broadleafcommerce.core.offer.service.discount.PromotionQualifier;
import org.broadleafcommerce.core.offer.service.type.OfferItemRestrictionRuleType;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
public class PromotableOrderItemPriceDetailImpl implements PromotableOrderItemPriceDetail {
protected PromotableOrderItem promotableOrderItem;
protected List<PromotableOrderItemPriceDetailAdjustment> promotableOrderItemPriceDetailAdjustments = new ArrayList<PromotableOrderItemPriceDetailAdjustment>();
protected List<PromotionDiscount> promotionDiscounts = new ArrayList<PromotionDiscount>();
protected List<PromotionQualifier> promotionQualifiers = new ArrayList<PromotionQualifier>();
protected int quantity;
protected boolean useSaleAdjustments = false;
protected boolean adjustmentsFinalized = false;
protected Money adjustedTotal;
public PromotableOrderItemPriceDetailImpl(PromotableOrderItem promotableOrderItem, int quantity) {
this.promotableOrderItem = promotableOrderItem;
this.quantity = quantity;
}
@Override
public boolean isAdjustmentsFinalized() {
return adjustmentsFinalized;
}
@Override
public void setAdjustmentsFinalized(boolean adjustmentsFinalized) {
this.adjustmentsFinalized = adjustmentsFinalized;
}
@Override
public void addCandidateItemPriceDetailAdjustment(PromotableOrderItemPriceDetailAdjustment itemAdjustment) {
promotableOrderItemPriceDetailAdjustments.add(itemAdjustment);
}
@Override
public List<PromotableOrderItemPriceDetailAdjustment> getCandidateItemAdjustments() {
return Collections.unmodifiableList(promotableOrderItemPriceDetailAdjustments);
}
@Override
public boolean hasNonCombinableAdjustments() {
for (PromotableOrderItemPriceDetailAdjustment adjustment : promotableOrderItemPriceDetailAdjustments) {
if (!adjustment.isCombinable()) {
return true;
}
}
return false;
}
protected boolean hasOrderItemAdjustments() {
return promotableOrderItemPriceDetailAdjustments.size() > 0;
}
@Override
public boolean isTotalitarianOfferApplied() {
for (PromotableOrderItemPriceDetailAdjustment adjustment : promotableOrderItemPriceDetailAdjustments) {
if (adjustment.isTotalitarian()) {
return true;
}
}
return false;
}
@Override
public boolean isNonCombinableOfferApplied() {
for (PromotableOrderItemPriceDetailAdjustment adjustment : promotableOrderItemPriceDetailAdjustments) {
if (!adjustment.isCombinable()) {
return true;
}
}
return false;
}
public Money calculateSaleAdjustmentUnitPrice() {
Money returnPrice = promotableOrderItem.getSalePriceBeforeAdjustments();
if (returnPrice == null) {
returnPrice = promotableOrderItem.getRetailPriceBeforeAdjustments();
}
for (PromotableOrderItemPriceDetailAdjustment adjustment : promotableOrderItemPriceDetailAdjustments) {
returnPrice = returnPrice.subtract(adjustment.getSaleAdjustmentValue());
}
return returnPrice;
}
public Money calculateRetailAdjustmentUnitPrice() {
Money returnPrice = promotableOrderItem.getRetailPriceBeforeAdjustments();
for (PromotableOrderItemPriceDetailAdjustment adjustment : promotableOrderItemPriceDetailAdjustments) {
returnPrice = returnPrice.subtract(adjustment.getRetailAdjustmentValue());
}
return returnPrice;
}
/**
* This method will check to see if the salePriceAdjustments or retailPriceAdjustments are better
* and remove those that should not apply.
* @return
*/
public void chooseSaleOrRetailAdjustments() {
adjustmentsFinalized = true;
adjustedTotal = null;
this.useSaleAdjustments = Boolean.FALSE;
Money salePriceBeforeAdjustments = promotableOrderItem.getSalePriceBeforeAdjustments();
Money retailPriceBeforeAdjustments = promotableOrderItem.getRetailPriceBeforeAdjustments();
if (hasOrderItemAdjustments()) {
Money saleAdjustmentPrice = calculateSaleAdjustmentUnitPrice();
Money retailAdjustmentPrice = calculateRetailAdjustmentUnitPrice();
if (promotableOrderItem.isOnSale()) {
if (saleAdjustmentPrice.lessThanOrEqual(retailAdjustmentPrice)) {
this.useSaleAdjustments = Boolean.TRUE;
adjustedTotal = saleAdjustmentPrice;
} else {
adjustedTotal = retailAdjustmentPrice;
}
if (!adjustedTotal.lessThan(salePriceBeforeAdjustments)) {
// Adjustments are not as good as the sale price. So, clear them and use the sale price.
promotableOrderItemPriceDetailAdjustments.clear();
adjustedTotal = salePriceBeforeAdjustments;
}
} else {
if (!retailAdjustmentPrice.lessThan(promotableOrderItem.getRetailPriceBeforeAdjustments())) {
// Adjustments are not as good as the retail price.
promotableOrderItemPriceDetailAdjustments.clear();
adjustedTotal = retailPriceBeforeAdjustments;
} else {
adjustedTotal = retailAdjustmentPrice;
}
}
if (useSaleAdjustments) {
removeRetailOnlyAdjustments();
}
removeZeroDollarAdjustments(useSaleAdjustments);
finalizeAdjustments(useSaleAdjustments);
}
if (adjustedTotal == null) {
if (salePriceBeforeAdjustments != null) {
this.useSaleAdjustments = true;
adjustedTotal = salePriceBeforeAdjustments;
} else {
adjustedTotal = retailPriceBeforeAdjustments;
}
}
adjustedTotal = adjustedTotal.multiply(quantity);
}
public void removeAllAdjustments() {
promotableOrderItemPriceDetailAdjustments.clear();
chooseSaleOrRetailAdjustments();
}
protected void finalizeAdjustments(boolean useSaleAdjustments) {
for (PromotableOrderItemPriceDetailAdjustment adjustment : promotableOrderItemPriceDetailAdjustments) {
adjustment.finalizeAdjustment(useSaleAdjustments);
}
}
/**
* Removes retail only adjustments.
*/
protected void removeRetailOnlyAdjustments() {
Iterator<PromotableOrderItemPriceDetailAdjustment> adjustments = promotableOrderItemPriceDetailAdjustments.iterator();
while (adjustments.hasNext()) {
PromotableOrderItemPriceDetailAdjustment adjustment = adjustments.next();
if (adjustment.getOffer().getApplyDiscountToSalePrice() == false) {
adjustments.remove();
}
}
}
/**
* If removeUnusedAdjustments is s
* @param useSaleAdjustments
*/
protected void removeZeroDollarAdjustments(boolean useSalePrice) {
Iterator<PromotableOrderItemPriceDetailAdjustment> adjustments = promotableOrderItemPriceDetailAdjustments.iterator();
while (adjustments.hasNext()) {
PromotableOrderItemPriceDetailAdjustment adjustment = adjustments.next();
if (useSalePrice) {
if (adjustment.getSaleAdjustmentValue().isZero()) {
adjustments.remove();
}
} else {
if (adjustment.getRetailAdjustmentValue().isZero()) {
adjustments.remove();
}
}
}
}
public PromotableOrderItem getPromotableOrderItem() {
return promotableOrderItem;
}
@Override
public List<PromotionDiscount> getPromotionDiscounts() {
return promotionDiscounts;
}
@Override
public List<PromotionQualifier> getPromotionQualifiers() {
return promotionQualifiers;
}
@Override
public int getQuantity() {
return quantity;
}
@Override
public void setQuantity(int quantity) {
this.quantity = quantity;
}
private boolean restrictTarget(Offer offer, boolean targetType) {
OfferItemRestrictionRuleType qualifierType;
if (targetType) {
qualifierType = offer.getOfferItemTargetRuleType();
} else {
qualifierType = offer.getOfferItemQualifierRuleType();
}
return OfferItemRestrictionRuleType.NONE.equals(qualifierType) ||
OfferItemRestrictionRuleType.QUALIFIER.equals(qualifierType);
}
private boolean restrictQualifier(Offer offer, boolean targetType) {
OfferItemRestrictionRuleType qualifierType;
if (targetType) {
qualifierType = offer.getOfferItemTargetRuleType();
} else {
qualifierType = offer.getOfferItemQualifierRuleType();
}
return OfferItemRestrictionRuleType.NONE.equals(qualifierType) ||
OfferItemRestrictionRuleType.TARGET.equals(qualifierType);
}
@Override
public int getQuantityAvailableToBeUsedAsTarget(PromotableCandidateItemOffer itemOffer) {
int qtyAvailable = quantity;
Offer promotion = itemOffer.getOffer();
// 1. Any quantities of this item that have already received the promotion are not eligible.
// 2. If this promotion is not combinable then any quantities that have received discounts
// from other promotions cannot receive this discount
// 3. If this promotion is combinable then any quantities that have received discounts from
// other combinable promotions are eligible to receive this discount as well
boolean combinable = promotion.isCombinableWithOtherOffers();
if (!combinable && isNonCombinableOfferApplied()) {
return 0;
}
// Any quantities of this item that have already received the promotion are not eligible.
// Also, any quantities of this item that have received another promotion are not eligible
// if this promotion cannot be combined with another discount.
for (PromotionDiscount promotionDiscount : promotionDiscounts) {
if (promotionDiscount.getPromotion().equals(promotion) || restrictTarget(promotion, true)) {
qtyAvailable = qtyAvailable - promotionDiscount.getQuantity();
} else {
// The other promotion is Combinable, but we must make sure that the item qualifier also allows
// it to be reused as a target.
if (restrictTarget(promotionDiscount.getPromotion(), true)) {
qtyAvailable = qtyAvailable - promotionDiscount.getQuantity();
}
}
}
// 4. Any quantities of this item that have been used as a qualifier for this promotion are not eligible as targets
// 5. Any quantities of this item that have been used as a qualifier for another promotion that does
// not allow the qualifier to be reused must be deduced from the qtyAvailable.
for (PromotionQualifier promotionQualifier : promotionQualifiers) {
if (promotionQualifier.getPromotion().equals(promotion) || restrictQualifier(promotion, true)) {
qtyAvailable = qtyAvailable - promotionQualifier.getQuantity();
} else {
if (restrictTarget(promotionQualifier.getPromotion(), false)) {
qtyAvailable = qtyAvailable - promotionQualifier.getQuantity();
}
}
}
return qtyAvailable;
}
public PromotionQualifier lookupOrCreatePromotionQualifier(PromotableCandidateItemOffer candidatePromotion) {
Offer promotion = candidatePromotion.getOffer();
for (PromotionQualifier pq : promotionQualifiers) {
if (pq.getPromotion().equals(promotion)) {
return pq;
}
}
PromotionQualifier pq = new PromotionQualifier();
pq.setPromotion(promotion);
promotionQualifiers.add(pq);
return pq;
}
public PromotionDiscount lookupOrCreatePromotionDiscount(PromotableCandidateItemOffer candidatePromotion) {
Offer promotion = candidatePromotion.getOffer();
for (PromotionDiscount pd : promotionDiscounts) {
if (pd.getPromotion().equals(promotion)) {
return pd;
}
}
PromotionDiscount pd = new PromotionDiscount();
pd.setPromotion(promotion);
promotionDiscounts.add(pd);
return pd;
}
@Override
public PromotionQualifier addPromotionQualifier(PromotableCandidateItemOffer itemOffer, OfferItemCriteria itemCriteria, int qtyToMarkAsQualifier) {
PromotionQualifier pq = lookupOrCreatePromotionQualifier(itemOffer);
pq.incrementQuantity(qtyToMarkAsQualifier);
pq.setItemCriteria(itemCriteria);
return pq;
}
@Override
public void addPromotionDiscount(PromotableCandidateItemOffer itemOffer, OfferItemCriteria itemCriteria, int qtyToMarkAsTarget) {
PromotionDiscount pd = lookupOrCreatePromotionDiscount(itemOffer);
if (pd == null) {
return;
}
pd.incrementQuantity(qtyToMarkAsTarget);
pd.setItemCriteria(itemCriteria);
pd.setCandidateItemOffer(itemOffer);
}
@Override
public void finalizeQuantities() {
for (PromotionDiscount promotionDiscount : promotionDiscounts) {
promotionDiscount.setFinalizedQuantity(promotionDiscount.getQuantity());
}
for (PromotionQualifier promotionQualifier : promotionQualifiers) {
promotionQualifier.setFinalizedQuantity(promotionQualifier.getQuantity());
}
}
@Override
public void clearAllNonFinalizedQuantities() {
Iterator<PromotionQualifier> promotionQualifierIterator = promotionQualifiers.iterator();
while (promotionQualifierIterator.hasNext()) {
PromotionQualifier promotionQualifier = promotionQualifierIterator.next();
if (promotionQualifier.getFinalizedQuantity() == 0) {
// If there are no quantities of this item that are finalized, then remove the item.
promotionQualifierIterator.remove();
} else {
// Otherwise, set the quantity to the number of finalized items.
promotionQualifier.setQuantity(promotionQualifier.getFinalizedQuantity());
}
}
Iterator<PromotionDiscount> promotionDiscountIterator = promotionDiscounts.iterator();
while (promotionDiscountIterator.hasNext()) {
PromotionDiscount promotionDiscount = promotionDiscountIterator.next();
if (promotionDiscount.getFinalizedQuantity() == 0) {
// If there are no quantities of this item that are finalized, then remove the item.
promotionDiscountIterator.remove();
} else {
// Otherwise, set the quantity to the number of finalized items.
promotionDiscount.setQuantity(promotionDiscount.getFinalizedQuantity());
}
}
}
@Override
public int getQuantityAvailableToBeUsedAsQualifier(PromotableCandidateItemOffer itemOffer) {
int qtyAvailable = quantity;
Offer promotion = itemOffer.getOffer();
// Any quantities of this item that have already received the promotion are not eligible.
for (PromotionDiscount promotionDiscount : promotionDiscounts) {
if (promotionDiscount.getPromotion().equals(promotion) || restrictTarget(promotion, false)) {
qtyAvailable = qtyAvailable - promotionDiscount.getQuantity();
} else {
// Item's that receive other discounts might still be allowed to be qualifiers
if (restrictQualifier(promotionDiscount.getPromotion(), true)) {
qtyAvailable = qtyAvailable - promotionDiscount.getQuantity();
}
}
}
// Any quantities of this item that have already been used as a qualifier for this promotion or for
// another promotion that has a qualifier type of NONE or TARGET_ONLY cannot be used for this promotion
for (PromotionQualifier promotionQualifier : promotionQualifiers) {
if (promotionQualifier.getPromotion().equals(promotion) || restrictQualifier(promotion, false)) {
qtyAvailable = qtyAvailable - promotionQualifier.getQuantity();
} else {
if (restrictQualifier(promotionQualifier.getPromotion(), false)) {
qtyAvailable = qtyAvailable - promotionQualifier.getQuantity();
}
}
}
return qtyAvailable;
}
@Override
public Money calculateItemUnitPriceWithAdjustments(boolean allowSalePrice) {
Money priceWithAdjustments = null;
if (allowSalePrice) {
priceWithAdjustments = promotableOrderItem.getSalePriceBeforeAdjustments();
if (priceWithAdjustments == null) {
return promotableOrderItem.getRetailPriceBeforeAdjustments();
}
} else {
priceWithAdjustments = promotableOrderItem.getRetailPriceBeforeAdjustments();
}
for (PromotableOrderItemPriceDetailAdjustment adjustment : promotableOrderItemPriceDetailAdjustments) {
if (allowSalePrice) {
priceWithAdjustments = priceWithAdjustments.subtract(adjustment.getSaleAdjustmentValue());
} else {
priceWithAdjustments = priceWithAdjustments.subtract(adjustment.getRetailAdjustmentValue());
}
}
return priceWithAdjustments;
}
protected Money calculateAdjustmentsUnitValue() {
Money adjustmentUnitValue = new Money(promotableOrderItem.getCurrency());
for (PromotableOrderItemPriceDetailAdjustment adjustment : promotableOrderItemPriceDetailAdjustments) {
adjustmentUnitValue = adjustmentUnitValue.add(adjustment.getAdjustmentValue());
}
return adjustmentUnitValue;
}
/**
* Creates a key that represents a unique priceDetail
* @return
*/
@Override
public String buildDetailKey() {
List<Long> offerIds = new ArrayList<Long>();
for (PromotableOrderItemPriceDetailAdjustment adjustment : promotableOrderItemPriceDetailAdjustments) {
Long offerId = adjustment.getOffer().getId();
offerIds.add(offerId);
}
Collections.sort(offerIds);
return promotableOrderItem.getOrderItem().toString() + offerIds.toString() + useSaleAdjustments;
}
@Override
public Money getFinalizedTotalWithAdjustments() {
chooseSaleOrRetailAdjustments();
return adjustedTotal;
}
@Override
public Money calculateTotalAdjustmentValue() {
return calculateAdjustmentsUnitValue().multiply(quantity);
}
@Override
public PromotableOrderItemPriceDetail shallowCopy() {
PromotableOrderItemPriceDetail copyDetail = promotableOrderItem.createNewDetail(quantity);
return copyDetail;
}
@Override
public PromotableOrderItemPriceDetail copyWithFinalizedData() {
PromotableOrderItemPriceDetail copyDetail = promotableOrderItem.createNewDetail(quantity);
for (PromotionDiscount existingDiscount : promotionDiscounts) {
if (existingDiscount.isFinalized()) {
PromotionDiscount newDiscount = existingDiscount.copy();
copyDetail.getPromotionDiscounts().add(newDiscount);
}
}
for (PromotionQualifier existingQualifier : promotionQualifiers) {
if (existingQualifier.isFinalized()) {
PromotionQualifier newQualifier = existingQualifier.copy();
copyDetail.getPromotionQualifiers().add(newQualifier);
}
}
for (PromotableOrderItemPriceDetailAdjustment existingAdjustment : promotableOrderItemPriceDetailAdjustments) {
PromotableOrderItemPriceDetailAdjustment newAdjustment = existingAdjustment.copy();
copyDetail.addCandidateItemPriceDetailAdjustment(newAdjustment);
}
return copyDetail;
}
protected PromotableOrderItemPriceDetail split(int discountQty, Long offerId, boolean hasQualifiers) {
int originalQty = quantity;
quantity = discountQty;
int splitItemQty = originalQty - discountQty;
// Create the new item with the correct quantity
PromotableOrderItemPriceDetail newDetail = promotableOrderItem.createNewDetail(splitItemQty);
// copy discounts
for (PromotionDiscount existingDiscount : promotionDiscounts) {
PromotionDiscount newDiscount = existingDiscount.split(discountQty);
if (newDiscount != null) {
newDetail.getPromotionDiscounts().add(newDiscount);
}
}
if (hasQualifiers) {
Iterator<PromotionQualifier> qualifiers = promotionQualifiers.iterator();
while (qualifiers.hasNext()) {
PromotionQualifier currentQualifier = qualifiers.next();
Long qualifierOfferId = currentQualifier.getPromotion().getId();
if (qualifierOfferId.equals(offerId) && currentQualifier.getQuantity() <= splitItemQty) {
// Remove this one from the original detail
qualifiers.remove();
newDetail.getPromotionQualifiers().add(currentQualifier);
} else {
PromotionQualifier newQualifier = currentQualifier.split(splitItemQty);
newDetail.getPromotionQualifiers().add(newQualifier);
}
}
}
for (PromotableOrderItemPriceDetailAdjustment existingAdjustment : promotableOrderItemPriceDetailAdjustments) {
PromotableOrderItemPriceDetailAdjustment newAdjustment = existingAdjustment.copy();
newDetail.addCandidateItemPriceDetailAdjustment(newAdjustment);
}
return newDetail;
}
@Override
public PromotableOrderItemPriceDetail splitIfNecessary() {
PromotableOrderItemPriceDetail returnDetail = null;
for (PromotionDiscount discount : promotionDiscounts) {
if (discount.getQuantity() != quantity) {
Long offerId = discount.getCandidateItemOffer().getOffer().getId();
Offer offer = discount.getCandidateItemOffer().getOffer();
return this.split(discount.getQuantity(), offerId,
!CollectionUtils.isEmpty(offer.getQualifyingItemCriteriaXref()));
}
}
return returnDetail;
}
@Override
public boolean useSaleAdjustments() {
return useSaleAdjustments;
}
}