Search in sources :

Example 6 with PromotableCandidateItemOffer

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;
}
Also used : PromotableCandidateItemOffer(org.broadleafcommerce.core.offer.service.discount.domain.PromotableCandidateItemOffer) PromotableCandidateOrderOffer(org.broadleafcommerce.core.offer.service.discount.domain.PromotableCandidateOrderOffer) Offer(org.broadleafcommerce.core.offer.domain.Offer) PromotableOrderItem(org.broadleafcommerce.core.offer.service.discount.domain.PromotableOrderItem) OrderItem(org.broadleafcommerce.core.order.domain.OrderItem) OfferItemCriteria(org.broadleafcommerce.core.offer.domain.OfferItemCriteria) PromotableOrderItem(org.broadleafcommerce.core.offer.service.discount.domain.PromotableOrderItem) PromotableOrderItemPriceDetail(org.broadleafcommerce.core.offer.service.discount.domain.PromotableOrderItemPriceDetail)

Example 7 with PromotableCandidateItemOffer

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;
}
Also used : Money(org.broadleafcommerce.common.money.Money) PromotableCandidateItemOffer(org.broadleafcommerce.core.offer.service.discount.domain.PromotableCandidateItemOffer) ArrayList(java.util.ArrayList) List(java.util.List) PromotableOrderItem(org.broadleafcommerce.core.offer.service.discount.domain.PromotableOrderItem)

Example 8 with PromotableCandidateItemOffer

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;
}
Also used : PromotableCandidateItemOffer(org.broadleafcommerce.core.offer.service.discount.domain.PromotableCandidateItemOffer)

Example 9 with 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;
}
Also used : PromotableCandidateItemOffer(org.broadleafcommerce.core.offer.service.discount.domain.PromotableCandidateItemOffer) PromotableCandidateFulfillmentGroupOffer(org.broadleafcommerce.core.offer.service.discount.domain.PromotableCandidateFulfillmentGroupOffer) PromotableCandidateOrderOffer(org.broadleafcommerce.core.offer.service.discount.domain.PromotableCandidateOrderOffer) CustomerOffer(org.broadleafcommerce.core.offer.domain.CustomerOffer) Offer(org.broadleafcommerce.core.offer.domain.Offer) PromotableCandidateItemOffer(org.broadleafcommerce.core.offer.service.discount.domain.PromotableCandidateItemOffer) PromotableCandidateOrderOffer(org.broadleafcommerce.core.offer.service.discount.domain.PromotableCandidateOrderOffer) ArrayList(java.util.ArrayList) PromotableOrder(org.broadleafcommerce.core.offer.service.discount.domain.PromotableOrder) Transactional(org.springframework.transaction.annotation.Transactional)

Example 10 with PromotableCandidateItemOffer

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();
}
Also used : PromotableOrder(org.broadleafcommerce.core.offer.service.discount.domain.PromotableOrder) Order(org.broadleafcommerce.core.order.domain.Order) PromotableCandidateItemOffer(org.broadleafcommerce.core.offer.service.discount.domain.PromotableCandidateItemOffer) PromotableCandidateItemOffer(org.broadleafcommerce.core.offer.service.discount.domain.PromotableCandidateItemOffer) CandidateItemOffer(org.broadleafcommerce.core.offer.domain.CandidateItemOffer) Offer(org.broadleafcommerce.core.offer.domain.Offer) ArrayList(java.util.ArrayList) OfferTargetCriteriaXref(org.broadleafcommerce.core.offer.domain.OfferTargetCriteriaXref) PromotableOrder(org.broadleafcommerce.core.offer.service.discount.domain.PromotableOrder)

Aggregations

PromotableCandidateItemOffer (org.broadleafcommerce.core.offer.service.discount.domain.PromotableCandidateItemOffer)11 ArrayList (java.util.ArrayList)7 Offer (org.broadleafcommerce.core.offer.domain.Offer)6 Money (org.broadleafcommerce.common.money.Money)4 PromotableCandidateOrderOffer (org.broadleafcommerce.core.offer.service.discount.domain.PromotableCandidateOrderOffer)4 PromotableOrder (org.broadleafcommerce.core.offer.service.discount.domain.PromotableOrder)4 PromotableOrderItem (org.broadleafcommerce.core.offer.service.discount.domain.PromotableOrderItem)4 CandidateItemOffer (org.broadleafcommerce.core.offer.domain.CandidateItemOffer)3 PromotableOrderItemPriceDetail (org.broadleafcommerce.core.offer.service.discount.domain.PromotableOrderItemPriceDetail)2 Order (org.broadleafcommerce.core.order.domain.Order)2 BigDecimal (java.math.BigDecimal)1 List (java.util.List)1 CustomerOffer (org.broadleafcommerce.core.offer.domain.CustomerOffer)1 OfferItemCriteria (org.broadleafcommerce.core.offer.domain.OfferItemCriteria)1 OfferTargetCriteriaXref (org.broadleafcommerce.core.offer.domain.OfferTargetCriteriaXref)1 CandidatePromotionItems (org.broadleafcommerce.core.offer.service.discount.CandidatePromotionItems)1 PromotionDiscount (org.broadleafcommerce.core.offer.service.discount.PromotionDiscount)1 PromotableCandidateFulfillmentGroupOffer (org.broadleafcommerce.core.offer.service.discount.domain.PromotableCandidateFulfillmentGroupOffer)1 PromotableFulfillmentGroup (org.broadleafcommerce.core.offer.service.discount.domain.PromotableFulfillmentGroup)1 OrderItem (org.broadleafcommerce.core.order.domain.OrderItem)1