I have not yet tested nor reviewed, I guess you are aware of  
http://ci.apache.org/projects/ofbiz/logs/trunk/html/ ?
Related to last build http://ci.apache.org/builders/ofbiz-trunk/builds/4247

Jacques

On Monday, December 30, 2013 4:53 PM jaco...@apache.org wrote
> Author: jacopoc
> Date: Mon Dec 30 15:53:07 2013
> New Revision: 1554265
> 
> URL: http://svn.apache.org/r1554265
> Log:
> This is a refactoring of the product promotion engine in order to overcome 
> some limitations that prevented it to select and apply
> the best set of promotions under special conditions. 
> 
> Example: Consider two promotions:
> * consider two products: Product A, with price $20, and Product B, with price 
> $40
> * Promotion 1: 20% discount on all the products in a category containing 
> Product A and Product B
> * Promotion 2: 40% discount on Product A
> 
> When Product A and Product B are both in the cart:
> * Expected behavior: on Product A the Promotion 2 should be applied i.e. 40% 
> discount, and on Product B Promotion 1 should be
> applied i.e. 20% discount. 
> ** Summary
> Product Price Discount Subtotal
> A $20 $8 (40% discount) $12
> B $40 $8 (20% discount) $32
> Total Adjustment: $16
> 
> * OFBiz behavior (before this fix): Promotion 1 is applied to Product A and 
> Product B; this happens because the total discount of
> Promotion 1 is greater than the total discount of Promotion 2 and OFBiz 
> applies promotions sorted by discount (desc) 
> ** Summary
> Product Price Discount Subtotal
> A $20 $4 (20% discount) $16
> B $40 $8 (20% discount) $32
> Total Adjustment: $12
> 
> The new solution fixes this issue and similar ones.
> 
> Here are some details about the new algorithm.
> 
> Overview of the flow:
> 1) run the promotions one by one in a test run
> 2) collect the ProductPromoUse information
> 3) sort them by weight (i.e. the ratio between the discount and the value of 
> the products discounted)
> 4) execute the ProductPromoUse in the given order
> 
> In order to understand this solution, and specifically the changes to 
> ProductPromoWorker.java, there is an important concept to
> consider: 
> one Promotion can generate more than one ProductPromoUseInfo objects.
> For example if I have 2 units of WG-1111 in the cart (in one cart item) and I 
> have the promotion “20% discount on WG-1111 and
> GZ-1000” then the system will create TWO ProductPromoUseInfo objects both 
> associated to the same promotion one for each of the 2
> units discounted.  
> Similarly if I had two lines: 2 units of WG-1111 and 1 unit of GZ-1000 I 
> would get 3 ProductPromoUseInfo objects 2 objects for
> WG-1111 and 1 object for GZ-1000 
> 
> We can sort these ProductPromoUseInfo objects based on their weight (i.e. the 
> ratio between the discount and the value of the
> products discounted) in desc order 
> and now we have a sorted list of ProductPromoUseInfo objects ready to be 
> executed
> However we only want to execute each of them once and for this reason we set 
> (in memory, not in the DB) the useLimitPerOrder to 1
> in the first ProductPromoUseInfo of a given promotion and then to 2 if the 
> same ProductPromoUseInfo is associated to the same
> promotion etc...  
> in this way the end result is that the system will generate, as we desire, 
> ONE ProductPromoUseInfo only for each of the
> ProductPromoUseInfo in the list. 
> 
> Here is an example:
> we have 2 promotions:
> PROMO A
> PROMO B
> 
> After test run:
> 
> ProductPromoUseInfo - PROMO A - #1 - weight 0.3
> ProductPromoUseInfo - PROMO A - #2 - weight 0.3
> ProductPromoUseInfo - PROMO B - #1 - weight 0.4
> 
> After sorting:
> 
> ProductPromoUseInfo - PROMO B - #1 - weight 0.4
> ProductPromoUseInfo - PROMO A - #1 - weight 0.3
> ProductPromoUseInfo - PROMO A - #2 - weight 0.3
> 
> Based on this we create a list (sortedExplodedProductPromoList) of 
> ProductPromo:
> 
> PROMO B - with useLimitPerOrder=1
> PROMO A - with useLimitPerOrder=1
> PROMO A - with useLimitPerOrder=2
> 
> When we apply these to the cart we get the following results:
> 
> PROMO B - with useLimitPerOrder=1 APPLIED
> PROMO A - with useLimitPerOrder=1 APPLIED
> PROMO A - with useLimitPerOrder=2 NOT APPLIED (because PROMO B used the item)
> 
> 
> Modified:
>    
> ofbiz/trunk/applications/order/src/org/ofbiz/order/shoppingcart/ShoppingCart.java
>    
> ofbiz/trunk/applications/order/src/org/ofbiz/order/shoppingcart/ShoppingCartServices.java
>    
> ofbiz/trunk/applications/order/src/org/ofbiz/order/shoppingcart/product/ProductPromoWorker.java
> 
> Modified: 
> ofbiz/trunk/applications/order/src/org/ofbiz/order/shoppingcart/ShoppingCart.java
> URL:
> http://svn.apache.org/viewvc/ofbiz/trunk/applications/order/src/org/ofbiz/order/shoppingcart/ShoppingCart.java?rev=1554265&r1=1554264&r2=1554265&view=diff
> ==============================================================================
>  ---
> ofbiz/trunk/applications/order/src/org/ofbiz/order/shoppingcart/ShoppingCart.java
>  (original) +++
> ofbiz/trunk/applications/order/src/org/ofbiz/order/shoppingcart/ShoppingCart.java
>  Mon Dec 30 15:53:07 2013 @@ -2714,7 +2714,7 @@
>             public class ShoppingCart implements Ite }
>             itemsTotal = itemsTotal.add(cartItem.getItemSubTotal());
>         }
> -        return itemsTotal;
> +        return itemsTotal.add(this.getOrderOtherAdjustmentTotal());
>     }
> 
>     /**
> @@ -3142,12 +3142,12 @@ public class ShoppingCart implements Ite
>         return new HashMap<GenericPK, 
> String>(this.desiredAlternateGiftByAction);
>     }
> 
> -    public void addProductPromoUse(String productPromoId, String 
> productPromoCodeId, BigDecimal totalDiscountAmount, BigDecimal
> quantityLeftInActions) { +    public void addProductPromoUse(String 
> productPromoId, String productPromoCodeId, BigDecimal
>         totalDiscountAmount, BigDecimal quantityLeftInActions, 
> Map<ShoppingCartItem,BigDecimal> usageInfoMap) { if
>             (UtilValidate.isNotEmpty(productPromoCodeId) && 
> !this.productPromoCodes.contains(productPromoCodeId)) { throw new
>         IllegalStateException("Cannot add a use to a promo code use for a 
> code that has not been entered."); }
>         if (Debug.verboseOn()) Debug.logVerbose("Used promotion [" + 
> productPromoId + "] with code [" + productPromoCodeId + "]
> for total discount [" + totalDiscountAmount + "] and quantity left in actions 
> [" + quantityLeftInActions + "]", module); -       
> this.productPromoUseInfoList.add(new ProductPromoUseInfo(productPromoId, 
> productPromoCodeId, totalDiscountAmount,
>     quantityLeftInActions)); +        this.productPromoUseInfoList.add(new 
> ProductPromoUseInfo(productPromoId,
> productPromoCodeId, totalDiscountAmount, quantityLeftInActions, 
> usageInfoMap)); } 
> 
>     public void removeProductPromoUse(String productPromoId) {
> @@ -4385,23 +4385,43 @@ public class ShoppingCart implements Ite
>         }
>     }
> 
> -    public static class ProductPromoUseInfo implements Serializable {
> +    public static class ProductPromoUseInfo implements Serializable, 
> Comparable<ProductPromoUseInfo> {
>         public String productPromoId = null;
>         public String productPromoCodeId = null;
>         public BigDecimal totalDiscountAmount = BigDecimal.ZERO;
>         public BigDecimal quantityLeftInActions = BigDecimal.ZERO;
> +        private Map<ShoppingCartItem,BigDecimal> usageInfoMap = null;
> 
> -        public ProductPromoUseInfo(String productPromoId, String 
> productPromoCodeId, BigDecimal totalDiscountAmount, BigDecimal
> quantityLeftInActions) { +        public ProductPromoUseInfo(String 
> productPromoId, String productPromoCodeId, BigDecimal
>             totalDiscountAmount, BigDecimal quantityLeftInActions, 
> Map<ShoppingCartItem,BigDecimal> usageInfoMap) {
>             this.productPromoId = productPromoId; this.productPromoCodeId = 
> productPromoCodeId;
>             this.totalDiscountAmount = totalDiscountAmount;
>             this.quantityLeftInActions = quantityLeftInActions;
> +            this.usageInfoMap = usageInfoMap;
>         }
> 
>         public String getProductPromoId() { return this.productPromoId; }
>         public String getProductPromoCodeId() { return 
> this.productPromoCodeId; }
>         public BigDecimal getTotalDiscountAmount() { return 
> this.totalDiscountAmount; }
>         public BigDecimal getQuantityLeftInActions() { return 
> this.quantityLeftInActions; }
> +        public Map<ShoppingCartItem,BigDecimal> getUsageInfoMap() { return 
> this.usageInfoMap; }
> +        public BigDecimal getUsageWeight() {
> +            Iterator<ShoppingCartItem> lineItems = 
> this.usageInfoMap.keySet().iterator();
> +            BigDecimal totalAmount = BigDecimal.ZERO;
> +            while (lineItems.hasNext()) {
> +                ShoppingCartItem lineItem = lineItems.next();
> +                totalAmount = 
> totalAmount.add(lineItem.getBasePrice().multiply(usageInfoMap.get(lineItem)));
> +            }
> +            if (totalAmount.compareTo(BigDecimal.ZERO) == 0) {
> +                return BigDecimal.ZERO;
> +            } else {
> +                return getTotalDiscountAmount().negate().divide(totalAmount);
> +            }
> +        }
> +
> +        public int compareTo(ProductPromoUseInfo other) {
> +            return other.getUsageWeight().compareTo(getUsageWeight());
> +        }
>     }
> 
>     public static class CartShipInfo implements Serializable {
> 
> Modified: 
> ofbiz/trunk/applications/order/src/org/ofbiz/order/shoppingcart/ShoppingCartServices.java
> URL:
> http://svn.apache.org/viewvc/ofbiz/trunk/applications/order/src/org/ofbiz/order/shoppingcart/ShoppingCartServices.java?rev=1554265&r1=1554264&r2=1554265&view=diff
> ==============================================================================
>  ---
> ofbiz/trunk/applications/order/src/org/ofbiz/order/shoppingcart/ShoppingCartServices.java
>  (original) +++
> ofbiz/trunk/applications/order/src/org/ofbiz/order/shoppingcart/ShoppingCartServices.java
>  Mon Dec 30 15:53:07 2013 @@ -21,6 +21,7
> @@ package org.ofbiz.order.shoppingcart; 
> import java.math.BigDecimal;
> import java.math.MathContext;
> import java.sql.Timestamp;
> +import java.util.HashMap;
> import java.util.Iterator;
> import java.util.List;
> import java.util.Locale;
> @@ -629,7 +630,7 @@ public class ShoppingCartServices {
>                 cart.addProductPromoCode(productPromoCode, dispatcher);
>             }
>             for (GenericValue productPromoUse: orh.getProductPromoUse()) {
> -                
> cart.addProductPromoUse(productPromoUse.getString("productPromoId"),
> productPromoUse.getString("productPromoCodeId"), 
> productPromoUse.getBigDecimal("totalDiscountAmount"),
>             productPromoUse.getBigDecimal("quantityLeftInActions")); +        
>        
>         cart.addProductPromoUse(productPromoUse.getString("productPromoId"), 
> productPromoUse.getString("productPromoCodeId"),
> productPromoUse.getBigDecimal("totalDiscountAmount"), 
> productPromoUse.getBigDecimal("quantityLeftInActions"), new
> HashMap<ShoppingCartItem, BigDecimal>()); } }  
> 
> 
> Modified: 
> ofbiz/trunk/applications/order/src/org/ofbiz/order/shoppingcart/product/ProductPromoWorker.java
> URL:
> http://svn.apache.org/viewvc/ofbiz/trunk/applications/order/src/org/ofbiz/order/shoppingcart/product/ProductPromoWorker.java?rev=1554265&r1=1554264&r2=1554265&view=diff
> ==============================================================================
>  ---
> ofbiz/trunk/applications/order/src/org/ofbiz/order/shoppingcart/product/ProductPromoWorker.java
>  (original) +++
> ofbiz/trunk/applications/order/src/org/ofbiz/order/shoppingcart/product/ProductPromoWorker.java
>  Mon Dec 30 15:53:07 2013 @@ -22,6
> +22,8 @@ import java.math.BigDecimal; 
> import java.math.MathContext;
> import java.sql.Timestamp;
> import java.util.ArrayList;
> +import java.util.Collections;
> +import java.util.HashMap;
> import java.util.Iterator;
> import java.util.List;
> import java.util.Locale;
> @@ -51,6 +53,7 @@ import org.ofbiz.entity.condition.Entity
> import org.ofbiz.entity.util.EntityUtil;
> import org.ofbiz.order.shoppingcart.CartItemModifyException;
> import org.ofbiz.order.shoppingcart.ShoppingCart;
> +import org.ofbiz.order.shoppingcart.ShoppingCart.ProductPromoUseInfo;
> import org.ofbiz.order.shoppingcart.ShoppingCartEvents;
> import org.ofbiz.order.shoppingcart.ShoppingCartItem;
> import org.ofbiz.product.product.ProductContentWrapper;
> @@ -318,44 +321,62 @@ public class ProductPromoWorker {
>             // do a calculate only run through the promotions, then order by 
> descending totalDiscountAmount for each promotion
>             // NOTE: on this run, with isolatedTestRun passed as false it 
> should not apply any adjustments
>             //  or track which cart items are used for which promotions, but 
> it will track ProductPromoUseInfo and
> -            //  useLimits; we are basicly just trying to run each promo 
> "independently" to see how much each is worth
> +            //  useLimits; we are basically just trying to run each promo 
> "independently" to see how much each is worth
>             runProductPromos(productPromoList, cart, delegator, dispatcher, 
> nowTimestamp, true);
> 
> -            // NOTE: after that first pass we could remove any that have a 0 
> totalDiscountAmount from the run list, but we won't
> because by the time they are run the cart may have changed enough to get them 
> to go; also, certain actions like free shipping
> should always be run even though we won't know what the totalDiscountAmount 
> is at the time the promotion is run  
> -            // each ProductPromoUseInfo on the shopping cart will contain 
> it's total value, so add up all totals for each
> promoId and put them in a List of Maps 
> -            // create a List of Maps with productPromo and 
> totalDiscountAmount, use the Map sorter to sort them descending by
> totalDiscountAmount -
> -            // before sorting split into two lists and sort each list; one 
> list for promos that have a order total condition,
> and the other list for all promos that don't; then we'll always run the ones 
> that have no condition on the order total first 
> -            List<Map<Object, Object>> productPromoDiscountMapList = 
> FastList.newInstance();
> -            List<Map<Object, Object>> productPromoDiscountMapListOrderTotal 
> = FastList.newInstance();
> +            // NOTE: we can easily recognize the promos for the order total: 
> they are the ones with usage set to 0
> +            Iterator<ProductPromoUseInfo> promoUses = 
> cart.getProductPromoUseInfoIter();
> +            List<ProductPromoUseInfo> sortedPromoUses = new 
> ArrayList<ProductPromoUseInfo>();
> +            while (promoUses.hasNext()) {
> +                ProductPromoUseInfo promoUse = promoUses.next();
> +                sortedPromoUses.add(promoUse);
> +            }
> +            Collections.sort(sortedPromoUses);
> +            List<GenericValue> sortedExplodedProductPromoList = new 
> ArrayList<GenericValue>(sortedPromoUses.size());
> +            Map<String, Long> usesPerPromo = new HashMap<String, Long>();
> +            int indexOfFirstOrderTotalPromo = -1;
> +            for (ProductPromoUseInfo promoUse: sortedPromoUses) {
> +                GenericValue productPromo = 
> delegator.findOne("ProductPromo", UtilMisc.toMap("productPromoId",
> promoUse.getProductPromoId()), true); +                GenericValue 
> newProductPromo = (GenericValue)productPromo.clone();
> +                if (!usesPerPromo.containsKey(promoUse.getProductPromoId())) 
> {
> +                    usesPerPromo.put(promoUse.getProductPromoId(), 0l);
> +                }
> +                long uses = usesPerPromo.get(promoUse.getProductPromoId());
> +                uses = uses + 1;
> +                long useLimitPerOrder = 
> (newProductPromo.get("useLimitPerOrder") != null?
> newProductPromo.getLong("useLimitPerOrder"): -1); +                if 
> (useLimitPerOrder == -1 || uses < useLimitPerOrder) {
> +                    newProductPromo.set("useLimitPerOrder", uses);
> +                }
> +                usesPerPromo.put(promoUse.getProductPromoId(), uses);
> +                sortedExplodedProductPromoList.add(newProductPromo);
> +                if (indexOfFirstOrderTotalPromo == -1 && 
> BigDecimal.ZERO.equals(promoUse.getUsageWeight())) {
> +                    indexOfFirstOrderTotalPromo = 
> sortedExplodedProductPromoList.size() - 1;
> +                }
> +            }
> +            if (indexOfFirstOrderTotalPromo == -1) {
> +                indexOfFirstOrderTotalPromo = 
> sortedExplodedProductPromoList.size() - 1;
> +            }
> +
>             for (GenericValue productPromo : productPromoList) {
>                 Map<Object, Object> productPromoDiscountMap = 
> UtilGenerics.checkMap(UtilMisc.toMap("productPromo", productPromo,
>                 "totalDiscountAmount", 
> cart.getProductPromoUseTotalDiscount(productPromo.getString("productPromoId"))));
>  if
> (hasOrderTotalCondition(productPromo, delegator)) { -                   
> productPromoDiscountMapListOrderTotal.add(productPromoDiscountMap); +         
>            if
> (!usesPerPromo.containsKey(productPromo.getString("productPromoId"))) { +     
>                   
> sortedExplodedProductPromoList.add(productPromo); +                    }
>                 } else {
> -                    productPromoDiscountMapList.add(productPromoDiscountMap);
> +                    if 
> (!usesPerPromo.containsKey(productPromo.getString("productPromoId"))) {
> +                        if (indexOfFirstOrderTotalPromo != -1) {
> +                            
> sortedExplodedProductPromoList.add(indexOfFirstOrderTotalPromo, productPromo);
> +                        } else {
> +                            sortedExplodedProductPromoList.add(0, 
> productPromo);
> +                        }
> +                    }
>                 }
>             }
> 
> -
> -            // sort the Map List, do it ascending because the discount 
> amounts will be negative, so the lowest number is really
> the highest discount 
> -            productPromoDiscountMapList = 
> UtilMisc.sortMaps(productPromoDiscountMapList,
> UtilMisc.toList("+totalDiscountAmount")); 
> -            productPromoDiscountMapListOrderTotal = 
> UtilMisc.sortMaps(productPromoDiscountMapListOrderTotal,
> UtilMisc.toList("+totalDiscountAmount")); -
> -            
> productPromoDiscountMapList.addAll(productPromoDiscountMapListOrderTotal);
> -
> -            List<GenericValue> sortedProductPromoList = new 
> ArrayList<GenericValue>(productPromoDiscountMapList.size());
> -            Iterator<Map<Object, Object>> productPromoDiscountMapIter = 
> productPromoDiscountMapList.iterator();
> -            while (productPromoDiscountMapIter.hasNext()) {
> -                Map<Object, Object> productPromoDiscountMap = 
> UtilGenerics.checkMap(productPromoDiscountMapIter.next());
> -                GenericValue productPromo = (GenericValue) 
> productPromoDiscountMap.get("productPromo");
> -                sortedProductPromoList.add(productPromo);
> -                if (Debug.verboseOn()) Debug.logVerbose("Sorted Promo [" + 
> productPromo.getString("productPromoId") + "] with
> total discount: " + productPromoDiscountMap.get("totalDiscountAmount"), 
> module); 
> -            }
> -
>             // okay, all ready, do the real run, clearing the temporary 
> result first...
>             cart.clearAllPromotionInformation();
> -            runProductPromos(sortedProductPromoList, cart, delegator, 
> dispatcher, nowTimestamp, false);
> +            runProductPromos(sortedExplodedProductPromoList, cart, 
> delegator, dispatcher, nowTimestamp, false);
>         } catch (NumberFormatException e) {
>             Debug.logError(e, "Number not formatted correctly in promotion 
> rules, not completed...", module);
>         } catch (GenericEntityException e) {
> @@ -436,7 +457,7 @@ public class ProductPromoWorker {
>                                     GenericValue productPromoCode = 
> productPromoCodeIter.next();
>                                     String productPromoCodeId = 
> productPromoCode.getString("productPromoCodeId");
>                                     Long codeUseLimit = 
> getProductPromoCodeUseLimit(productPromoCode, partyId, delegator);
> -                                    if (runProductPromoRules(cart, 
> cartChanged, useLimit, true, productPromoCodeId,
> codeUseLimit, maxUseLimit, productPromo, productPromoRules, dispatcher, 
> delegator, nowTimestamp)) { +                            
>                                         if (runProductPromoRules(cart, 
> useLimit, true, productPromoCodeId, codeUseLimit,
>                                     maxUseLimit, productPromo, 
> productPromoRules, dispatcher, delegator, nowTimestamp)) {
> cartChanged = true; } 
> 
> @@ -448,7 +469,7 @@ public class ProductPromoWorker {
>                             }
>                         } else {
>                             try {
> -                                if (runProductPromoRules(cart, cartChanged, 
> useLimit, false, null, null, maxUseLimit,
> productPromo, productPromoRules, dispatcher, delegator, nowTimestamp)) { +    
>                             if
>                                     (runProductPromoRules(cart, useLimit, 
> false, null, null, maxUseLimit, productPromo,
>                                 productPromoRules, dispatcher, delegator, 
> nowTimestamp)) { cartChanged = true; }
>                             } catch (RuntimeException e) {
> @@ -735,8 +756,10 @@ public class ProductPromoWorker {
>         return promoDescBuf.toString();
>     }
> 
> -    protected static boolean runProductPromoRules(ShoppingCart cart, boolean 
> cartChanged, Long useLimit, boolean requireCode,
> String productPromoCodeId, Long codeUseLimit, long maxUseLimit, +    
> protected static boolean runProductPromoRules(ShoppingCart
>         cart, Long useLimit, boolean requireCode, String productPromoCodeId, 
> Long codeUseLimit, long maxUseLimit, GenericValue
> productPromo, List<GenericValue> productPromoRules, LocalDispatcher 
> dispatcher, Delegator delegator, Timestamp nowTimestamp)
> throws GenericEntityException, UseLimitException { +        boolean 
> cartChanged = false; +       
>         Map<ShoppingCartItem,BigDecimal> usageInfoMap = 
> prepareProductUsageInfoMap(cart); String productPromoId =
>         productPromo.getString("productPromoId"); while ((useLimit == null || 
> useLimit.longValue() >
>                 cart.getProductPromoUseCount(productPromoId)) && 
> (!requireCode || UtilValidate.isNotEmpty(productPromoCodeId)) &&
> @@ -755,17 +778,17 @@ public class ProductPromoWorker {
>                 // loop through conditions for rule, if any false, set 
> allConditionsTrue to false
>                 List<GenericValue> productPromoConds = 
> delegator.findByAnd("ProductPromoCond", UtilMisc.toMap("productPromoId",
>                 productPromo.get("productPromoId")), 
> UtilMisc.toList("productPromoCondSeqId"), true); productPromoConds =
> EntityUtil.filterByAnd(productPromoConds, 
> UtilMisc.toMap("productPromoRuleId", 
> productPromoRule.get("productPromoRuleId"))); -   
> // using the other method to consolodate cache entries because the same cache 
> is used elsewhere: List productPromoConds =
>                 productPromoRule.getRelated("ProductPromoCond", null, 
> UtilMisc.toList("productPromoCondSeqId"), true); +         
> // using the other method to consolidate cache entries because the same cache 
> is used elsewhere: List productPromoConds =
> productPromoRule.getRelated("ProductPromoCond", null, 
> UtilMisc.toList("productPromoCondSeqId"), true); if (Debug.verboseOn())
> Debug.logVerbose("Checking " + productPromoConds.size() + " conditions for 
> rule " + productPromoRule, module);   
> 
>                 Iterator<GenericValue> productPromoCondIter = 
> UtilMisc.toIterator(productPromoConds);
>                 while (productPromoCondIter != null && 
> productPromoCondIter.hasNext()) {
>                     GenericValue productPromoCond = 
> productPromoCondIter.next();
> 
> -                    boolean condResult = checkCondition(productPromoCond, 
> cart, delegator, dispatcher, nowTimestamp);
> +                    boolean conditionSatisfied = 
> checkCondition(productPromoCond, cart, delegator, dispatcher, nowTimestamp);
> 
>                     // any false condition will cause it to NOT perform the 
> action
> -                    if (condResult == false) {
> +                    if (!conditionSatisfied) {
>                         performActions = false;
>                         break;
>                     }
> @@ -797,13 +820,16 @@ public class ProductPromoWorker {
>             }
> 
>             if (promoUsed) {
> -                
> cart.addProductPromoUse(productPromo.getString("productPromoId"), 
> productPromoCodeId, totalDiscountAmount,
> quantityLeftInActions); +                // Get product use information from 
> the cart
> +                Map<ShoppingCartItem,BigDecimal> newUsageInfoMap = 
> prepareProductUsageInfoMap(cart);
> +                Map<ShoppingCartItem,BigDecimal> deltaUsageInfoMap = 
> prepareDeltaProductUsageInfoMap(usageInfoMap,
> newUsageInfoMap); +                usageInfoMap = newUsageInfoMap;
> +                
> cart.addProductPromoUse(productPromo.getString("productPromoId"), 
> productPromoCodeId, totalDiscountAmount,
>             quantityLeftInActions, deltaUsageInfoMap); } else {
>                 // the promotion was not used, don't try again until we 
> finish a full pass and come back to see the promo
>                 conditions are now satisfied based on changes to the cart 
> break;
>             }
> 
> -
>             if (cart.getProductPromoUseCount(productPromoId) > maxUseLimit) {
>                 throw new UseLimitException("ERROR: While calculating 
> promotions the promotion [" + productPromoId + "] action
>             was applied more than " + maxUseLimit + " times, so the 
> calculation has been ended. This should generally never
> happen unless you have bad rule definitions."); } @@ -812,6 +838,34 @@ public 
> class ProductPromoWorker {
>         return cartChanged;
>     }
> 
> +    private static Map<ShoppingCartItem,BigDecimal> 
> prepareProductUsageInfoMap(ShoppingCart cart) {
> +        Map<ShoppingCartItem,BigDecimal> usageInfoMap = new 
> HashMap<ShoppingCartItem, BigDecimal>();
> +        List<ShoppingCartItem> lineOrderedByBasePriceList = 
> cart.getLineListOrderedByBasePrice(false);
> +        for (ShoppingCartItem cartItem : lineOrderedByBasePriceList) {
> +            BigDecimal used = cartItem.getPromoQuantityUsed();
> +            if (used.compareTo(BigDecimal.ZERO) != 0) {
> +                usageInfoMap.put(cartItem, used);
> +            }
> +        }
> +        return usageInfoMap;
> +    }
> +
> +    private static Map<ShoppingCartItem,BigDecimal> 
> prepareDeltaProductUsageInfoMap(Map<ShoppingCartItem,BigDecimal> oldMap,
> Map<ShoppingCartItem,BigDecimal> newMap) { +        
> Map<ShoppingCartItem,BigDecimal> deltaUsageInfoMap = new
> HashMap<ShoppingCartItem, BigDecimal>(newMap); +        
> Iterator<ShoppingCartItem> cartLines = oldMap.keySet().iterator();
> +        while (cartLines.hasNext()) {
> +            ShoppingCartItem cartLine = cartLines.next();
> +            BigDecimal oldUsed = oldMap.get(cartLine);
> +            BigDecimal newUsed = newMap.get(cartLine);
> +            if (newUsed.compareTo(oldUsed) > 0) {
> +                deltaUsageInfoMap.put(cartLine, 
> newUsed.add(oldUsed.negate()));
> +            } else {
> +                deltaUsageInfoMap.remove(cartLine);
> +            }
> +        }
> +        return deltaUsageInfoMap;
> +    }
> +
>     protected static boolean checkCondition(GenericValue productPromoCond, 
> ShoppingCart cart, Delegator delegator,
>         LocalDispatcher dispatcher, Timestamp nowTimestamp) throws 
> GenericEntityException { String condValue =
>         productPromoCond.getString("condValue"); String otherValue = 
> productPromoCond.getString("otherValue");
> @@ -1772,8 +1826,8 @@ public class ProductPromoWorker {
>             actionResultInfo.ranAction = false;
>         }
> 
> +        // in action, if doesn't have enough quantity to use the promo at 
> all, remove candidate promo uses and increment
>         promoQuantityUsed; this should go for all actions, if any action runs 
> we confirm if (actionResultInfo.ranAction) {
> -            // in action, if doesn't have enough quantity to use the promo 
> at all, remove candidate promo uses and increment
>             promoQuantityUsed; this should go for all actions, if any action 
> runs we confirm
>         
> cart.confirmPromoRuleUse(productPromoAction.getString("productPromoId"),
>             productPromoAction.getString("productPromoRuleId")); } else {
> cart.resetPromoRuleUse(productPromoAction.getString("productPromoId"), 
> productPromoAction.getString("productPromoRuleId"));

Reply via email to