protected boolean compareAndAdjustFulfillmentGroupOffers(
      PromotableOrder order, boolean fgOfferApplied) {
    Money regularOrderDiscountShippingTotal =
        BroadleafCurrencyUtils.getMoney(BigDecimal.ZERO, order.getOrderCurrency());
    regularOrderDiscountShippingTotal =
        regularOrderDiscountShippingTotal.add(order.calculateSubtotalWithoutAdjustments());
    for (PromotableFulfillmentGroup fg : order.getFulfillmentGroups()) {
      regularOrderDiscountShippingTotal =
          regularOrderDiscountShippingTotal.add(fg.getFinalizedPriceWithAdjustments());
    }

    Money discountOrderRegularShippingTotal =
        BroadleafCurrencyUtils.getMoney(BigDecimal.ZERO, order.getOrderCurrency());
    discountOrderRegularShippingTotal =
        discountOrderRegularShippingTotal.add(order.calculateSubtotalWithAdjustments());
    for (PromotableFulfillmentGroup fg : order.getFulfillmentGroups()) {
      discountOrderRegularShippingTotal =
          discountOrderRegularShippingTotal.add(fg.calculatePriceWithoutAdjustments());
    }

    if (discountOrderRegularShippingTotal.lessThan(regularOrderDiscountShippingTotal)) {
      // order/item offer is better; remove totalitarian fulfillment group offer and process other
      // fg offers
      order.removeAllCandidateFulfillmentOfferAdjustments();
      fgOfferApplied = false;
    } else {
      // totalitarian fg offer is better; remove all order/item offers
      order.removeAllCandidateOrderOfferAdjustments();
      order.removeAllCandidateItemOfferAdjustments();
      order.getOrder().setSubTotal(order.calculateSubtotalWithAdjustments());
    }
    return fgOfferApplied;
  }
 protected void applyFulfillmentGroupOffer(
     PromotableFulfillmentGroup promotableFulfillmentGroup,
     PromotableCandidateFulfillmentGroupOffer fulfillmentGroupOffer) {
   if (promotableFulfillmentGroup.canApplyOffer(fulfillmentGroupOffer)) {
     PromotableFulfillmentGroupAdjustment promotableFulfillmentGroupAdjustment =
         promotableItemFactory.createPromotableFulfillmentGroupAdjustment(
             fulfillmentGroupOffer, promotableFulfillmentGroup);
     promotableFulfillmentGroup.addCandidateFulfillmentGroupAdjustment(
         promotableFulfillmentGroupAdjustment);
   }
 }
 @Override
 public void calculateFulfillmentGroupTotal(PromotableOrder order) {
   Money totalFulfillmentCharges =
       BroadleafCurrencyUtils.getMoney(BigDecimal.ZERO, order.getOrderCurrency());
   for (PromotableFulfillmentGroup fulfillmentGroupMember : order.getFulfillmentGroups()) {
     PromotableFulfillmentGroup fulfillmentGroup = fulfillmentGroupMember;
     Money fulfillmentCharges = fulfillmentGroup.getFinalizedPriceWithAdjustments();
     fulfillmentGroup.getFulfillmentGroup().setFulfillmentPrice(fulfillmentCharges);
     totalFulfillmentCharges = totalFulfillmentCharges.add(fulfillmentCharges);
   }
   order.setTotalFufillmentCharges(totalFulfillmentCharges);
 }
  @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());
        }
      }
    }
  }
  protected boolean couldOfferApplyToFulfillmentGroup(
      Offer offer, PromotableFulfillmentGroup fulfillmentGroup) {
    boolean appliesToItem = false;

    OfferRule rule = null;
    OfferOfferRuleXref ruleXref =
        offer.getOfferMatchRulesXref().get(OfferRuleType.FULFILLMENT_GROUP.getType());
    if (ruleXref != null && ruleXref.getOfferRule() != null) {
      rule = ruleXref.getOfferRule();
    }

    if (rule != null && rule.getMatchRule() != null) {
      HashMap<String, Object> vars = new HashMap<String, Object>();
      fulfillmentGroup.updateRuleVariables(vars);
      Boolean expressionOutcome = executeExpression(rule.getMatchRule(), vars);
      if (expressionOutcome != null && expressionOutcome) {
        appliesToItem = true;
      }
    } else {
      appliesToItem = true;
    }

    return appliesToItem;
  }
  @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;
  }