use of org.broadleafcommerce.core.offer.service.discount.domain.PromotableCandidateItemOffer in project BroadleafCommerce by BroadleafCommerce.
the class ItemOfferProcessorImpl method markTargets.
/**
* Loop through ItemCriteria and mark targets that can get this promotion to give the promotion to 1 or more targets.
* @param itemOffer
* @param order
* @return
*/
public boolean markTargets(PromotableCandidateItemOffer itemOffer, PromotableOrder order, OrderItem relatedQualifier, boolean checkOnly) {
Offer promotion = itemOffer.getOffer();
if (itemOffer.getCandidateTargetsMap().keySet().isEmpty()) {
return false;
}
OrderItem relatedQualifierRoot = offerServiceUtilities.findRelatedQualifierRoot(relatedQualifier);
for (OfferItemCriteria itemCriteria : itemOffer.getCandidateTargetsMap().keySet()) {
List<PromotableOrderItem> promotableItems = itemOffer.getCandidateTargetsMap().get(itemCriteria);
List<PromotableOrderItemPriceDetail> priceDetails = buildPriceDetailListFromOrderItems(promotableItems);
offerServiceUtilities.sortTargetItemDetails(priceDetails, itemOffer.getOffer().getApplyDiscountToSalePrice());
int targetQtyNeeded = itemCriteria.getQuantity();
// This code was added to support a requirement of buying at least "x" matching items
// but also applying the item to "x+1", "x+2", etc.
int minRequiredTargetQuantity = itemOffer.getMinimumRequiredTargetQuantity();
if (minRequiredTargetQuantity > 1 && minRequiredTargetQuantity > targetQtyNeeded) {
targetQtyNeeded = minRequiredTargetQuantity;
}
targetQtyNeeded = offerServiceUtilities.markTargetsForCriteria(itemOffer, relatedQualifier, checkOnly, promotion, relatedQualifierRoot, itemCriteria, priceDetails, targetQtyNeeded);
if (targetQtyNeeded != 0) {
return false;
}
}
if (!checkOnly) {
itemOffer.addUse();
}
return true;
}
use of org.broadleafcommerce.core.offer.service.discount.domain.PromotableCandidateItemOffer in project BroadleafCommerce by BroadleafCommerce.
the class ItemOfferProcessorImpl method determineBestPermutation.
protected List<PromotableCandidateItemOffer> determineBestPermutation(List<PromotableCandidateItemOffer> itemOffers, PromotableOrder order) {
List<List<PromotableCandidateItemOffer>> permutations = buildItemOfferPermutations(itemOffers);
removeDuplicatePermutations(permutations);
List<PromotableCandidateItemOffer> bestOfferList = null;
Money lowestSubtotal = null;
if (permutations.size() > 1) {
for (List<PromotableCandidateItemOffer> offerList : permutations) {
for (PromotableCandidateItemOffer offer : offerList) {
offer.resetUses();
}
applyAllItemOffers(offerList, order);
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);
}
for (PromotableCandidateItemOffer offer : bestOfferList) {
offer.resetUses();
}
return bestOfferList;
}
use of org.broadleafcommerce.core.offer.service.discount.domain.PromotableCandidateItemOffer in project BroadleafCommerce by BroadleafCommerce.
the class ItemOfferProcessorImpl method createCandidateItemOffer.
/**
* Create a candidate item offer based on the offer in question and a specific order item
*
* @param qualifiedItemOffers the container list for candidate item offers
* @param offer the offer in question
* @return the candidate item offer
*/
protected PromotableCandidateItemOffer createCandidateItemOffer(List<PromotableCandidateItemOffer> qualifiedItemOffers, Offer offer, PromotableOrder promotableOrder) {
PromotableCandidateItemOffer promotableCandidateItemOffer = promotableItemFactory.createPromotableCandidateItemOffer(promotableOrder, offer);
qualifiedItemOffers.add(promotableCandidateItemOffer);
return promotableCandidateItemOffer;
}
use of org.broadleafcommerce.core.offer.service.discount.domain.PromotableCandidateItemOffer in project BroadleafCommerce by BroadleafCommerce.
the class OfferServiceImpl method applyAndSaveOffersToOrder.
/*
*
* Offers Logic:
* 1) Remove all existing offers in the Order (order, item, and fulfillment)
* 2) Check and remove offers
* a) Remove out of date offers
* b) Remove offers that do not apply to this customer
* 3) Loop through offers
* a) Verifies type of offer (order, order item, fulfillment)
* b) Verifies if offer can be applies
* c) Assign offer to type (order, order item, or fulfillment)
* 4) Sorts the order and item offers list by priority and then discount
* 5) Identify the best offers to apply to order item and create adjustments for each item offer
* 6) Compare order item adjustment price to sales price, and remove adjustments if sale price is better
* 7) Identify the best offers to apply to the order and create adjustments for each order offer
* 8) If item contains non-combinable offers remove either the item or order adjustments based on discount value
* 9) Set final order item prices and reapply order offers
*
* Assumptions:
* 1) % off all items will be created as an item offer with no expression
* 2) $ off order will be created as an order offer
* 3) Order offers applies to the best price for each item (not just retail price)
* 4) Fulfillment offers apply to best price for each item (not just retail price)
* 5) Stackable only applies to the same offer type (i.e. a not stackable order offer can be used with item offers)
* 6) Fulfillment offers cannot be not combinable
* 7) Order offers cannot be FIXED_PRICE
* 8) FIXED_PRICE offers cannot be stackable
* 9) Non-combinable offers only apply to the order and order items, fulfillment group offers will always apply
*
*/
@Override
@Transactional("blTransactionManager")
public Order applyAndSaveOffersToOrder(List<Offer> offers, Order order) throws PricingException {
/*
TODO rather than a threadlocal, we should update the "shouldPrice" boolean on the service API to
use a richer object to describe the parameters of the pricing call. This object would include
the pricing boolean, but would also include a list of activities to include or exclude in the
call - see http://jira.broadleafcommerce.org/browse/BLC-664
*/
OfferContext offerContext = OfferContext.getOfferContext();
if (offerContext == null || offerContext.executePromotionCalculation) {
PromotableOrder promotableOrder = promotableItemFactory.createPromotableOrder(order, false);
List<Offer> filteredOffers = orderOfferProcessor.filterOffers(offers, order.getCustomer());
if ((filteredOffers == null) || (filteredOffers.isEmpty())) {
if (LOG.isTraceEnabled()) {
LOG.trace("No offers applicable to this order.");
}
} else {
List<PromotableCandidateOrderOffer> qualifiedOrderOffers = new ArrayList<PromotableCandidateOrderOffer>();
List<PromotableCandidateItemOffer> qualifiedItemOffers = new ArrayList<PromotableCandidateItemOffer>();
itemOfferProcessor.filterOffers(promotableOrder, filteredOffers, qualifiedOrderOffers, qualifiedItemOffers);
if (!(qualifiedItemOffers.isEmpty() && qualifiedOrderOffers.isEmpty())) {
// At this point, we should have a PromotableOrder that contains PromotableItems each of which
// has a list of candidatePromotions that might be applied.
// We also have a list of orderOffers that might apply and a list of itemOffers that might apply.
itemOfferProcessor.applyAndCompareOrderAndItemOffers(promotableOrder, qualifiedOrderOffers, qualifiedItemOffers);
}
}
orderOfferProcessor.synchronizeAdjustmentsAndPrices(promotableOrder);
verifyAdjustments(order, true);
order.setSubTotal(order.calculateSubTotal());
order.finalizeItemPrices();
order = orderService.save(order, false);
boolean madeChange = verifyAdjustments(order, false);
if (madeChange) {
order = orderService.save(order, false);
}
}
return order;
}
use of org.broadleafcommerce.core.offer.service.discount.domain.PromotableCandidateItemOffer in project BroadleafCommerce by BroadleafCommerce.
the class ItemOfferProcessorTest method testApplyItemQualifiersAndTargets.
public void testApplyItemQualifiersAndTargets() throws Exception {
replay();
List<PromotableCandidateItemOffer> qualifiedOffers = new ArrayList<PromotableCandidateItemOffer>();
Offer offer1 = dataProvider.createItemBasedOfferWithItemCriteria("order.subTotal.getAmount()>20", OfferDiscountType.PERCENT_OFF, "([MVEL.eval(\"toUpperCase()\",\"test1\"), MVEL.eval(\"toUpperCase()\",\"test2\")] contains MVEL.eval(\"toUpperCase()\", discreteOrderItem.category.name))", "([MVEL.eval(\"toUpperCase()\",\"test1\"), MVEL.eval(\"toUpperCase()\",\"test2\")] contains MVEL.eval(\"toUpperCase()\", discreteOrderItem.category.name))").get(0);
offer1.setId(1L);
Offer offer2 = dataProvider.createItemBasedOfferWithItemCriteria("order.subTotal.getAmount()>20", OfferDiscountType.PERCENT_OFF, "([MVEL.eval(\"toUpperCase()\",\"test1\"), MVEL.eval(\"toUpperCase()\",\"test2\")] contains MVEL.eval(\"toUpperCase()\", discreteOrderItem.category.name))", "([MVEL.eval(\"toUpperCase()\",\"test1\"), MVEL.eval(\"toUpperCase()\",\"test2\")] contains MVEL.eval(\"toUpperCase()\", discreteOrderItem.category.name))").get(0);
offer2.setId(2L);
OfferTargetCriteriaXref targetXref = offer2.getTargetItemCriteriaXref().iterator().next();
targetXref.getOfferItemCriteria().setQuantity(4);
offer2.getQualifyingItemCriteriaXref().clear();
offer2.setOfferItemTargetRuleType(OfferItemRestrictionRuleType.TARGET);
Offer offer3 = dataProvider.createItemBasedOfferWithItemCriteria("order.subTotal.getAmount()>20", OfferDiscountType.PERCENT_OFF, "([MVEL.eval(\"toUpperCase()\",\"test1\"), MVEL.eval(\"toUpperCase()\",\"test2\")] contains MVEL.eval(\"toUpperCase()\", discreteOrderItem.category.name))", "([MVEL.eval(\"toUpperCase()\",\"test1\"), MVEL.eval(\"toUpperCase()\",\"test2\")] contains MVEL.eval(\"toUpperCase()\", discreteOrderItem.category.name))").get(0);
PromotableOrder promotableOrder = dataProvider.createBasicPromotableOrder();
itemProcessor.filterItemLevelOffer(promotableOrder, qualifiedOffers, offer1);
assertTrue(qualifiedOffers.size() == 1 && qualifiedOffers.get(0).getOffer().equals(offer1) && qualifiedOffers.get(0).getCandidateQualifiersMap().size() == 1);
itemProcessor.filterItemLevelOffer(promotableOrder, qualifiedOffers, offer2);
assertTrue(qualifiedOffers.size() == 2 && qualifiedOffers.get(1).getOffer().equals(offer2) && qualifiedOffers.get(1).getCandidateQualifiersMap().size() == 0);
itemProcessor.filterItemLevelOffer(promotableOrder, qualifiedOffers, offer3);
assertTrue(qualifiedOffers.size() == 3 && qualifiedOffers.get(2).getOffer().equals(offer3) && qualifiedOffers.get(2).getCandidateQualifiersMap().size() == 1);
// Try with just the second offer. Expect to get 4 targets based on the offer having no qualifiers required
// and targeting category test1 or test2 and that the offer requires 4 target criteria.
Order order = dataProvider.createBasicOrder();
List<Offer> offerList = new ArrayList<Offer>();
offerList.add(offer2);
offerService.applyAndSaveOffersToOrder(offerList, order);
assertTrue(checkOrderItemOfferAppliedQuantity(order, offer2) == 4);
assertTrue(countPriceDetails(order) == 3);
// Now try with both offers. Since the targets can be reused, we expect to have 4 targets on offer2
// and 1 target on offer1
order = dataProvider.createBasicOrder();
// add in second offer (which happens to be offer1)
offerList.add(offer1);
offerService.applyAndSaveOffersToOrder(offerList, order);
assertTrue(checkOrderItemOfferAppliedQuantity(order, offer2) == 4);
assertTrue(countPriceDetails(order) == 3);
// All three offers - offer 3 is now higher priority so the best offer (offer 2) won't be applied
order = dataProvider.createBasicOrder();
// add in second offer (which happens to be offer1)
offerList.add(offer3);
offer3.setPriority(-1);
offerService.applyAndSaveOffersToOrder(offerList, order);
assertTrue(checkOrderItemOfferAppliedQuantity(order, offer3) == 2);
assertTrue(countPriceDetails(order) == 4);
verify();
}
Aggregations