@Override
  public void filterFulfillmentGroupLevelOffer(
      PromotableOrder order,
      List<PromotableCandidateFulfillmentGroupOffer> qualifiedFGOffers,
      Offer offer) {
    for (PromotableFulfillmentGroup fulfillmentGroup : order.getFulfillmentGroups()) {
      boolean fgLevelQualification = false;
      fgQualification:
      {
        // handle legacy fields in addition to the 1.5 order rule field
        if (couldOfferApplyToOrder(offer, order, fulfillmentGroup)) {
          fgLevelQualification = true;
          break fgQualification;
        }
        for (PromotableOrderItem discreteOrderItem : order.getAllOrderItems()) {
          if (couldOfferApplyToOrder(offer, order, discreteOrderItem, fulfillmentGroup)) {
            fgLevelQualification = true;
            break fgQualification;
          }
        }
      }
      if (fgLevelQualification) {
        fgLevelQualification = false;
        // handle 1.5 FG field
        if (couldOfferApplyToFulfillmentGroup(offer, fulfillmentGroup)) {
          fgLevelQualification = true;
        }
      }

      // Item Qualification - new for 1.5!
      if (fgLevelQualification) {
        CandidatePromotionItems candidates =
            couldOfferApplyToOrderItems(offer, fulfillmentGroup.getDiscountableOrderItems());
        if (candidates.isMatchedQualifier()) {
          PromotableCandidateFulfillmentGroupOffer candidateOffer =
              createCandidateFulfillmentGroupOffer(offer, qualifiedFGOffers, fulfillmentGroup);
          candidateOffer.getCandidateQualifiersMap().putAll(candidates.getCandidateQualifiersMap());
        }
      }
    }
  }
  @Override
  @SuppressWarnings("unchecked")
  public boolean applyAllFulfillmentGroupOffers(
      List<PromotableCandidateFulfillmentGroupOffer> qualifiedFGOffers, PromotableOrder order) {
    Map<FulfillmentGroupOfferPotential, List<PromotableCandidateFulfillmentGroupOffer>> offerMap =
        new HashMap<
            FulfillmentGroupOfferPotential, List<PromotableCandidateFulfillmentGroupOffer>>();
    for (PromotableCandidateFulfillmentGroupOffer candidate : qualifiedFGOffers) {
      FulfillmentGroupOfferPotential potential = new FulfillmentGroupOfferPotential();
      potential.setOffer(candidate.getOffer());
      if (offerMap.get(potential) == null) {
        offerMap.put(potential, new ArrayList<PromotableCandidateFulfillmentGroupOffer>());
      }
      offerMap.get(potential).add(candidate);
    }
    List<FulfillmentGroupOfferPotential> potentials =
        new ArrayList<FulfillmentGroupOfferPotential>();
    for (FulfillmentGroupOfferPotential potential : offerMap.keySet()) {
      List<PromotableCandidateFulfillmentGroupOffer> fgOffers = offerMap.get(potential);
      Collections.sort(
          fgOffers,
          new ReverseComparator(new BeanComparator("discountedAmount", new NullComparator())));
      Collections.sort(fgOffers, new BeanComparator("priority", new NullComparator()));

      if (potential.getOffer().isLimitedUsePerOrder()
          && fgOffers.size() > potential.getOffer().getMaxUsesPerOrder()) {
        for (int j = potential.getOffer().getMaxUsesPerOrder(); j < fgOffers.size(); j++) {
          fgOffers.remove(j);
        }
      }
      for (PromotableCandidateFulfillmentGroupOffer candidate : fgOffers) {
        if (potential.getTotalSavings().getAmount().equals(BankersRounding.zeroAmount())) {
          BroadleafCurrency currency = order.getOrderCurrency();
          if (currency != null) {
            potential.setTotalSavings(new Money(BigDecimal.ZERO, currency.getCurrencyCode()));
          } else {
            potential.setTotalSavings(new Money(BigDecimal.ZERO));
          }
        }

        Money priceBeforeAdjustments =
            candidate.getFulfillmentGroup().calculatePriceWithoutAdjustments();
        Money discountedPrice = candidate.getDiscountedPrice();
        potential.setTotalSavings(
            potential.getTotalSavings().add(priceBeforeAdjustments.subtract(discountedPrice)));
        potential.setPriority(candidate.getOffer().getPriority());
      }

      potentials.add(potential);
    }

    // Sort fg potentials by priority and discount
    Collections.sort(potentials, new BeanComparator("totalSavings", Collections.reverseOrder()));
    Collections.sort(potentials, new BeanComparator("priority"));
    potentials = removeTrailingNotCombinableFulfillmentGroupOffers(potentials);

    boolean fgOfferApplied = false;
    for (FulfillmentGroupOfferPotential potential : potentials) {
      Offer offer = potential.getOffer();

      boolean alreadyContainsTotalitarianOffer = order.isTotalitarianOfferApplied();
      List<PromotableCandidateFulfillmentGroupOffer> candidates = offerMap.get(potential);
      for (PromotableCandidateFulfillmentGroupOffer candidate : candidates) {
        applyFulfillmentGroupOffer(candidate.getFulfillmentGroup(), candidate);
        fgOfferApplied = true;
      }
      for (PromotableFulfillmentGroup fg : order.getFulfillmentGroups()) {
        fg.chooseSaleOrRetailAdjustments();
      }

      if ((offer.isTotalitarianOffer() != null && offer.isTotalitarianOffer())
          || alreadyContainsTotalitarianOffer) {
        fgOfferApplied = compareAndAdjustFulfillmentGroupOffers(order, fgOfferApplied);
        if (fgOfferApplied) {
          break;
        }
      } else if (!offer.isCombinableWithOtherOffers()) {
        break;
      }
    }

    return fgOfferApplied;
  }